Extend GC stage and TUMO parsing
This commit is contained in:
+14
-1
@@ -6,8 +6,17 @@ include(GNUInstallDirs)
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
option(VECTORAIL_GC_BUILD_TOOLS "Build format inspection tools" ON)
|
||||
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
set(VECTORAIL_GC_INSTALL_DEFAULT ON)
|
||||
else()
|
||||
set(VECTORAIL_GC_INSTALL_DEFAULT OFF)
|
||||
endif()
|
||||
option(VECTORAIL_GC_INSTALL "Generate VectorailGC install rules"
|
||||
${VECTORAIL_GC_INSTALL_DEFAULT})
|
||||
|
||||
if(NOT TARGET glm::glm)
|
||||
find_package(glm CONFIG REQUIRED)
|
||||
endif()
|
||||
|
||||
add_library(vectorail-gc
|
||||
src/EventStream.cpp
|
||||
@@ -51,6 +60,9 @@ if(VECTORAIL_GC_BUILD_TOOLS)
|
||||
add_executable(vectorail-gc-stage-probe tools/stage_probe.cpp)
|
||||
target_link_libraries(vectorail-gc-stage-probe PRIVATE Vectorail::GC)
|
||||
|
||||
add_executable(vectorail-gc-stage-ext-probe tools/stage_ext_probe.cpp)
|
||||
target_link_libraries(vectorail-gc-stage-ext-probe PRIVATE Vectorail::GC)
|
||||
|
||||
add_executable(vectorail-gc-rvb-probe tools/rvb_probe.cpp)
|
||||
target_link_libraries(vectorail-gc-rvb-probe PRIVATE Vectorail::GC)
|
||||
|
||||
@@ -61,6 +73,7 @@ if(VECTORAIL_GC_BUILD_TOOLS)
|
||||
target_link_libraries(vectorail-gc-effect-probe PRIVATE Vectorail::GCEffects)
|
||||
endif()
|
||||
|
||||
if(VECTORAIL_GC_INSTALL)
|
||||
install(TARGETS vectorail-gc vectorail-gc-effects
|
||||
EXPORT VectorailGCTargets
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
@@ -90,4 +103,4 @@ install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/VectorailGCConfigVersion.cmake
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/VectorailGC
|
||||
)
|
||||
|
||||
endif()
|
||||
|
||||
@@ -138,6 +138,9 @@ struct CameraPoint {
|
||||
struct ParticlePoint {
|
||||
static constexpr size_t kRecordSize = 44;
|
||||
|
||||
// TuneBGEffectData in Android libtune.so. GameScene::ExecFlowItem uses
|
||||
// these authored keys to spawn the stage's flowing background items.
|
||||
|
||||
uint32_t timeMs = 0;
|
||||
uint32_t enabled = 0;
|
||||
uint32_t shape = 0;
|
||||
@@ -157,6 +160,16 @@ struct VisualizerPoint {
|
||||
Color color;
|
||||
};
|
||||
|
||||
struct BackgroundImagePoint {
|
||||
static constexpr size_t kRecordSize = 20;
|
||||
|
||||
uint32_t timeMs = 0;
|
||||
uint32_t mode = 0;
|
||||
uint32_t atlasIndex = 0;
|
||||
int32_t textureIndex = -1;
|
||||
Color color;
|
||||
};
|
||||
|
||||
struct BackgroundColorPoint {
|
||||
static constexpr size_t kRecordSize = 22;
|
||||
|
||||
@@ -166,27 +179,27 @@ struct BackgroundColorPoint {
|
||||
Color bottomRight;
|
||||
Color bottomLeft;
|
||||
bool interpolateToNext = false;
|
||||
bool audioReactive = false;
|
||||
bool rhythmReactive = false;
|
||||
};
|
||||
|
||||
struct VisibilityPoint {
|
||||
uint32_t timeMs = 0;
|
||||
bool fadeOut = false;
|
||||
bool fadeIn = false;
|
||||
bool repeat = false; // wire +4: member of a repeating key range
|
||||
bool interpolate = false; // wire +5: interpolate/fade into this key
|
||||
bool visible = false;
|
||||
};
|
||||
|
||||
struct TransformPoint {
|
||||
uint32_t timeMs = 0;
|
||||
bool tweenTowards = false;
|
||||
bool tweenAway = false;
|
||||
bool repeat = false; // wire +4: member of a repeating key range
|
||||
bool interpolate = false; // wire +5: interpolate to the following key
|
||||
float value[3] = {0.0f, 0.0f, 0.0f};
|
||||
};
|
||||
|
||||
struct ObjectColorPoint {
|
||||
uint32_t timeMs = 0;
|
||||
bool tweenTowards = false;
|
||||
bool tweenAway = false;
|
||||
bool repeat = false; // wire +4: member of a repeating key range
|
||||
bool interpolate = false; // wire +5: interpolate to the following key
|
||||
Color color;
|
||||
};
|
||||
|
||||
@@ -221,13 +234,24 @@ struct ParsedStagePattern {
|
||||
std::vector<CameraPoint> cameras;
|
||||
std::vector<ParticlePoint> particles;
|
||||
std::vector<VisualizerPoint> visualizer;
|
||||
std::vector<std::string> backgroundTextureNames;
|
||||
std::vector<BackgroundImagePoint> backgroundImages;
|
||||
std::vector<BackgroundColorPoint> backgroundColors;
|
||||
std::vector<std::string> modelNames;
|
||||
std::vector<std::string> fragmentShaderNames;
|
||||
std::vector<StageObject> objects;
|
||||
};
|
||||
|
||||
struct StageExtension {
|
||||
std::array<std::vector<NoteSetting>, 4> noteSettings;
|
||||
uint32_t unknown = 0;
|
||||
std::vector<StageNote> notes;
|
||||
size_t trailingBytes = 0;
|
||||
};
|
||||
|
||||
bool ParseStagePattern(const StageDat& dat, ParsedStagePattern* out, std::string* err);
|
||||
bool ParseStageExtension(const std::vector<uint8_t>& bytes, StageExtension* out,
|
||||
std::string* err);
|
||||
|
||||
} // namespace gc
|
||||
|
||||
|
||||
@@ -17,9 +17,11 @@ struct TumoGeometry {
|
||||
std::vector<TumoVertex> triangles;
|
||||
std::vector<TumoVertex> solidLines;
|
||||
std::vector<TumoVertex> wireframeLines;
|
||||
std::uint32_t meshCount = 0;
|
||||
std::uint32_t nodeCount = 0;
|
||||
};
|
||||
|
||||
// Decodes the common one-mesh TUMO layout used by gameplay stage objects.
|
||||
// Decodes and aggregates every static TUMO mesh used by gameplay stage objects.
|
||||
// Polygon fans are expanded on the host so the PSP only has to submit flat
|
||||
// GU_TRIANGLES/GU_LINES arrays.
|
||||
bool LoadTumoGeometry(const std::string& path, TumoGeometry* out, std::string* error);
|
||||
|
||||
+152
-43
@@ -231,6 +231,43 @@ bool parseTrack(const StageDat& dat, uint32_t start, uint32_t next, std::vector<
|
||||
return true;
|
||||
}
|
||||
|
||||
bool readStageNote(PatternReader& r, StageNote* out, std::string* err) {
|
||||
if (!out) return false;
|
||||
StageNote note;
|
||||
uint8_t rawType = 0;
|
||||
if (!r.u32(¬e.timeMs) || !r.u8(&rawType) || !r.u8(¬e.typeOverride)) {
|
||||
return failAt("Note prefix", r.tell(), err);
|
||||
}
|
||||
note.type = static_cast<StageNoteType>(rawType);
|
||||
for (int16_t& value : note.params16) {
|
||||
uint16_t encoded = 0;
|
||||
if (!r.u16(&encoded)) return failAt("Note s16 fields", r.tell(), err);
|
||||
value = static_cast<int16_t>(encoded);
|
||||
}
|
||||
if (!r.u8(¬e.flag24)) return failAt("Note flag24", r.tell(), err);
|
||||
for (float& value : note.params25) {
|
||||
if (!r.f32(&value)) return failAt("Note float fields at +25", r.tell(), err);
|
||||
}
|
||||
if (!r.u8(¬e.flag37) || !r.u8(¬e.flag38)) {
|
||||
return failAt("Note flags at +37", r.tell(), err);
|
||||
}
|
||||
for (float& value : note.params39) {
|
||||
if (!r.f32(&value)) return failAt("Note float fields at +39", r.tell(), err);
|
||||
}
|
||||
for (uint32_t& value : note.params55) {
|
||||
if (!r.u32(&value)) return failAt("Note u32 fields at +55", r.tell(), err);
|
||||
}
|
||||
if (!r.f32(¬e.param67) || !r.u32(¬e.param71)) {
|
||||
return failAt("Note fields at +67", r.tell(), err);
|
||||
}
|
||||
for (float& value : note.params75) {
|
||||
if (!r.f32(&value)) return failAt("Note float fields at +75", r.tell(), err);
|
||||
}
|
||||
if (!r.u32(¬e.param95)) return failAt("Note field at +95", r.tell(), err);
|
||||
*out = note;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseNotes(
|
||||
const StageDat& dat,
|
||||
uint32_t start,
|
||||
@@ -257,26 +294,7 @@ bool parseNotes(
|
||||
out->reserve(count);
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
StageNote note;
|
||||
uint8_t rawType = 0;
|
||||
if (!r.u32(¬e.timeMs) ||
|
||||
!r.u8(&rawType) ||
|
||||
!r.u8(¬e.typeOverride)) {
|
||||
return failAt("Note prefix", r.tell(), err);
|
||||
}
|
||||
note.type = static_cast<StageNoteType>(rawType);
|
||||
for (int16_t& value : note.params16) {
|
||||
uint16_t encoded = 0;
|
||||
if (!r.u16(&encoded)) return failAt("Note s16 fields", r.tell(), err);
|
||||
value = static_cast<int16_t>(encoded);
|
||||
}
|
||||
if (!r.u8(¬e.flag24)) return failAt("Note flag24", r.tell(), err);
|
||||
for (float& value : note.params25) if (!r.f32(&value)) return failAt("Note float fields at +25", r.tell(), err);
|
||||
if (!r.u8(¬e.flag37) || !r.u8(¬e.flag38)) return failAt("Note flags at +37", r.tell(), err);
|
||||
for (float& value : note.params39) if (!r.f32(&value)) return failAt("Note float fields at +39", r.tell(), err);
|
||||
for (uint32_t& value : note.params55) if (!r.u32(&value)) return failAt("Note u32 fields at +55", r.tell(), err);
|
||||
if (!r.f32(¬e.param67) || !r.u32(¬e.param71)) return failAt("Note fields at +67", r.tell(), err);
|
||||
for (float& value : note.params75) if (!r.f32(&value)) return failAt("Note float fields at +75", r.tell(), err);
|
||||
if (!r.u32(¬e.param95)) return failAt("Note field at +95", r.tell(), err);
|
||||
if (!readStageNote(r, ¬e, err)) return false;
|
||||
out->push_back(note);
|
||||
}
|
||||
|
||||
@@ -368,6 +386,43 @@ bool parseVisualizer(const StageDat& dat, uint32_t start, uint32_t next,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseBackgroundImages(const StageDat& dat, uint32_t start, uint32_t next,
|
||||
std::vector<std::string>* textureNames,
|
||||
std::vector<BackgroundImagePoint>* out, std::string* err) {
|
||||
if (!textureNames || !out || !validOffset(dat, start)) return false;
|
||||
PatternReader r(dat.bytes, start, next);
|
||||
textureNames->clear();
|
||||
out->clear();
|
||||
|
||||
uint32_t count = 0;
|
||||
if (!r.u32(&count)) return failAt("BackgroundImage texture count", r.tell(), err);
|
||||
textureNames->reserve(count);
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
std::string name;
|
||||
if (!r.string8(&name)) return failAt("BackgroundImage texture name", r.tell(), err);
|
||||
textureNames->push_back(std::move(name));
|
||||
}
|
||||
|
||||
if (!r.u32(&count)) return failAt("BackgroundImage key count", r.tell(), err);
|
||||
if (!readCount(count, BackgroundImagePoint::kRecordSize, r.tell(), next,
|
||||
"BackgroundImage", err)) {
|
||||
return false;
|
||||
}
|
||||
out->reserve(count);
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
BackgroundImagePoint point;
|
||||
uint32_t textureIndex = 0;
|
||||
if (!r.u32(&point.timeMs) || !r.u32(&point.mode) || !r.u32(&point.atlasIndex) ||
|
||||
!r.u32(&textureIndex) || !r.color(&point.color)) {
|
||||
return failAt("BackgroundImage", r.tell(), err);
|
||||
}
|
||||
point.textureIndex = static_cast<int32_t>(textureIndex);
|
||||
out->push_back(point);
|
||||
}
|
||||
if (r.tell() != next) return failAt("BackgroundImage trailing bytes", r.tell(), err);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseBackgroundColors(const StageDat& dat, uint32_t start, uint32_t next,
|
||||
std::vector<BackgroundColorPoint>* out, std::string* err) {
|
||||
if (!out || !validOffset(dat, start)) return false;
|
||||
@@ -380,14 +435,14 @@ bool parseBackgroundColors(const StageDat& dat, uint32_t start, uint32_t next,
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
BackgroundColorPoint c;
|
||||
uint8_t interpolateToNext = 0;
|
||||
uint8_t audioReactive = 0;
|
||||
uint8_t rhythmReactive = 0;
|
||||
if (!r.u32(&c.timeMs) || !r.color(&c.topRight) || !r.color(&c.topLeft) ||
|
||||
!r.color(&c.bottomRight) || !r.color(&c.bottomLeft) ||
|
||||
!r.u8(&interpolateToNext) || !r.u8(&audioReactive)) {
|
||||
!r.u8(&interpolateToNext) || !r.u8(&rhythmReactive)) {
|
||||
return failAt("ColorTable entry", r.tell(), err);
|
||||
}
|
||||
c.interpolateToNext = interpolateToNext != 0;
|
||||
c.audioReactive = audioReactive != 0;
|
||||
c.rhythmReactive = rhythmReactive != 0;
|
||||
out->push_back(c);
|
||||
}
|
||||
if (r.tell() != next) return failAt("ColorTable trailing bytes", r.tell(), err);
|
||||
@@ -402,14 +457,14 @@ bool parseTransformArray(PatternReader& r, std::vector<TransformPoint>* out,
|
||||
out->reserve(count);
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
TransformPoint p;
|
||||
uint8_t towards = 0;
|
||||
uint8_t away = 0;
|
||||
if (!r.u32(&p.timeMs) || !r.u8(&towards) || !r.u8(&away) ||
|
||||
uint8_t repeat = 0;
|
||||
uint8_t interpolate = 0;
|
||||
if (!r.u32(&p.timeMs) || !r.u8(&repeat) || !r.u8(&interpolate) ||
|
||||
!r.f32(&p.value[0]) || !r.f32(&p.value[1]) || !r.f32(&p.value[2])) {
|
||||
return failAt(what, r.tell(), err);
|
||||
}
|
||||
p.tweenTowards = towards != 0;
|
||||
p.tweenAway = away != 0;
|
||||
p.repeat = repeat != 0;
|
||||
p.interpolate = interpolate != 0;
|
||||
out->push_back(p);
|
||||
}
|
||||
return true;
|
||||
@@ -418,8 +473,12 @@ bool parseTransformArray(PatternReader& r, std::vector<TransformPoint>* out,
|
||||
bool parseObjects(const StageDat& dat, uint32_t start, uint32_t next,
|
||||
std::vector<std::string>* modelNames,
|
||||
std::vector<std::string>* fragmentShaderNames,
|
||||
std::vector<StageObject>* out, std::string* err) {
|
||||
if (!modelNames || !fragmentShaderNames || !out || !validOffset(dat, start)) return false;
|
||||
std::vector<StageObject>* out, size_t* extensionStart,
|
||||
std::string* err) {
|
||||
if (!modelNames || !fragmentShaderNames || !out || !extensionStart ||
|
||||
!validOffset(dat, start)) {
|
||||
return false;
|
||||
}
|
||||
PatternReader r(dat.bytes, start, next);
|
||||
modelNames->clear();
|
||||
fragmentShaderNames->clear();
|
||||
@@ -466,14 +525,14 @@ bool parseObjects(const StageDat& dat, uint32_t start, uint32_t next,
|
||||
object.visibility.reserve(keyCount);
|
||||
for (uint32_t key = 0; key < keyCount; ++key) {
|
||||
VisibilityPoint p;
|
||||
uint8_t fadeOut = 0;
|
||||
uint8_t fadeIn = 0;
|
||||
uint8_t repeat = 0;
|
||||
uint8_t interpolate = 0;
|
||||
uint8_t visible = 0;
|
||||
if (!r.u32(&p.timeMs) || !r.u8(&fadeOut) || !r.u8(&fadeIn) || !r.u8(&visible)) {
|
||||
if (!r.u32(&p.timeMs) || !r.u8(&repeat) || !r.u8(&interpolate) || !r.u8(&visible)) {
|
||||
return failAt("Object visibility", r.tell(), err);
|
||||
}
|
||||
p.fadeOut = fadeOut != 0;
|
||||
p.fadeIn = fadeIn != 0;
|
||||
p.repeat = repeat != 0;
|
||||
p.interpolate = interpolate != 0;
|
||||
p.visible = visible != 0;
|
||||
object.visibility.push_back(p);
|
||||
}
|
||||
@@ -487,18 +546,21 @@ bool parseObjects(const StageDat& dat, uint32_t start, uint32_t next,
|
||||
object.colorChanges.reserve(keyCount);
|
||||
for (uint32_t key = 0; key < keyCount; ++key) {
|
||||
ObjectColorPoint p;
|
||||
uint8_t towards = 0;
|
||||
uint8_t away = 0;
|
||||
if (!r.u32(&p.timeMs) || !r.u8(&towards) || !r.u8(&away) || !r.color(&p.color)) {
|
||||
uint8_t repeat = 0;
|
||||
uint8_t interpolate = 0;
|
||||
if (!r.u32(&p.timeMs) || !r.u8(&repeat) || !r.u8(&interpolate) || !r.color(&p.color)) {
|
||||
return failAt("Object color key", r.tell(), err);
|
||||
}
|
||||
p.tweenTowards = towards != 0;
|
||||
p.tweenAway = away != 0;
|
||||
p.repeat = repeat != 0;
|
||||
p.interpolate = interpolate != 0;
|
||||
object.colorChanges.push_back(p);
|
||||
}
|
||||
out->push_back(std::move(object));
|
||||
}
|
||||
if (r.tell() != next) return failAt("ObjectArray trailing bytes", r.tell(), err);
|
||||
// LoadBGData records RomReadPosition() here and uses it as the base of the
|
||||
// extension offset table. Header word 11 points into that table, so it is
|
||||
// not a reliable object-stream end marker on older mobile charts.
|
||||
*extensionStart = r.tell();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -573,12 +635,17 @@ bool ParseStagePattern(const StageDat& dat, ParsedStagePattern* out, std::string
|
||||
if (err) *err = "invalid background section order";
|
||||
return false;
|
||||
}
|
||||
size_t objectExtensionStart = 0;
|
||||
if (!parseParticles(dat, tmp.header.particles, tmp.header.visualizer, &tmp.particles, err) ||
|
||||
!parseVisualizer(dat, tmp.header.visualizer, tmp.header.unk1, &tmp.visualizer, err) ||
|
||||
!parseBackgroundImages(dat, tmp.header.unk1, tmp.header.colors,
|
||||
&tmp.backgroundTextureNames, &tmp.backgroundImages, err) ||
|
||||
!parseBackgroundColors(dat, tmp.header.colors, tmp.header.objects, &tmp.backgroundColors, err) ||
|
||||
!parseObjects(dat, tmp.header.objects, tmp.header.colors2,
|
||||
&tmp.modelNames, &tmp.fragmentShaderNames, &tmp.objects, err) ||
|
||||
!parseObjectParents(dat, tmp.header.colors2, tmp.header.unk3, &tmp.objects, err)) {
|
||||
&tmp.modelNames, &tmp.fragmentShaderNames, &tmp.objects,
|
||||
&objectExtensionStart, err) ||
|
||||
!parseObjectParents(dat, static_cast<uint32_t>(objectExtensionStart), tmp.header.unk3,
|
||||
&tmp.objects, err)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -586,4 +653,46 @@ bool ParseStagePattern(const StageDat& dat, ParsedStagePattern* out, std::string
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseStageExtension(const std::vector<uint8_t>& bytes, StageExtension* out,
|
||||
std::string* err) {
|
||||
if (!out || bytes.size() < 6) {
|
||||
if (err) *err = "stage extension is smaller than its six-byte prefix";
|
||||
return false;
|
||||
}
|
||||
|
||||
StageExtension tmp;
|
||||
PatternReader r(bytes, 6, bytes.size());
|
||||
for (std::vector<NoteSetting>& list : tmp.noteSettings) {
|
||||
uint16_t count = 0;
|
||||
if (!r.u16(&count) ||
|
||||
!readCount(count, 12, r.tell(), bytes.size(), "StageExtension timing", err)) {
|
||||
return false;
|
||||
}
|
||||
list.reserve(count);
|
||||
for (uint16_t i = 0; i < count; ++i) {
|
||||
NoteSetting setting;
|
||||
if (!r.u32(&setting.timeMs) || !r.u32(&setting.mode) || !r.f32(&setting.value)) {
|
||||
return failAt("StageExtension timing", r.tell(), err);
|
||||
}
|
||||
list.push_back(setting);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t noteCount = 0;
|
||||
if (!r.u32(&tmp.unknown) || !r.u32(¬eCount) ||
|
||||
!readCount(noteCount, StageNote::kRecordSize, r.tell(), bytes.size(),
|
||||
"StageExtension notes", err)) {
|
||||
return false;
|
||||
}
|
||||
tmp.notes.reserve(noteCount);
|
||||
for (uint32_t i = 0; i < noteCount; ++i) {
|
||||
StageNote note;
|
||||
if (!readStageNote(r, ¬e, err)) return false;
|
||||
tmp.notes.push_back(note);
|
||||
}
|
||||
tmp.trailingBytes = bytes.size() - r.tell();
|
||||
*out = std::move(tmp);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace gc
|
||||
|
||||
+15
-3
@@ -72,9 +72,12 @@ bool LoadTumoGeometry(const std::string& path, TumoGeometry* out, std::string* e
|
||||
Reader reader(std::move(bytes));
|
||||
|
||||
std::uint32_t meshCount = 0;
|
||||
if (!reader.u32(&meshCount) || meshCount != 1) {
|
||||
return malformed(error, "unsupported TUMO mesh layout");
|
||||
if (!reader.u32(&meshCount) || meshCount == 0 || meshCount > 100000) {
|
||||
return malformed(error, "invalid TUMO mesh count");
|
||||
}
|
||||
out->meshCount = meshCount;
|
||||
|
||||
for (std::uint32_t mesh = 0; mesh < meshCount; ++mesh) {
|
||||
std::uint32_t nameCount = 0;
|
||||
if (!reader.u32(&nameCount) || nameCount > 4096) return malformed(error, "invalid TUMO names");
|
||||
for (std::uint32_t i = 0; i < nameCount; ++i) {
|
||||
@@ -146,7 +149,7 @@ bool LoadTumoGeometry(const std::string& path, TumoGeometry* out, std::string* e
|
||||
if (!reader.u32(&lineCount) || lineCount > 10000000) {
|
||||
return malformed(error, "invalid TUMO edge count");
|
||||
}
|
||||
out->wireframeLines.reserve(static_cast<std::size_t>(lineCount) * 2);
|
||||
out->wireframeLines.reserve(out->wireframeLines.size() + static_cast<std::size_t>(lineCount) * 2);
|
||||
for (std::uint64_t line = 0; line < static_cast<std::uint64_t>(lineCount) * 2; ++line) {
|
||||
std::uint32_t index = 0;
|
||||
if (!reader.u32(&index) || index >= vertices.size()) {
|
||||
@@ -154,6 +157,15 @@ bool LoadTumoGeometry(const std::string& path, TumoGeometry* out, std::string* e
|
||||
}
|
||||
out->wireframeLines.push_back(vertices[index]);
|
||||
}
|
||||
|
||||
std::uint32_t nodeCount = 0;
|
||||
if (!reader.u32(&nodeCount)) return malformed(error, "missing TUMO node count");
|
||||
out->nodeCount += nodeCount;
|
||||
if (nodeCount != 0 && mesh + 1 != meshCount) {
|
||||
return malformed(error, "unsupported TUMO nodes before the final mesh");
|
||||
}
|
||||
if (nodeCount != 0) break;
|
||||
}
|
||||
if (out->triangles.empty() && out->solidLines.empty() && out->wireframeLines.empty()) {
|
||||
return malformed(error, "TUMO model contains no geometry");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#include "gc/StagePattern.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
std::cerr << "Usage: " << argv[0] << " <stage_ext.dat> [...]\n";
|
||||
return 2;
|
||||
}
|
||||
|
||||
bool okay = true;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::ifstream file(argv[i], std::ios::binary);
|
||||
if (!file) {
|
||||
std::cerr << argv[i] << ": could not open file\n";
|
||||
okay = false;
|
||||
continue;
|
||||
}
|
||||
std::vector<uint8_t> bytes((std::istreambuf_iterator<char>(file)), {});
|
||||
gc::StageExtension extension;
|
||||
std::string error;
|
||||
if (!gc::ParseStageExtension(bytes, &extension, &error)) {
|
||||
std::cerr << argv[i] << ": " << error << '\n';
|
||||
okay = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::map<uint8_t, size_t> types;
|
||||
for (const gc::StageNote& note : extension.notes) {
|
||||
++types[static_cast<uint8_t>(note.type)];
|
||||
}
|
||||
std::cout << argv[i] << ": notes=" << extension.notes.size()
|
||||
<< " trailing=" << extension.trailingBytes << " timing=";
|
||||
for (const auto& list : extension.noteSettings) std::cout << list.size() << ',';
|
||||
std::cout << " types=";
|
||||
for (const auto& [type, count] : types) {
|
||||
std::cout << static_cast<unsigned>(type) << ':' << count << ',';
|
||||
}
|
||||
std::cout << '\n';
|
||||
}
|
||||
return okay ? 0 : 1;
|
||||
}
|
||||
+50
-1
@@ -39,12 +39,29 @@ bool probe(const std::string& path) {
|
||||
size_t flashingObjects = 0;
|
||||
size_t noGlobalFadeObjects = 0;
|
||||
size_t translucentObjects = 0;
|
||||
size_t objectAnimationKeys = 0;
|
||||
size_t objectRepeatKeys = 0;
|
||||
size_t objectInterpolateKeys = 0;
|
||||
size_t objectDifferingFlagKeys = 0;
|
||||
const auto countAnimationKeys = [&](const auto& keys) {
|
||||
objectAnimationKeys += keys.size();
|
||||
for (const auto& key : keys) {
|
||||
objectRepeatKeys += key.repeat ? 1 : 0;
|
||||
objectInterpolateKeys += key.interpolate ? 1 : 0;
|
||||
objectDifferingFlagKeys += key.repeat != key.interpolate ? 1 : 0;
|
||||
}
|
||||
};
|
||||
for (const gc::StageObject& object : stage.objects) {
|
||||
if (object.parentIndex >= 0) ++parentedObjects;
|
||||
if (object.wireframe) ++wireframeObjects;
|
||||
if (object.flashing) ++flashingObjects;
|
||||
if (object.unknownFlag) ++noGlobalFadeObjects;
|
||||
if (object.color[3] < 0.999f) ++translucentObjects;
|
||||
countAnimationKeys(object.visibility);
|
||||
countAnimationKeys(object.movement);
|
||||
countAnimationKeys(object.scaling);
|
||||
countAnimationKeys(object.rotations);
|
||||
countAnimationKeys(object.colorChanges);
|
||||
}
|
||||
|
||||
std::cout << path
|
||||
@@ -67,6 +84,8 @@ bool probe(const std::string& path) {
|
||||
<< " noteNames=" << stage.noteNames.size()
|
||||
<< "\n background: particles=" << stage.particles.size()
|
||||
<< " visualizerKeys=" << stage.visualizer.size()
|
||||
<< " imageTextures=" << stage.backgroundTextureNames.size()
|
||||
<< " imageKeys=" << stage.backgroundImages.size()
|
||||
<< " colorKeys=" << stage.backgroundColors.size()
|
||||
<< " models=" << stage.modelNames.size()
|
||||
<< " objects=" << stage.objects.size()
|
||||
@@ -74,7 +93,11 @@ bool probe(const std::string& path) {
|
||||
<< " wire=" << wireframeObjects
|
||||
<< " flashing=" << flashingObjects
|
||||
<< " noGlobalFade=" << noGlobalFadeObjects
|
||||
<< " translucent=" << translucentObjects;
|
||||
<< " translucent=" << translucentObjects
|
||||
<< " objectKeys=" << objectAnimationKeys
|
||||
<< " repeat=" << objectRepeatKeys
|
||||
<< " interpolate=" << objectInterpolateKeys
|
||||
<< " differingFlags=" << objectDifferingFlagKeys;
|
||||
if (!stage.notes.empty()) {
|
||||
const auto [lo, hi] = std::minmax_element(
|
||||
stage.notes.begin(), stage.notes.end(),
|
||||
@@ -96,6 +119,32 @@ bool probe(const std::string& path) {
|
||||
for (const auto& key : stage.visualizer) ++visualizerTypes[key.type];
|
||||
std::cout << "\n visualizerTypes:";
|
||||
for (const auto& [type, count] : visualizerTypes) std::cout << ' ' << type << '=' << count;
|
||||
std::cout << "\n visualizerKeys:";
|
||||
for (const auto& key : stage.visualizer) {
|
||||
std::cout << ' ' << key.timeMs << ":type=" << key.type
|
||||
<< ",rgba=" << static_cast<unsigned>(key.color.r) << ','
|
||||
<< static_cast<unsigned>(key.color.g) << ','
|
||||
<< static_cast<unsigned>(key.color.b) << ','
|
||||
<< static_cast<unsigned>(key.color.a);
|
||||
}
|
||||
}
|
||||
if (!stage.backgroundTextureNames.empty()) {
|
||||
std::cout << "\n backgroundTextures:";
|
||||
for (size_t i = 0; i < stage.backgroundTextureNames.size(); ++i) {
|
||||
std::cout << ' ' << i << '=' << stage.backgroundTextureNames[i];
|
||||
}
|
||||
}
|
||||
if (!stage.backgroundImages.empty()) {
|
||||
std::cout << "\n backgroundImageKeys:";
|
||||
for (const auto& image : stage.backgroundImages) {
|
||||
std::cout << ' ' << image.timeMs << ":mode=" << image.mode
|
||||
<< ",atlas=" << image.atlasIndex
|
||||
<< ",texture=" << image.textureIndex
|
||||
<< ",rgba=" << static_cast<unsigned>(image.color.r) << ','
|
||||
<< static_cast<unsigned>(image.color.g) << ','
|
||||
<< static_cast<unsigned>(image.color.b) << ','
|
||||
<< static_cast<unsigned>(image.color.a);
|
||||
}
|
||||
}
|
||||
if (!stage.drawDistances.empty()) {
|
||||
std::cout << "\n drawDistance=" << stage.drawDistances.front().timeMs << ':'
|
||||
|
||||
Reference in New Issue
Block a user