#include "MagMetadataTable.hh" #include "CommonFileFormats.hh" MagMetadataTable::MotionReferences MagMetadataTable::MotionReferences::from_json(const phosg::JSON& json) { auto parse_side = [](Side& side, const phosg::JSON& side_json) -> void { side.eff_1 = side_json.at("Eff1").as_int(); side.eff_2 = side_json.at("Eff2").as_int(); side.eff_3 = side_json.at("Eff3").as_int(); side.eff_4_8 = side_json.at("Eff48").as_int(); side.eff_5 = side_json.at("Eff5").as_int(); side.eff_6_7 = side_json.at("Eff67").as_int(); }; MagMetadataTable::MotionReferences ret; parse_side(ret.sides[0], json.at("Left")); parse_side(ret.sides[1], json.at("Right")); return ret; } phosg::JSON MagMetadataTable::MotionReferences::json() const { auto serialize_side = [](const Side& side) -> phosg::JSON { return phosg::JSON::dict({ {"Eff1", side.eff_1}, {"Eff2", side.eff_2}, {"Eff3", side.eff_3}, {"Eff48", side.eff_4_8}, {"Eff5", side.eff_5}, {"Eff67", side.eff_6_7}, }); }; return phosg::JSON::dict({{"Left", serialize_side(this->sides[0])}, {"Right", serialize_side(this->sides[1])}}); } MagMetadataTable::UnknownA3Entry MagMetadataTable::UnknownA3Entry::from_json(const phosg::JSON& json) { return UnknownA3Entry{ .flags = static_cast(json.get_int("Flags")), .unknown_a2 = static_cast(json.get_int("UnknownA2")), .unknown_a3 = static_cast(json.get_int("UnknownA3")), .unknown_a4 = static_cast(json.get_int("UnknownA4")), .unknown_a5 = static_cast(json.get_int("UnknownA5")), }; } phosg::JSON MagMetadataTable::UnknownA3Entry::json() const { return phosg::JSON::dict({ {"Flags", this->flags}, {"UnknownA2", this->unknown_a2}, {"UnknownA3", this->unknown_a3}, {"UnknownA4", this->unknown_a4}, {"UnknownA5", this->unknown_a5}, }); } static VectorXYZTF color_for_json(const phosg::JSON& json) { return VectorXYZTF{ .x = json.get_float(0), .y = json.get_float(1), .z = json.get_float(2), .t = json.get_float(3), }; } static phosg::JSON json_for_color(const VectorXYZTF& color) { return phosg::JSON::list({color.x.load(), color.y.load(), color.z.load(), color.t.load()}); } template struct MotionReferenceTables { // It seems that there are two definition tables, but only the first is used on any version of PSO. On v3 and later, // the two offsets point to the same table, but on v2 they don't and the second table contains different data. // TODO: Figure out what the deal is with the different v2 tables. U32T first_ref_table; U32T second_ref_table; } __packed_ws_be__(MotionReferenceTables, 0x08); template struct ColorEntry { // Colors are specified as 4 floats, each in the range [0, 1], for each color channel. The default colors are: // alpha red green blue color (see StaticGameData.cc) // 00 => 1.0 1.0 0.2 0.1 red // 01 => 1.0 0.2 0.2 1.0 blue // 02 => 1.0 1.0 0.9 0.1 yellow // 03 => 1.0 0.1 1.0 0.1 green // 04 => 1.0 0.8 0.1 1.0 purple // 05 => 1.0 0.1 0.1 0.2 black // 06 => 1.0 0.9 1.0 1.0 white // 07 => 1.0 0.1 0.9 1.0 cyan // 08 => 1.0 0.5 0.3 0.2 brown // 09 => 1.0 1.0 0.4 0.0 orange (v3+) // 0A => 1.0 0.502 0.545 0.977 light-blue (v3+) // 0B => 1.0 0.502 0.502 0.0 olive (v3+) // 0C => 1.0 0.0 0.941 0.714 turquoise (v3+) // 0D => 1.0 0.8 0.098 0.392 fuchsia (v3+) // 0E => 1.0 0.498 0.498 0.498 grey (v3+) // 0F => 1.0 0.996 0.996 0.832 cream (v3+) // 10 => 1.0 0.996 0.498 0.784 pink (v3+) // 11 => 1.0 0.0 0.498 0.322 dark-green (v3+) // If a mag's color index is invalid (>= 0x12), it is reassigned at equip time using the following logic: // - Set base_index to player->visual.sh.skin if player is an android, or player->visual.costume otherwise // - If (base_index % 9) < 7 (that is, if their costume or body color is one of the colored slots on the character // creation screen), then set the mag color to either (base_index % 9) or (base_index % 9) + 9, with equal // probability. // - If (base_index % 9) >= 7 (that is, if their costume or body color is one of the last two blank-colored slots // on the character creation screen), then set the mag color to any of the available colors, chosen at random. F32T alpha; F32T red; F32T green; F32T blue; ColorEntry(const VectorXYZTF& c) : alpha(c.t), red(c.x), green(c.y), blue(c.z) {} operator VectorXYZTF() const { return VectorXYZTF{this->red.load(), this->green.load(), this->blue.load(), this->alpha.load()}; } } __packed_ws_be__(ColorEntry, 0x10); template struct UnknownA3EntryT { uint8_t flags; uint8_t unknown_a2; S16T unknown_a3; S16T unknown_a4; S16T unknown_a5; UnknownA3EntryT(const MagMetadataTable::UnknownA3Entry& e) : flags(e.flags), unknown_a2(e.unknown_a2), unknown_a3(e.unknown_a3), unknown_a4(e.unknown_a4), unknown_a5(e.unknown_a5) {} operator MagMetadataTable::UnknownA3Entry() const { return MagMetadataTable::UnknownA3Entry{ this->flags, this->unknown_a2, this->unknown_a3, this->unknown_a4, this->unknown_a5}; } } __packed_ws_be__(UnknownA3EntryT, 0x08); class JSONMagMetadataTable : public MagMetadataTable { public: explicit JSONMagMetadataTable(const phosg::JSON& json) { for (const auto& mag_json : json.at("Mags").as_list()) { const auto& unknown_a2_json = mag_json->at("UnknownA2").as_list(); auto& mag = this->mags.emplace_back(); mag.first_motion_refs = MotionReferences::from_json(mag_json->at("MotionRefs1")); mag.second_motion_refs = MotionReferences::from_json(mag_json->at("MotionRefs2")); mag.unknown_a2 = std::make_pair(unknown_a2_json.at(0)->as_int(), unknown_a2_json.at(1)->as_int()); mag.render_flags = mag_json->at("RenderFlags").as_int(); mag.evolution_number = mag_json->at("EvolutionNumber").as_int(); } for (const auto& a3_json : json.at("UnknownA3").as_list()) { this->unknown_a3.emplace_back(UnknownA3Entry::from_json(*a3_json)); } for (const auto& color_json : json.at("Colors").as_list()) { this->colors.emplace_back(color_for_json(*color_json)); } } virtual ~JSONMagMetadataTable() = default; virtual size_t num_mags() const { return this->mags.size(); } virtual size_t num_motion_entries(bool) const { return this->mags.size(); } virtual const MotionReferences& get_motion_references(bool use_second_table, size_t data1_1) const { const auto& mag = this->mags.at(data1_1); return use_second_table ? mag.second_motion_refs : mag.first_motion_refs; } virtual std::pair get_unknown_a2(size_t data1_1) const { return this->mags.at(data1_1).unknown_a2; } virtual size_t num_unknown_a3_entries() const { return this->unknown_a3.size(); } virtual const UnknownA3Entry& get_unknown_a3(size_t index) const { return this->unknown_a3.at(index); } virtual uint8_t get_render_flags(size_t data1_1) const { return this->mags.at(data1_1).render_flags; } virtual size_t num_colors() const { return this->colors.size(); } virtual const VectorXYZTF& get_color_rgba(size_t index) const { return this->colors.at(index); } virtual uint8_t get_evolution_number(uint8_t data1_1) const { return this->mags.at(data1_1).evolution_number; } protected: struct Mag { MotionReferences first_motion_refs; MotionReferences second_motion_refs; std::pair unknown_a2 = std::make_pair(0, 0); uint8_t render_flags = 0; uint8_t evolution_number = 0; }; std::vector mags; std::vector unknown_a3; std::vector colors; }; struct HeaderV1 { parray unknown_a1 = {0x0F, 0xF0, 0x00, 0x00}; le_uint32_t unknown_a2 = 0x00000003; le_uint16_t unknown_a3 = 0x00C8; le_uint16_t unknown_a4 = 0x0078; // unknown_a5 added in V2 le_float unknown_a6 = 0.25; // 3E800000 le_float unknown_a7 = 0.099999994; // 3DCCCCCC le_uint32_t unknown_a8 = 0x00000C00; } __packed_ws__(HeaderV1, 0x18); template struct HeaderV2V3V4 { parray unknown_a1 = {0x0F, 0xF0, 0x00, 0x00}; U32T unknown_a2 = 0x00000003; U16T unknown_a3 = 0x00C8; U16T unknown_a4 = 0x0078; parray unknown_a5 = {0xC8, 0x00, 0x00, 0x00}; F32T unknown_a6 = 0.25; // 3E800000 F32T unknown_a7 = 0.099999994; // 3DCCCCCC U32T unknown_a8 = 0x00000C00; } __packed_ws_be__(HeaderV2V3V4, 0x1C); // Fields: // 112K / V1 / V2 / V3 / BB R // 0018 / 0018 / 001C / 001C / 001C motion_tables.first_ref_table // -> MotionReferences[NumMags] // 0228 / 0228 / 02D4 / 001C / 001C motion_tables.second_ref_table // -> MotionReferences[NumMags] // 0438 / 0438 / 05BC / 0340 / 0400 * motion_tables; // -> MotionReferenceTables // 0440 / 0440 / 0594 / 0348 / 0408 * unknown_a2; // -> (uint8_t[2])[NumMags] (references into unknown_a3) // 0498 / 0498 / 0608 / 03CE / 04AE * unknown_a3; // -> UnknownA3Entry[max(unknown_a2) + 1] // 0510 / 0520 / 06B0 / 0476 / 0556 * render_flags; // -> uint8_t[NumMags] // 053C / 054C / 06EC / 04BC / 05AC * color_table; // -> ColorEntry[NumColors] // ---- / ---- / 077C / 05DC / 06CC * evolution_number_table; // -> uint8_t[NumMags] template struct RootV1 { U32T motion_tables; U32T unknown_a2; U32T unknown_a3; U32T render_flags; U32T color_table; } __packed_ws_be__(RootV1, 0x14); template struct RootV2V3V4 : RootV1 { U32T evolution_number_table; } __packed_ws_be__(RootV2V3V4, 0x18); static uint8_t get_v1_mag_evolution_number(uint8_t data1_1) { static const std::array v1_evolution_number_table{ /* 00 */ 0, 1, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 2, /* 10 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 2, 3, 4, 3, 3, /* 20 */ 3, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 4}; if (data1_1 >= v1_evolution_number_table.size()) { throw std::runtime_error("invalid mag number"); } return v1_evolution_number_table[data1_1]; } template class BinaryMagMetadataTableT : public MagMetadataTable { public: explicit BinaryMagMetadataTableT(std::shared_ptr data) : data(data), r(*data), root(&r.pget(this->r.pget>(this->data->size() - 0x10))) {} virtual ~BinaryMagMetadataTableT() = default; template const ParsedT& add_to_vector_cache(std::vector& cache, size_t base_offset, size_t index) const { while (cache.size() <= index) { cache.emplace_back(this->r.pget(base_offset + sizeof(RawT) * cache.size())); } return cache[index]; } virtual size_t num_mags() const { return NumMags; } virtual size_t num_motion_entries(bool use_second_table) const { const auto& tables = this->r.pget>(this->root->motion_tables); return get_rel_array_count( this->all_start_offsets(), use_second_table ? tables.second_ref_table : tables.first_ref_table); } virtual const MotionReferences& get_motion_references(bool use_second_table, size_t index) const { if (index >= this->num_motion_entries(use_second_table)) { throw std::logic_error("Invalid motion reference index"); } const auto& tables = this->r.pget>(this->root->motion_tables); uint32_t array_offset = use_second_table ? tables.second_ref_table : tables.first_ref_table; return this->r.pget(array_offset + sizeof(MotionReferences) * index); } virtual std::pair get_unknown_a2(size_t index) const { if (index >= this->num_mags()) { throw std::logic_error("Invalid unknown_a2 index"); } uint32_t base_offset = this->root->unknown_a2 + (index * 2); return std::make_pair(this->r.pget_u8(base_offset), this->r.pget_u8(base_offset + 1)); } virtual size_t num_unknown_a3_entries() const { return get_rel_array_count>(this->all_start_offsets(), this->root->unknown_a3); } virtual const UnknownA3Entry& get_unknown_a3(size_t index) const { if (index >= this->num_unknown_a3_entries()) { throw std::logic_error("Invalid unknown_a2 index"); } return this->add_to_vector_cache>(this->unknown_a3_entries, this->root->unknown_a3, index); } virtual uint8_t get_render_flags(size_t index) const { if (index >= this->num_mags()) { throw std::logic_error("Invalid render_flags index"); } return this->r.pget_u8(this->root->render_flags + index); } virtual size_t num_colors() const { return NumColors; } virtual const VectorXYZTF& get_color_rgba(size_t index) const { if (index >= NumColors) { throw std::runtime_error("invalid mag color index"); } return this->add_to_vector_cache>(this->colors, this->root->color_table, index); } virtual uint8_t get_evolution_number(uint8_t data1_1) const { if (data1_1 >= this->num_mags()) { throw std::logic_error("Invalid evolution_number index"); } if constexpr (requires { this->root->evolution_number_table; }) { return this->r.pget_u8(this->root->evolution_number_table + data1_1); } else { return get_v1_mag_evolution_number(data1_1); } } const std::set& all_start_offsets() const { if (this->start_offsets.empty()) { this->start_offsets = all_relocation_offsets_for_rel_file(r.pgetv(0, r.size()), r.size()); } return this->start_offsets; } static std::string serialize(const MagMetadataTable& table) { RELFileWriter rel; RootT root; rel.template put(HeaderT()); MotionReferenceTables motion_ref_tables; motion_ref_tables.first_ref_table = rel.w.size(); bool alias_motion_ref_tables = true; for (size_t z = 0; z < table.num_motion_entries(false); z++) { const auto& refs = table.get_motion_references(false, z); rel.template put(refs); if (refs != table.get_motion_references(true, z)) { alias_motion_ref_tables = false; } } if (alias_motion_ref_tables) { motion_ref_tables.second_ref_table = motion_ref_tables.first_ref_table; } else { motion_ref_tables.second_ref_table = rel.w.size(); for (size_t z = 0; z < table.num_motion_entries(true); z++) { rel.template put(table.get_motion_references(true, z)); } } root.motion_tables = rel.w.size(); rel.template put>(motion_ref_tables); rel.relocations.emplace(root.motion_tables); rel.relocations.emplace(root.motion_tables + 4); root.unknown_a2 = rel.w.size(); for (size_t z = 0; z < table.num_mags(); z++) { auto [left, right] = table.get_unknown_a2(z); rel.template put(left); rel.template put(right); } root.unknown_a3 = rel.w.size(); for (size_t z = 0; z < table.num_unknown_a3_entries(); z++) { rel.template put>(table.get_unknown_a3(z)); } root.render_flags = rel.w.size(); for (size_t z = 0; z < table.num_mags(); z++) { rel.template put(table.get_render_flags(z)); } rel.align(4); root.color_table = rel.w.size(); for (size_t z = 0; z < table.num_colors(); z++) { rel.template put>(table.get_color_rgba(z)); } if constexpr (requires { root.evolution_number_table; }) { root.evolution_number_table = rel.w.size(); for (size_t z = 0; z < table.num_mags(); z++) { rel.template put(table.get_evolution_number(z)); } } rel.align(4); uint32_t root_offset = rel.template put(root); for (size_t z = 1; z <= sizeof(RootT) / 4; z++) { rel.relocations.emplace(rel.w.size() - (z * 4)); } return rel.finalize(root_offset); } protected: std::shared_ptr data; phosg::StringReader r; const RootT* root; mutable std::set start_offsets; mutable std::vector unknown_a3_entries; mutable std::vector colors; }; class MagMetadataTableDCNTE : public MagMetadataTable { public: MagMetadataTableDCNTE() = default; virtual ~MagMetadataTableDCNTE() = default; virtual size_t num_mags() const { return 0x2C; } virtual size_t num_motion_entries(bool) const { return 0; } virtual const MotionReferences& get_motion_references(bool, size_t) const { throw std::runtime_error("Mag tables not available on DC NTE"); } virtual std::pair get_unknown_a2(size_t) const { throw std::runtime_error("Mag tables not available on DC NTE"); } virtual size_t num_unknown_a3_entries() const { return 0; } virtual const UnknownA3Entry& get_unknown_a3(size_t) const { throw std::runtime_error("Mag tables not available on DC NTE"); } virtual uint8_t get_render_flags(size_t) const { throw std::runtime_error("Mag tables not available on DC NTE"); } virtual size_t num_colors() const { return 0; } virtual const VectorXYZTF& get_color_rgba(size_t) const { throw std::runtime_error("Mag tables not available on DC NTE"); } virtual uint8_t get_evolution_number(uint8_t data1_1) const { return get_v1_mag_evolution_number(data1_1); } }; using MagMetadataTableDC112000 = BinaryMagMetadataTableT, 0x2C, 0x09, false>; using MagMetadataTableV1 = BinaryMagMetadataTableT, 0x2C, 0x09, false>; using MagMetadataTableV2 = BinaryMagMetadataTableT, RootV2V3V4, 0x3A, 0x09, false>; using MagMetadataTableGCNTE = BinaryMagMetadataTableT, RootV2V3V4, 0x3A, 0x09, true>; using MagMetadataTableGC = BinaryMagMetadataTableT, RootV2V3V4, 0x43, 0x12, true>; using MagMetadataTableXB = BinaryMagMetadataTableT, RootV2V3V4, 0x43, 0x12, false>; using MagMetadataTableV4 = BinaryMagMetadataTableT, RootV2V3V4, 0x53, 0x12, false>; std::shared_ptr MagMetadataTable::from_binary( std::shared_ptr data, Version version) { switch (version) { case Version::DC_NTE: return std::make_shared(); case Version::DC_11_2000: return std::make_shared(data); case Version::DC_V1: return std::make_shared(data); case Version::DC_V2: case Version::PC_NTE: case Version::PC_V2: return std::make_shared(data); case Version::GC_NTE: return std::make_shared(data); case Version::GC_V3: case Version::GC_EP3: case Version::GC_EP3_NTE: return std::make_shared(data); case Version::XB_V3: return std::make_shared(data); case Version::BB_V4: return std::make_shared(data); default: throw std::logic_error("Cannot create mag metadata table for this version"); } } std::shared_ptr MagMetadataTable::from_json(const phosg::JSON& json) { return std::make_shared(json); } phosg::JSON MagMetadataTable::json() const { if (this->num_motion_entries(true) != this->num_motion_entries(false)) { throw std::runtime_error("Motion entry counts differ across tables"); } if (this->num_motion_entries(false) != this->num_mags()) { throw std::runtime_error(std::format("Motion entry count {} does not match mag count {}", this->num_motion_entries(false), this->num_mags())); } auto mags_json = phosg::JSON::list(); for (size_t z = 0; z < this->num_mags(); z++) { auto mag_json = phosg::JSON::dict(); mag_json.emplace("MotionRefs1", this->get_motion_references(false, z).json()); mag_json.emplace("MotionRefs2", this->get_motion_references(true, z).json()); auto unknown_a2 = this->get_unknown_a2(z); mag_json.emplace("UnknownA2", phosg::JSON::list({unknown_a2.first, unknown_a2.second})); mag_json.emplace("RenderFlags", this->get_render_flags(z)); mag_json.emplace("EvolutionNumber", this->get_evolution_number(z)); mags_json.emplace_back(std::move(mag_json)); } auto unknown_a3_json = phosg::JSON::list(); for (size_t z = 0; z < this->num_unknown_a3_entries(); z++) { unknown_a3_json.emplace_back(this->get_unknown_a3(z).json()); } auto colors_json = phosg::JSON::list(); for (size_t z = 0; z < this->num_colors(); z++) { colors_json.emplace_back(json_for_color(this->get_color_rgba(z))); } return phosg::JSON::dict({ {"Mags", std::move(mags_json)}, {"UnknownA3", std::move(unknown_a3_json)}, {"Colors", std::move(colors_json)}, }); } std::string MagMetadataTable::serialize_binary(Version version) const { switch (version) { case Version::DC_NTE: throw std::runtime_error("DC NTE does not have a an ItemMagEdit format"); case Version::DC_11_2000: return MagMetadataTableDC112000::serialize(*this); case Version::DC_V1: return MagMetadataTableV1::serialize(*this); case Version::DC_V2: case Version::PC_NTE: case Version::PC_V2: return MagMetadataTableV2::serialize(*this); case Version::GC_NTE: return MagMetadataTableGCNTE::serialize(*this); case Version::GC_V3: case Version::GC_EP3: case Version::GC_EP3_NTE: return MagMetadataTableGC::serialize(*this); case Version::XB_V3: return MagMetadataTableXB::serialize(*this); case Version::BB_V4: return MagMetadataTableV4::serialize(*this); default: throw std::logic_error("Cannot create item parameter table for this version"); } }