Initial Vectorail GC format library
This commit is contained in:
@@ -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';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user