590 lines
23 KiB
C++
590 lines
23 KiB
C++
#include "gc/StagePattern.hpp"
|
|
|
|
#include <cstring>
|
|
#include <limits>
|
|
#include <sstream>
|
|
|
|
namespace gc {
|
|
|
|
namespace {
|
|
|
|
class PatternReader {
|
|
public:
|
|
PatternReader(const std::vector<uint8_t>& bytes, size_t pos, size_t end)
|
|
: bytes_(bytes), pos_(pos), end_(end) {}
|
|
|
|
size_t tell() const { return pos_; }
|
|
|
|
bool u8(uint8_t* out) {
|
|
if (!out || !need(1)) return false;
|
|
*out = bytes_[pos_++];
|
|
return true;
|
|
}
|
|
|
|
bool u16(uint16_t* out) {
|
|
if (!out || !need(2)) return false;
|
|
*out = static_cast<uint16_t>((static_cast<uint16_t>(bytes_[pos_]) << 8) |
|
|
static_cast<uint16_t>(bytes_[pos_ + 1]));
|
|
pos_ += 2;
|
|
return true;
|
|
}
|
|
|
|
bool u32(uint32_t* out) {
|
|
if (!out || !need(4)) return false;
|
|
*out = (static_cast<uint32_t>(bytes_[pos_ + 0]) << 24) |
|
|
(static_cast<uint32_t>(bytes_[pos_ + 1]) << 16) |
|
|
(static_cast<uint32_t>(bytes_[pos_ + 2]) << 8) |
|
|
(static_cast<uint32_t>(bytes_[pos_ + 3]));
|
|
pos_ += 4;
|
|
return true;
|
|
}
|
|
|
|
bool f32(float* out) {
|
|
uint32_t u = 0;
|
|
if (!u32(&u) || !out) return false;
|
|
static_assert(sizeof(float) == sizeof(uint32_t), "float must be 32-bit");
|
|
std::memcpy(out, &u, sizeof(float));
|
|
return true;
|
|
}
|
|
|
|
bool color(Color* out) {
|
|
if (!out || !need(4)) return false;
|
|
out->r = bytes_[pos_ + 0];
|
|
out->g = bytes_[pos_ + 1];
|
|
out->b = bytes_[pos_ + 2];
|
|
out->a = bytes_[pos_ + 3];
|
|
pos_ += 4;
|
|
return true;
|
|
}
|
|
|
|
bool string8(std::string* out) {
|
|
uint8_t len = 0;
|
|
if (!u8(&len)) return false;
|
|
return sizedString(len, out);
|
|
}
|
|
|
|
bool string16(std::string* out) {
|
|
uint16_t len = 0;
|
|
if (!u16(&len)) return false;
|
|
return sizedString(len, out);
|
|
}
|
|
|
|
private:
|
|
bool need(size_t n) const {
|
|
return pos_ <= end_ && n <= end_ - pos_ && pos_ + n <= bytes_.size();
|
|
}
|
|
|
|
bool sizedString(size_t len, std::string* out) {
|
|
if (!out || !need(len)) return false;
|
|
out->assign(reinterpret_cast<const char*>(bytes_.data() + pos_), len);
|
|
while (!out->empty() && out->back() == '\0') out->pop_back();
|
|
pos_ += len;
|
|
return true;
|
|
}
|
|
|
|
const std::vector<uint8_t>& bytes_;
|
|
size_t pos_ = 0;
|
|
size_t end_ = 0;
|
|
};
|
|
|
|
bool failAt(const char* what, size_t off, std::string* err) {
|
|
if (err) {
|
|
std::ostringstream oss;
|
|
oss << "failed to parse " << what << " at 0x" << std::hex << off;
|
|
*err = oss.str();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool validOffset(const StageDat& dat, uint32_t off) {
|
|
return off < dat.bytes.size();
|
|
}
|
|
|
|
bool validEndOffset(const StageDat& dat, uint32_t off) {
|
|
return off <= dat.bytes.size();
|
|
}
|
|
|
|
bool parseHeader(const StageDat& dat, StageHeader* out, std::string* err) {
|
|
if (!out) return false;
|
|
if (dat.headerWords.size() < 10) {
|
|
if (err) *err = "file too small for stage header";
|
|
return false;
|
|
}
|
|
out->stageCfg = dat.headerWords[0];
|
|
out->trackDrawDist = dat.headerWords[1];
|
|
out->track = dat.headerWords[2];
|
|
out->notes = dat.headerWords[3];
|
|
out->camera = dat.headerWords[4];
|
|
out->particles = dat.headerWords[5];
|
|
out->visualizer = dat.headerWords[6];
|
|
out->unk1 = dat.headerWords[7];
|
|
out->colors = dat.headerWords[8];
|
|
out->objects = dat.headerWords[9];
|
|
// Older stage revisions have an 11-word header and place ColorTable2 in
|
|
// slot 10. The 13-word revision used by game471 inserts a scalar at 10,
|
|
// moves ColorTable2 to 11, then appends another scalar.
|
|
if (dat.headerWords.size() == 11) {
|
|
out->colors2 = dat.headerWords[10];
|
|
} else if (dat.headerWords.size() >= 13) {
|
|
out->unk2 = dat.headerWords[10];
|
|
out->colors2 = dat.headerWords[11];
|
|
out->unk3 = dat.headerWords[12];
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool parseStageConfig(const StageDat& dat, uint32_t start, uint32_t next, StageConfig* out, std::string* err) {
|
|
if (!out || !validOffset(dat, start)) return false;
|
|
PatternReader r(dat.bytes, start, next);
|
|
uint16_t bpmCount = 0;
|
|
|
|
if (!r.f32(&out->endTime1) ||
|
|
!r.f32(&out->endTime2) ||
|
|
!r.f32(&out->outroTime) ||
|
|
!r.u16(&bpmCount)) {
|
|
return failAt("StageConfig header", r.tell(), err);
|
|
}
|
|
|
|
out->bpmChanges.clear();
|
|
out->bpmChanges.reserve(bpmCount);
|
|
for (uint16_t i = 0; i < bpmCount; i++) {
|
|
BpmChange bp;
|
|
if (!r.u32(&bp.timeMs) || !r.u32(&bp.bpm)) return failAt("BpmChange", r.tell(), err);
|
|
out->bpmChanges.push_back(bp);
|
|
}
|
|
|
|
for (std::vector<NoteSetting>& list : out->noteSettings) {
|
|
uint16_t count = 0;
|
|
if (!r.u16(&count)) return failAt("NoteSetting count", r.tell(), err);
|
|
list.clear();
|
|
list.reserve(count);
|
|
for (uint16_t i = 0; i < count; ++i) {
|
|
NoteSetting ns;
|
|
if (!r.u32(&ns.timeMs) || !r.u32(&ns.mode) || !r.f32(&ns.value)) {
|
|
return failAt("NoteSetting", r.tell(), err);
|
|
}
|
|
list.push_back(ns);
|
|
}
|
|
}
|
|
|
|
if (!r.string16(&out->chartName) ||
|
|
!r.string16(&out->chartName2) ||
|
|
!r.string16(&out->bgmName) ||
|
|
!r.string16(&out->shotName) ||
|
|
!r.f32(&out->backwardsDrawDist) ||
|
|
!r.f32(&out->forwardDrawDist) ||
|
|
!r.color(&out->trackAheadColor) ||
|
|
!r.color(&out->trackBehindColor) ||
|
|
!r.u8(&out->audioOffset) ||
|
|
!r.f32(&out->visualOffset) ||
|
|
!r.color(&out->unkColor)) {
|
|
return failAt("StageConfig tail", r.tell(), err);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool readCount(uint32_t count, size_t recordSize, size_t start, size_t end, const char* what, std::string* err) {
|
|
const size_t maxCount = (end > start) ? ((end - start) / recordSize) : 0;
|
|
if (count > maxCount) {
|
|
if (err) {
|
|
std::ostringstream oss;
|
|
oss << what << " count " << count << " exceeds section capacity " << maxCount;
|
|
*err = oss.str();
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool parseDrawDistances(const StageDat& dat, uint32_t start, uint32_t next, std::vector<DrawDistancePoint>* out, std::string* err) {
|
|
if (!out || !validOffset(dat, start)) return false;
|
|
out->clear();
|
|
PatternReader r(dat.bytes, start, next);
|
|
uint32_t count = 0;
|
|
if (!r.u32(&count)) return failAt("TrackDrawDist count", r.tell(), err);
|
|
if (!readCount(count, 8, r.tell(), next, "TrackDrawDist", err)) return false;
|
|
out->reserve(count);
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
DrawDistancePoint p;
|
|
if (!r.u32(&p.timeMs) || !r.f32(&p.distance)) return failAt("DrawDistance", r.tell(), err);
|
|
out->push_back(p);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool parseTrack(const StageDat& dat, uint32_t start, uint32_t next, std::vector<TrackPiece>* out, std::string* err) {
|
|
if (!out || !validOffset(dat, start)) return false;
|
|
out->clear();
|
|
PatternReader r(dat.bytes, start, next);
|
|
uint32_t count = 0;
|
|
if (!r.u32(&count)) return failAt("TrackPieceArray count", r.tell(), err);
|
|
if (!readCount(count, 16, r.tell(), next, "TrackPieceArray", err)) return false;
|
|
out->reserve(count);
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
TrackPiece p;
|
|
if (!r.u32(&p.timeMs) || !r.f32(&p.x) || !r.f32(&p.y) || !r.f32(&p.z)) {
|
|
return failAt("TrackPiece", r.tell(), err);
|
|
}
|
|
out->push_back(p);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool parseNotes(
|
|
const StageDat& dat,
|
|
uint32_t start,
|
|
uint32_t next,
|
|
std::vector<std::string>* names,
|
|
std::vector<StageNote>* out,
|
|
std::string* err) {
|
|
if (!names || !out || !validOffset(dat, start)) return false;
|
|
names->clear();
|
|
out->clear();
|
|
PatternReader r(dat.bytes, start, next);
|
|
uint32_t nameCount = 0;
|
|
uint32_t count = 0;
|
|
if (!r.u32(&nameCount)) return failAt("NoteArray name count", r.tell(), err);
|
|
names->reserve(nameCount);
|
|
for (uint32_t i = 0; i < nameCount; ++i) {
|
|
std::string name;
|
|
if (!r.string8(&name)) return failAt("NoteArray name", r.tell(), err);
|
|
names->push_back(std::move(name));
|
|
}
|
|
if (!r.u32(&count)) return failAt("NoteArray note count", r.tell(), err);
|
|
if (!readCount(count, StageNote::kRecordSize, r.tell(), next, "NoteArray", err)) return false;
|
|
|
|
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);
|
|
out->push_back(note);
|
|
}
|
|
|
|
if (r.tell() != next) {
|
|
if (err) {
|
|
std::ostringstream oss;
|
|
oss << "NoteArray leaves " << (next - r.tell()) << " trailing bytes";
|
|
*err = oss.str();
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool parseCameras(const StageDat& dat, uint32_t start, uint32_t next, std::vector<CameraPoint>* out, std::string* err) {
|
|
if (!out || !validOffset(dat, start)) return false;
|
|
out->clear();
|
|
PatternReader r(dat.bytes, start, next);
|
|
uint32_t count = 0;
|
|
if (!r.u32(&count)) return failAt("CameraArray count", r.tell(), err);
|
|
if (!readCount(count, 59, r.tell(), next, "CameraArray", err)) return false;
|
|
out->reserve(count);
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
CameraPoint c;
|
|
if (!r.u32(&c.timeMs) ||
|
|
!r.u8(&c.aMode) ||
|
|
!r.u8(&c.fMode) ||
|
|
!r.f32(&c.dist) ||
|
|
!r.f32(&c.rotationA[0]) ||
|
|
!r.f32(&c.rotationA[1]) ||
|
|
!r.f32(&c.originOff[0]) ||
|
|
!r.f32(&c.originOff[1]) ||
|
|
!r.f32(&c.originOff[2]) ||
|
|
!r.u8(&c.projType) ||
|
|
!r.f32(&c.fieldFar[0]) ||
|
|
!r.f32(&c.fieldFar[1]) ||
|
|
!r.f32(&c.fieldFar[2]) ||
|
|
!r.f32(&c.fieldNear[0]) ||
|
|
!r.f32(&c.fieldNear[1]) ||
|
|
!r.f32(&c.fieldNear[2]) ||
|
|
!r.f32(&c.rotationB)) {
|
|
return failAt("Camera", r.tell(), err);
|
|
}
|
|
out->push_back(c);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool parseParticles(const StageDat& dat, uint32_t start, uint32_t next,
|
|
std::vector<ParticlePoint>* out, std::string* err) {
|
|
if (!out || !validOffset(dat, start)) return false;
|
|
out->clear();
|
|
PatternReader r(dat.bytes, start, next);
|
|
uint32_t count = 0;
|
|
if (!r.u32(&count)) return failAt("ParticleArray count", r.tell(), err);
|
|
if (!readCount(count, ParticlePoint::kRecordSize, r.tell(), next, "ParticleArray", err)) return false;
|
|
out->reserve(count);
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
ParticlePoint p;
|
|
if (!r.u32(&p.timeMs) || !r.u32(&p.enabled) || !r.u32(&p.shape) || !r.u32(&p.texture) ||
|
|
!r.color(&p.color) || !r.f32(&p.velocity[0]) || !r.f32(&p.velocity[1]) ||
|
|
!r.f32(&p.velocity[2]) || !r.f32(&p.repeatMeasure) || !r.f32(&p.lifespanMeasure) ||
|
|
!r.u32(&p.groupShapeSize)) {
|
|
return failAt("Particle", r.tell(), err);
|
|
}
|
|
out->push_back(p);
|
|
}
|
|
if (r.tell() != next) return failAt("ParticleArray trailing bytes", r.tell(), err);
|
|
return true;
|
|
}
|
|
|
|
bool parseVisualizer(const StageDat& dat, uint32_t start, uint32_t next,
|
|
std::vector<VisualizerPoint>* out, std::string* err) {
|
|
if (!out || !validOffset(dat, start)) return false;
|
|
out->clear();
|
|
PatternReader r(dat.bytes, start, next);
|
|
uint32_t count = 0;
|
|
if (!r.u32(&count)) return failAt("VisualizerArray count", r.tell(), err);
|
|
if (!readCount(count, VisualizerPoint::kRecordSize, r.tell(), next, "VisualizerArray", err)) return false;
|
|
out->reserve(count);
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
VisualizerPoint v;
|
|
if (!r.u32(&v.timeMs) || !r.u32(&v.type) || !r.color(&v.color)) {
|
|
return failAt("Visualizer", r.tell(), err);
|
|
}
|
|
out->push_back(v);
|
|
}
|
|
if (r.tell() != next) return failAt("VisualizerArray 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;
|
|
out->clear();
|
|
PatternReader r(dat.bytes, start, next);
|
|
uint32_t count = 0;
|
|
if (!r.u32(&count)) return failAt("ColorTable count", r.tell(), err);
|
|
if (!readCount(count, BackgroundColorPoint::kRecordSize, r.tell(), next, "ColorTable", err)) return false;
|
|
out->reserve(count);
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
BackgroundColorPoint c;
|
|
uint8_t interpolateToNext = 0;
|
|
uint8_t audioReactive = 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)) {
|
|
return failAt("ColorTable entry", r.tell(), err);
|
|
}
|
|
c.interpolateToNext = interpolateToNext != 0;
|
|
c.audioReactive = audioReactive != 0;
|
|
out->push_back(c);
|
|
}
|
|
if (r.tell() != next) return failAt("ColorTable trailing bytes", r.tell(), err);
|
|
return true;
|
|
}
|
|
|
|
bool parseTransformArray(PatternReader& r, std::vector<TransformPoint>* out,
|
|
const char* what, std::string* err) {
|
|
uint32_t count = 0;
|
|
if (!r.u32(&count)) return failAt(what, r.tell(), err);
|
|
out->clear();
|
|
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) ||
|
|
!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;
|
|
out->push_back(p);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
PatternReader r(dat.bytes, start, next);
|
|
modelNames->clear();
|
|
fragmentShaderNames->clear();
|
|
out->clear();
|
|
|
|
uint32_t count = 0;
|
|
if (!r.u32(&count)) return failAt("ObjectArray model name count", r.tell(), err);
|
|
modelNames->reserve(count);
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
std::string name;
|
|
if (!r.string8(&name)) return failAt("ObjectArray model name", r.tell(), err);
|
|
modelNames->push_back(std::move(name));
|
|
}
|
|
if (!r.u32(&count)) return failAt("ObjectArray shader name count", r.tell(), err);
|
|
fragmentShaderNames->reserve(count);
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
std::string name;
|
|
if (!r.string8(&name)) return failAt("ObjectArray shader name", r.tell(), err);
|
|
fragmentShaderNames->push_back(std::move(name));
|
|
}
|
|
if (!r.u32(&count)) return failAt("ObjectArray count", r.tell(), err);
|
|
out->reserve(count);
|
|
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
StageObject object;
|
|
uint8_t wireframe = 0;
|
|
uint8_t flashing = 0;
|
|
uint8_t unknown = 0;
|
|
if (!r.u32(&object.model) || !r.u32(&object.fragmentShader) ||
|
|
!r.u8(&wireframe) || !r.u8(&flashing) || !r.u8(&unknown)) {
|
|
return failAt("Object prefix", r.tell(), err);
|
|
}
|
|
object.wireframe = wireframe != 0;
|
|
object.flashing = flashing != 0;
|
|
object.unknownFlag = unknown != 0;
|
|
for (float& value : object.position) if (!r.f32(&value)) return failAt("Object position", r.tell(), err);
|
|
for (float& value : object.scale) if (!r.f32(&value)) return failAt("Object scale", r.tell(), err);
|
|
for (float& value : object.rotation) if (!r.f32(&value)) return failAt("Object rotation", r.tell(), err);
|
|
for (float& value : object.color) if (!r.f32(&value)) return failAt("Object color", r.tell(), err);
|
|
for (float& value : object.unknownVector) if (!r.f32(&value)) return failAt("Object vector", r.tell(), err);
|
|
|
|
uint32_t keyCount = 0;
|
|
if (!r.u32(&keyCount)) return failAt("Object visibility count", r.tell(), err);
|
|
object.visibility.reserve(keyCount);
|
|
for (uint32_t key = 0; key < keyCount; ++key) {
|
|
VisibilityPoint p;
|
|
uint8_t fadeOut = 0;
|
|
uint8_t fadeIn = 0;
|
|
uint8_t visible = 0;
|
|
if (!r.u32(&p.timeMs) || !r.u8(&fadeOut) || !r.u8(&fadeIn) || !r.u8(&visible)) {
|
|
return failAt("Object visibility", r.tell(), err);
|
|
}
|
|
p.fadeOut = fadeOut != 0;
|
|
p.fadeIn = fadeIn != 0;
|
|
p.visible = visible != 0;
|
|
object.visibility.push_back(p);
|
|
}
|
|
if (!parseTransformArray(r, &object.movement, "Object movement", err) ||
|
|
!parseTransformArray(r, &object.scaling, "Object scaling", err) ||
|
|
!parseTransformArray(r, &object.rotations, "Object rotation keys", err)) {
|
|
return false;
|
|
}
|
|
|
|
if (!r.u32(&keyCount)) return failAt("Object color count", r.tell(), err);
|
|
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)) {
|
|
return failAt("Object color key", r.tell(), err);
|
|
}
|
|
p.tweenTowards = towards != 0;
|
|
p.tweenAway = away != 0;
|
|
object.colorChanges.push_back(p);
|
|
}
|
|
out->push_back(std::move(object));
|
|
}
|
|
if (r.tell() != next) return failAt("ObjectArray trailing bytes", r.tell(), err);
|
|
return true;
|
|
}
|
|
|
|
bool parseObjectParents(const StageDat& dat, uint32_t extensionBase, uint32_t version,
|
|
std::vector<StageObject>* objects, std::string* err) {
|
|
if (!objects || objects->empty() || version <= 0x29ce) return true;
|
|
if (extensionBase > dat.bytes.size() || dat.bytes.size() - extensionBase < 12) {
|
|
return true;
|
|
}
|
|
|
|
// The extension begins with relative offsets. game471 seeks base+8 for
|
|
// the parent stream, then reads objectCount followed by signed BE int16s.
|
|
PatternReader table(dat.bytes, extensionBase + 8, dat.bytes.size());
|
|
uint32_t relative = 0;
|
|
if (!table.u32(&relative) || relative > dat.bytes.size() - extensionBase) {
|
|
return true;
|
|
}
|
|
const size_t parentStart = static_cast<size_t>(extensionBase) + relative;
|
|
PatternReader parents(dat.bytes, parentStart, dat.bytes.size());
|
|
uint32_t count = 0;
|
|
if (!parents.u32(&count) || count != objects->size()) return true;
|
|
for (size_t i = 0; i < objects->size(); ++i) {
|
|
uint16_t encoded = 0;
|
|
if (!parents.u16(&encoded)) return failAt("ObjectArray parent index", parents.tell(), err);
|
|
const int32_t parent = static_cast<int16_t>(encoded);
|
|
if (parent >= 0 && static_cast<size_t>(parent) >= objects->size()) {
|
|
continue;
|
|
}
|
|
(*objects)[i].parentIndex = parent;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
const char* NoteTypeName(uint8_t rawType) {
|
|
static constexpr const char* names[] = {
|
|
"NONE", "NORMAL", "FLICK", "HOLD", "SCRATCH", "BEAT", "MERRY GO ROUND", "HIDDEN",
|
|
"HIDDEN2", "CRITICAL", "SLIDE HOLD", "SLIDE COUNTER", "TURN", "SPIN", "FINISH", "DUAL HOLD",
|
|
};
|
|
return rawType < (sizeof(names) / sizeof(names[0])) ? names[rawType] : "UNKNOWN";
|
|
}
|
|
|
|
bool ParseStagePattern(const StageDat& dat, ParsedStagePattern* out, std::string* err) {
|
|
if (!out) return false;
|
|
ParsedStagePattern tmp;
|
|
if (!parseHeader(dat, &tmp.header, err)) return false;
|
|
|
|
if (!validOffset(dat, tmp.header.stageCfg) ||
|
|
!validOffset(dat, tmp.header.trackDrawDist) ||
|
|
!validOffset(dat, tmp.header.track) ||
|
|
!validOffset(dat, tmp.header.camera)) {
|
|
if (err) *err = "stage header contains out-of-file offsets";
|
|
return false;
|
|
}
|
|
|
|
if (!parseStageConfig(dat, tmp.header.stageCfg, tmp.header.trackDrawDist, &tmp.config, err)) return false;
|
|
if (!parseDrawDistances(dat, tmp.header.trackDrawDist, tmp.header.track, &tmp.drawDistances, err)) return false;
|
|
if (!parseTrack(dat, tmp.header.track, tmp.header.notes, &tmp.track, err)) return false;
|
|
if (!parseNotes(dat, tmp.header.notes, tmp.header.camera, &tmp.noteNames, &tmp.notes, err)) return false;
|
|
|
|
if (!validOffset(dat, tmp.header.particles) || tmp.header.particles < tmp.header.camera) {
|
|
if (err) *err = "invalid camera/particle section order";
|
|
return false;
|
|
}
|
|
if (!parseCameras(dat, tmp.header.camera, tmp.header.particles, &tmp.cameras, err)) return false;
|
|
if (!validOffset(dat, tmp.header.visualizer) || tmp.header.visualizer < tmp.header.particles ||
|
|
!validOffset(dat, tmp.header.unk1) || tmp.header.unk1 < tmp.header.visualizer ||
|
|
!validOffset(dat, tmp.header.colors) || tmp.header.colors < tmp.header.unk1 ||
|
|
!validOffset(dat, tmp.header.objects) || tmp.header.objects < tmp.header.colors ||
|
|
!validEndOffset(dat, tmp.header.colors2) || tmp.header.colors2 < tmp.header.objects) {
|
|
if (err) *err = "invalid background section order";
|
|
return false;
|
|
}
|
|
if (!parseParticles(dat, tmp.header.particles, tmp.header.visualizer, &tmp.particles, err) ||
|
|
!parseVisualizer(dat, tmp.header.visualizer, tmp.header.unk1, &tmp.visualizer, 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)) {
|
|
return false;
|
|
}
|
|
|
|
*out = std::move(tmp);
|
|
return true;
|
|
}
|
|
|
|
} // namespace gc
|