Initial Vectorail GC format library

This commit is contained in:
2026-08-02 17:05:27 +02:00
commit 8c4e1bcacb
28 changed files with 3426 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
#include "gc/GcTargetEffect.hpp"
#include <cstdlib>
#include <iostream>
#include <set>
#include <string>
int main(int argc, char** argv) {
if (argc < 3) {
std::cerr << "usage: opencoaster-effect-probe EFC_DATA UV_DATA [first [last]]\n";
return 2;
}
GcTargetEffectBank bank;
std::string error;
if (!bank.load(argv[1], argv[2], &error)) {
std::cerr << error << '\n';
return 1;
}
const int first = argc > 3 ? std::atoi(argv[3]) : 0;
const int last = argc > 4 ? std::atoi(argv[4]) : first;
const int uvBase = argc > 5 ? std::atoi(argv[5]) : 0;
for (int effect = first; effect <= last; ++effect) {
std::cout << "effect " << effect << " lifetime=" << bank.lifetime(effect) << '\n';
const int lifetime = bank.lifetime(effect);
const int sampleTicks[] = {0, 1, lifetime / 2, lifetime > 0 ? lifetime - 1 : 0};
std::set<int> seenTicks;
for (const int tick : sampleTicks) {
if (!seenTicks.insert(tick).second) continue;
const auto sprites = bank.evaluate(effect, static_cast<float>(tick), uvBase);
std::cout << " tick " << tick << " sprites=" << sprites.size() << '\n';
for (const auto& sprite : sprites) {
const GcUvCell* cell = bank.uvCell(sprite.uvRecord, sprite.frame);
std::cout << " uv=" << sprite.uvRecord << " tex="
<< bank.uvTexture(sprite.uvRecord) << " frame=" << sprite.frame;
if (cell) {
std::cout << " cell=" << cell->x << ',' << cell->y << ' '
<< cell->width << 'x' << cell->height;
}
std::cout << " pos=" << sprite.offsetPixels.x << ',' << sprite.offsetPixels.y
<< " scale=" << sprite.scale.x << ',' << sprite.scale.y
<< " rot=" << sprite.rotationDegrees
<< " rgba=" << sprite.color.r << ',' << sprite.color.g << ','
<< sprite.color.b << ',' << sprite.color.a << '\n';
}
}
}
}
+99
View File
@@ -0,0 +1,99 @@
#include "gc/MtxArchive.hpp"
#include "gc/RvbScene.hpp"
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <iterator>
#include <limits>
#include <string>
#include <vector>
namespace fs = std::filesystem;
namespace {
bool readFile(const fs::path& path, std::vector<uint8_t>* bytes) {
std::ifstream file(path, std::ios::binary);
if (!file) return false;
bytes->assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
return true;
}
size_t textureIndex(const gc::RvbImageResource& image) {
static constexpr char prefix[] = "Image";
if (image.symbolName.rfind(prefix, 0) != 0 || image.symbolName.size() <= 5) {
return std::numeric_limits<size_t>::max();
}
size_t number = 0;
for (size_t i = 5; i < image.symbolName.size(); ++i) {
const char c = image.symbolName[i];
if (c < '0' || c > '9') return std::numeric_limits<size_t>::max();
number = number * 10 + static_cast<size_t>(c - '0');
}
return number == 0 ? std::numeric_limits<size_t>::max() : number - 1;
}
} // namespace
int main(int argc, char** argv) {
if (argc < 3 || argc > 4) {
std::cerr << "Usage: " << argv[0] << " <scene.rvb> <textures.mtx> [output-dir]\n";
return 2;
}
std::vector<uint8_t> rvbBytes;
std::vector<uint8_t> mtxBytes;
if (!readFile(argv[1], &rvbBytes) || !readFile(argv[2], &mtxBytes)) {
std::cerr << "could not read RVB/MTX input\n";
return 1;
}
gc::RvbScene scene;
gc::MtxArchive archive;
std::string error;
if (!gc::ParseRvbScene(rvbBytes, &scene, &error) ||
!gc::ParseMtxArchive(mtxBytes, &archive, &error)) {
std::cerr << "parse failed: " << error << '\n';
return 1;
}
if (scene.images.size() != archive.textures.size()) {
std::cerr << "resource mismatch: RVB images=" << scene.images.size()
<< " MTX textures=" << archive.textures.size() << '\n';
return 1;
}
const bool extract = argc == 4;
const fs::path output = extract ? fs::path(argv[3]) : fs::path();
if (extract) fs::create_directories(output);
for (const gc::RvbImageResource& image : scene.images) {
const size_t index = textureIndex(image);
if (index >= archive.textures.size()) {
std::cerr << "invalid MTX symbol index: " << image.symbolName << '\n';
return 1;
}
const gc::MtxTexture& texture = archive.textures[index];
if (image.width != texture.width || image.height != texture.height) {
std::cerr << "dimension mismatch at " << index << ": " << image.symbolName
<< " RVB=" << image.width << 'x' << image.height
<< " MTX=" << texture.width << 'x' << texture.height << '\n';
return 1;
}
std::cout << index << ' ' << image.symbolName << ' ' << texture.width << 'x'
<< texture.height << " bytes=" << texture.size << '\n';
if (extract) {
std::vector<uint8_t> dds;
if (!gc::ExtractMtxTextureDds(mtxBytes, texture, &dds, &error)) {
std::cerr << "extract failed: " << error << '\n';
return 1;
}
std::ofstream file(output / (image.symbolName + ".dds"), std::ios::binary);
file.write(reinterpret_cast<const char*>(dds.data()),
static_cast<std::streamsize>(dds.size()));
if (!file) {
std::cerr << "could not write extracted DDS\n";
return 1;
}
}
}
return 0;
}
+117
View File
@@ -0,0 +1,117 @@
#include "gc/RvbScene.hpp"
#include "gc/RvbLayout.hpp"
#include <cstdint>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <string>
#include <unordered_map>
#include <vector>
namespace {
void printNode(const gc::RvbNode& node, unsigned depth) {
std::cout << " " << std::string(depth * 2, ' ') << node.tag
<< " @0x" << std::hex << node.offset << "/0x" << node.size
<< std::dec << " local=" << node.localDataSize << '\n';
for (const gc::RvbNode& child : node.children) printNode(child, depth + 1);
}
bool probe(const std::string& path, bool tree, const std::string& symbol,
const gc::RvbSnapshotState& state) {
std::ifstream file(path, std::ios::binary);
if (!file) {
std::cerr << path << ": could not open\n";
return false;
}
std::vector<uint8_t> bytes((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
gc::RvbScene scene;
std::string error;
if (!gc::ParseRvbScene(bytes, &scene, &error)) {
std::cerr << path << ": parse failed: " << error << '\n';
return false;
}
std::vector<gc::RvbImageDraw> snapshot;
const bool built = symbol.empty()
? gc::BuildRvbSnapshot(bytes, scene, state, &snapshot, &error)
: gc::BuildRvbSymbolSnapshot(bytes, scene, symbol, state, &snapshot, &error);
if (!built) {
std::cerr << path << ": snapshot failed: " << error << '\n';
return false;
}
std::cout << path << "\n movie=" << scene.sourceWidth << 'x' << scene.sourceHeight
<< " @ " << static_cast<unsigned>(scene.framesPerSecond) << " fps"
<< " bindings=" << scene.bindings.size()
<< " images=" << scene.images.size()
<< " initialDraws=" << snapshot.size() << "\n chunks:";
for (const gc::RvbChunk& chunk : scene.chunks) {
std::cout << ' ' << chunk.tag << "@0x" << std::hex << chunk.offset
<< "/0x" << chunk.size << std::dec;
}
std::cout << "\n PREP bindings:\n";
for (const gc::RvbBinding& binding : scene.bindings) {
std::cout << " " << std::left << std::setw(28) << binding.action
<< ' ' << binding.instancePath << '\n';
}
if (tree) {
std::cout << " animation tree:\n";
for (const gc::RvbNode& root : scene.roots) printNode(root, 0);
std::cout << " initial draws:\n";
for (const gc::RvbImageDraw& draw : snapshot) {
std::cout << " " << draw.imageSymbol << " depth=" << draw.depth
<< " path=" << draw.instancePath
<< " rgba=(" << draw.color[0] << ',' << draw.color[1] << ','
<< draw.color[2] << ',' << draw.alpha << ')'
<< " tl=(" << draw.corners[0][0] << ',' << draw.corners[0][1]
<< ") br=(" << draw.corners[3][0] << ',' << draw.corners[3][1]
<< ")\n";
}
}
return true;
}
} // namespace
int main(int argc, char** argv) {
bool tree = false;
std::string symbol;
gc::RvbSnapshotState state;
int firstPath = 1;
while (firstPath < argc) {
const std::string option = argv[firstPath];
if (option == "--tree") {
tree = true;
++firstPath;
} else if (option == "--include-other") {
state.includeRootOther = true;
++firstPath;
} else if (option == "--symbol" && firstPath + 1 < argc) {
symbol = argv[firstPath + 1];
firstPath += 2;
} else if (option == "--state" && firstPath + 1 < argc) {
const std::string value = argv[firstPath + 1];
const size_t equals = value.find('=');
if (equals == std::string::npos) {
std::cerr << "--state expects /path=frame_label\n";
return 2;
}
state.frameByPath[value.substr(0, equals)] = value.substr(equals + 1);
firstPath += 2;
} else {
break;
}
}
if (argc <= firstPath) {
std::cerr << "Usage: " << argv[0]
<< " [--tree] [--include-other] [--symbol name]"
" [--state /path=frame] <scene.rvb> [scene.rvb ...]\n";
return 2;
}
bool ok = true;
for (int i = firstPath; i < argc; ++i) ok = probe(argv[i], tree, symbol, state) && ok;
return ok ? 0 : 1;
}
+163
View File
@@ -0,0 +1,163 @@
#include "gc/StageDat.hpp"
#include "gc/StagePattern.hpp"
#include <algorithm>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <map>
#include <string>
namespace {
bool probe(const std::string& path) {
gc::StageDat dat;
std::string err;
if (!gc::StageDat::LoadFromFile(path, dat, &err)) {
std::cerr << path << ": load failed: " << err << '\n';
return false;
}
gc::ParsedStagePattern stage;
if (!gc::ParseStagePattern(dat, &stage, &err)) {
std::cerr << path << ": parse failed: " << err << '\n';
return false;
}
std::map<uint8_t, size_t> types;
for (const gc::StageNote& note : stage.notes) ++types[static_cast<uint8_t>(note.type)];
std::map<uint8_t, size_t> cameraAnchorModes;
std::map<uint8_t, size_t> cameraFadeModes;
std::map<uint8_t, size_t> cameraProjectionTypes;
for (const gc::CameraPoint& camera : stage.cameras) {
++cameraAnchorModes[camera.aMode];
++cameraFadeModes[camera.fMode];
++cameraProjectionTypes[camera.projType];
}
size_t parentedObjects = 0;
size_t wireframeObjects = 0;
size_t flashingObjects = 0;
size_t noGlobalFadeObjects = 0;
size_t translucentObjects = 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;
}
std::cout << path
<< "\n chart=" << stage.config.chartName
<< " bgm=" << stage.config.bgmName
<< "\n track=" << stage.track.size()
<< " drawDistanceKeys=" << stage.drawDistances.size()
<< " drawBehind=" << stage.config.backwardsDrawDist
<< " drawAhead=" << stage.config.forwardDrawDist
<< " audioOffset=" << static_cast<unsigned>(stage.config.audioOffset)
<< " visualOffset=" << stage.config.visualOffset
<< " trackColors=(" << static_cast<unsigned>(stage.config.trackAheadColor.r) << ','
<< static_cast<unsigned>(stage.config.trackAheadColor.g) << ','
<< static_cast<unsigned>(stage.config.trackAheadColor.b) << ")->("
<< static_cast<unsigned>(stage.config.trackBehindColor.r) << ','
<< static_cast<unsigned>(stage.config.trackBehindColor.g) << ','
<< static_cast<unsigned>(stage.config.trackBehindColor.b) << ')'
<< " notes=" << stage.notes.size()
<< " cameras=" << stage.cameras.size()
<< " noteNames=" << stage.noteNames.size()
<< "\n background: particles=" << stage.particles.size()
<< " visualizerKeys=" << stage.visualizer.size()
<< " colorKeys=" << stage.backgroundColors.size()
<< " models=" << stage.modelNames.size()
<< " objects=" << stage.objects.size()
<< " parented=" << parentedObjects
<< " wire=" << wireframeObjects
<< " flashing=" << flashingObjects
<< " noGlobalFade=" << noGlobalFadeObjects
<< " translucent=" << translucentObjects;
if (!stage.notes.empty()) {
const auto [lo, hi] = std::minmax_element(
stage.notes.begin(), stage.notes.end(),
[](const gc::StageNote& a, const gc::StageNote& b) { return a.timeMs < b.timeMs; });
std::cout << " noteTimeMs=" << lo->timeMs << ".." << hi->timeMs;
}
if (!stage.particles.empty()) {
std::cout << "\n particleKeys:";
for (const auto& particle : stage.particles) {
std::cout << ' ' << particle.timeMs << ":on=" << particle.enabled
<< ",shape=" << particle.shape << ",tex=" << particle.texture
<< ",repeat=" << particle.repeatMeasure
<< ",life=" << particle.lifespanMeasure
<< ",group=" << particle.groupShapeSize;
}
}
if (!stage.visualizer.empty()) {
std::map<std::uint32_t, std::size_t> visualizerTypes;
for (const auto& key : stage.visualizer) ++visualizerTypes[key.type];
std::cout << "\n visualizerTypes:";
for (const auto& [type, count] : visualizerTypes) std::cout << ' ' << type << '=' << count;
}
if (!stage.drawDistances.empty()) {
std::cout << "\n drawDistance=" << stage.drawDistances.front().timeMs << ':'
<< stage.drawDistances.front().distance << ".."
<< stage.drawDistances.back().timeMs << ':'
<< stage.drawDistances.back().distance;
}
if (!stage.track.empty()) {
const auto& p = stage.track.front();
std::cout << "\n firstTrack=" << p.timeMs << ":(" << p.x << ',' << p.y << ',' << p.z << ')';
if (stage.track.size() > 1) {
const auto& q = stage.track[1];
std::cout << " nextTrack=" << q.timeMs << ":(" << q.x << ',' << q.y << ',' << q.z << ')';
}
}
if (!stage.cameras.empty()) {
const auto& c = stage.cameras.front();
std::cout << " firstCamera=" << c.timeMs << " dist=" << c.dist
<< " rot=(" << c.rotationA[0] << ',' << c.rotationA[1] << ',' << c.rotationB << ')'
<< " off=(" << c.originOff[0] << ',' << c.originOff[1] << ',' << c.originOff[2] << ')';
std::cout << "\n cameraModes: aMode";
for (const auto& [mode, count] : cameraAnchorModes) {
std::cout << ' ' << static_cast<unsigned>(mode) << '=' << count;
}
std::cout << " fMode";
for (const auto& [mode, count] : cameraFadeModes) {
std::cout << ' ' << static_cast<unsigned>(mode) << '=' << count;
}
std::cout << " projType";
for (const auto& [mode, count] : cameraProjectionTypes) {
std::cout << ' ' << static_cast<unsigned>(mode) << '=' << count;
}
}
std::cout << "\n noteTypes:";
for (const auto& [type, count] : types) {
std::cout << " 0x" << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<unsigned>(type) << std::dec << '/' << gc::NoteTypeName(type) << '=' << count;
}
if (!stage.fragmentShaderNames.empty()) {
std::cout << "\n fragmentShaders:";
for (size_t i = 0; i < stage.fragmentShaderNames.size(); ++i) {
std::cout << ' ' << i << '=' << stage.fragmentShaderNames[i];
}
}
if (!stage.modelNames.empty()) {
std::cout << "\n modelNames:";
for (size_t i = 0; i < stage.modelNames.size(); ++i) {
std::cout << ' ' << i << '=' << stage.modelNames[i];
}
}
std::cout << '\n';
return true;
}
} // namespace
int main(int argc, char** argv) {
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " <stage.dat> [stage.dat ...]\n";
return 2;
}
bool ok = true;
for (int i = 1; i < argc; ++i) ok = probe(argv[i]) && ok;
return ok ? 0 : 1;
}