#include "ShopRandomSets.hh" #include "CommonFileFormats.hh" #include "StaticGameData.hh" #include "Types.hh" template struct TableSpecT { U32T offset; uint8_t row_size; parray unused; } __packed_ws_be__(TableSpecT, 8); template void print_table_2d(FILE* stream, const std::vector>& table) { for (size_t z = 0; z < table.size(); z++) { phosg::fwrite_fmt(stream, " {:02X}:", z); for (const auto& cell : table[z]) { if constexpr (std::is_integral_v) { phosg::fwrite_fmt(stream, " {:02X}", cell); } else { // It should be ShopRandomSetBase::IntPairT<...> phosg::fwrite_fmt(stream, " {:02X} @ {:03}", cell.first, cell.second); } } phosg::fwrite_fmt(stream, "\n"); } } template std::vector> parse_table_t( const phosg::StringReader& r, uint32_t offset, size_t row_size, const std::set start_offsets) { auto end_offset_it = start_offsets.upper_bound(offset); if (end_offset_it == start_offsets.end()) { throw std::runtime_error("Cannot determine table end offset"); } uint32_t end_offset = *end_offset_it; size_t row_bytes = row_size * sizeof(StoredT); size_t row_count = (end_offset - offset) / row_bytes; auto sub_r = r.sub(offset, row_bytes * row_count); std::vector> ret; while (ret.size() < row_count) { auto& row = ret.emplace_back(); while (row.size() < row_size) { row.emplace_back(sub_r.get()); } } return ret; } template TableSpecT serialize_table_t(RELFileWriter& rel, const std::vector>& table) { if (table.empty()) { throw std::runtime_error("Table is empty"); } TableSpecT ret; ret.offset = rel.w.size(); ret.row_size = table[0].size(); for (const auto& row : table) { if (row.size() != ret.row_size) { throw std::runtime_error("Table has different row sizes"); } for (const auto& cell : row) { rel.template put(cell); } } return ret; } template std::array, RowCount> parse_fixed_table_t(const phosg::StringReader& r, uint32_t offset) { auto sub_r = r.sub(offset, RowSize * RowCount * sizeof(StoredT)); std::array, RowCount> ret; for (size_t y = 0; y < RowCount; y++) { for (size_t x = 0; x < RowSize; x++) { ret[y][x] = sub_r.get(); } } return ret; } template uint32_t serialize_fixed_table_t( RELFileWriter& rel, const std::array, RowCount>& table) { uint32_t ret = rel.w.size(); for (const auto& row : table) { for (const auto& cell : row) { rel.template put(cell); } } return ret; } template std::vector> table_for_json_t(const phosg::JSON& table_json) { std::vector> ret; for (const auto& row_json : table_json.as_list()) { auto& row = ret.emplace_back(); for (const auto& cell_json : row_json->as_list()) { if constexpr (std::is_integral_v) { row.emplace_back(cell_json->as_int()); } else { row.emplace_back(*cell_json); } } } return ret; } template phosg::JSON json_for_table_t(const std::vector>& table) { auto table_json = phosg::JSON::list(); for (const auto& row : table) { auto row_json = phosg::JSON::list(); for (const auto& cell : row) { if constexpr (std::is_integral_v) { row_json.emplace_back(cell); } else { row_json.emplace_back(cell.json()); } } table_json.emplace_back(std::move(row_json)); } return table_json; } template std::array fixed_table_for_json_t(const phosg::JSON& table_json) { std::array ret; for (size_t y = 0; y < Count; y++) { ret[y] = table_json.at(y); } return ret; } template std::array, RowCount> fixed_table_for_json_t(const phosg::JSON& table_json) { std::array, RowCount> ret; for (size_t y = 0; y < RowCount; y++) { const auto& row_json = table_json.at(y); for (size_t x = 0; x < RowSize; x++) { ret[y][x] = row_json.at(x); } } return ret; } template phosg::JSON json_for_fixed_table_t(const std::array& table) { auto ret = phosg::JSON::list(); for (const auto& cell : table) { ret.emplace_back(cell.json()); } return ret; } template phosg::JSON json_for_fixed_table_t(const std::array, RowCount>& table) { auto table_json = phosg::JSON::list(); for (const auto& row : table) { auto row_json = phosg::JSON::list(); for (const auto& cell : row) { row_json.emplace_back(cell.json()); } table_json.emplace_back(std::move(row_json)); } return table_json; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Armor shop set template struct ArmorSubRootT { TableSpecT armor_table; // -> WeightTableEntry8[...][...] TableSpecT shield_table; // -> WeightTableEntry8[...][...] TableSpecT unit_table; // -> WeightTableEntry8[...][...] } __packed_ws_be__(ArmorSubRootT, 0x18); template struct ArmorRootT { U32T subroot; // -> ArmorSubRootT } __packed_ws_be__(ArmorRootT, 4); ArmorShopRandomSet::ArmorShopRandomSet(const void* data, size_t size, bool big_endian) { if (big_endian) { this->parse_t(data, size); } else { this->parse_t(data, size); } } ArmorShopRandomSet::ArmorShopRandomSet(const std::string& data, bool big_endian) : ArmorShopRandomSet(data.data(), data.size(), big_endian) {} ArmorShopRandomSet::ArmorShopRandomSet(const phosg::JSON& json) { this->armor_table = table_for_json_t>(json.at("ArmorTable")); this->shield_table = table_for_json_t>(json.at("ShieldTable")); this->unit_table = table_for_json_t>(json.at("UnitTable")); } template void ArmorShopRandomSet::parse_t(const void* data, size_t size) { std::set start_offsets; phosg::StringReader r(data, size); uint32_t root_offset = r.pget>(size - 0x10); start_offsets.emplace(root_offset); const auto& root = r.pget>(root_offset); start_offsets.emplace(root.subroot); const auto& subroot = r.pget>(root.subroot); start_offsets.emplace(subroot.armor_table.offset); start_offsets.emplace(subroot.shield_table.offset); start_offsets.emplace(subroot.unit_table.offset); this->armor_table = parse_table_t>( r, subroot.armor_table.offset, subroot.armor_table.row_size, start_offsets); this->shield_table = parse_table_t>( r, subroot.shield_table.offset, subroot.shield_table.row_size, start_offsets); this->unit_table = parse_table_t>( r, subroot.unit_table.offset, subroot.unit_table.row_size, start_offsets); } template std::string ArmorShopRandomSet::serialize_binary_t() const { RELFileWriter rel; ArmorSubRootT subroot; subroot.armor_table = serialize_table_t>(rel, this->armor_table); subroot.shield_table = serialize_table_t>(rel, this->shield_table); subroot.unit_table = serialize_table_t>(rel, this->unit_table); ArmorRootT root; rel.align(4); root.subroot = rel.put(subroot); rel.relocations.emplace(rel.w.size() - 0x18); rel.relocations.emplace(rel.w.size() - 0x10); rel.relocations.emplace(rel.w.size() - 0x08); uint32_t root_offset = rel.put(root); rel.relocations.emplace(rel.w.size() - 4); return rel.finalize(root_offset); } std::string ArmorShopRandomSet::serialize_binary(bool big_endian) const { return big_endian ? this->serialize_binary_t() : this->serialize_binary_t(); } phosg::JSON ArmorShopRandomSet::json() const { return phosg::JSON::dict({ {"ArmorTable", json_for_table_t(this->armor_table)}, {"ShieldTable", json_for_table_t(this->shield_table)}, {"UnitTable", json_for_table_t(this->unit_table)}, }); } void ArmorShopRandomSet::print(FILE* stream) const { phosg::fwrite_fmt(stream, "ArmorShopRandomSet\n"); phosg::fwrite_fmt(stream, " Armor table\n"); print_table_2d(stream, this->armor_table); phosg::fwrite_fmt(stream, " Shield table\n"); print_table_2d(stream, this->shield_table); phosg::fwrite_fmt(stream, " Unit table\n"); print_table_2d(stream, this->unit_table); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Tool shop set ToolShopRandomSet::TechDiskLevelEntry::TechDiskLevelEntry(const phosg::JSON& json) { if (json.contains("PlayerLevelDivisor")) { this->mode = Mode::PLAYER_LEVEL_DIVISOR; this->player_level_divisor_or_min_level = json.get_int("PlayerLevelDivisor"); this->max_level = 0; } else if (json.contains("MinLevel")) { this->mode = Mode::RANDOM_IN_RANGE; this->player_level_divisor_or_min_level = json.get_int("MinLevel"); this->max_level = json.get_int("MaxLevel"); } else { this->mode = Mode::LEVEL_1; this->player_level_divisor_or_min_level = 0; this->max_level = 0; } } phosg::JSON ToolShopRandomSet::TechDiskLevelEntry::json() const { switch (this->mode) { case Mode::LEVEL_1: return phosg::JSON::dict(); case Mode::PLAYER_LEVEL_DIVISOR: return phosg::JSON::dict({{"PlayerLevelDivisor", this->player_level_divisor_or_min_level}}); case Mode::RANDOM_IN_RANGE: return phosg::JSON::dict({{"MinLevel", this->player_level_divisor_or_min_level}, {"MaxLevel", this->max_level}}); default: throw std::runtime_error("Invalid TechDiskLevelEntry mode"); } } template struct ToolSubRootT { TableSpecT rare_recovery_table; // -> WeightTableEntry8[...][...] TableSpecT tech_disk_table; // -> WeightTableEntry8[...][...] } __packed_ws_be__(ToolSubRootT, 0x10); template struct ToolRootT { U32T common_recovery_table; // -> TableSpecT -> WeightTableEntry8[...][...] U32T subroot; // -> ToolSubRootT U32T tech_disk_level_table; // -> TableSpecT -> TechDiskLevelEntry[...][...] } __packed_ws_be__(ToolRootT, 0x0C); ToolShopRandomSet::ToolShopRandomSet(const void* data, size_t size, bool big_endian) { if (big_endian) { this->parse_t(data, size); } else { this->parse_t(data, size); } } const std::vector> ToolShopRandomSet::item_defs{ {0x00, 0x00}, // 00 -> Monomate {0x00, 0x01}, // 01 -> Dimate {0x00, 0x02}, // 02 -> Trimate {0x01, 0x00}, // 03 -> Monofluid {0x01, 0x01}, // 04 -> Difluid {0x01, 0x02}, // 05 -> Trifluid {0x06, 0x00}, // 06 -> Antidote {0x06, 0x01}, // 07 -> Antiparalysis {0x03, 0x00}, // 08 -> Sol Atomizer {0x04, 0x00}, // 09 -> Moon Atomizer {0x05, 0x00}, // 0A -> Star Atomizer {0x07, 0x00}, // 0B -> Telepipe {0x08, 0x00}, // 0C -> Trap Vision {0x09, 0x00}, // 0D -> Scape Doll {0x0A, 0x00}, // 0E -> Monogrinder {0xFF, 0xFF}, // 0F -> Nothing }; const std::array ToolShopRandomSet::tech_num_map{ 0x00, 0x03, 0x06, 0x0F, 0x10, 0x0D, 0x0A, 0x0B, 0x0C, 0x01, 0x04, 0x07, 0x0E, 0x11, 0x02, 0x05, 0x08, 0x09, 0x12}; ToolShopRandomSet::ToolShopRandomSet(const std::string& data, bool big_endian) : ToolShopRandomSet(data.data(), data.size(), big_endian) {} ToolShopRandomSet::ToolShopRandomSet(const phosg::JSON& json) { this->common_recovery_table = table_for_json_t(json.at("CommonRecoveryTable")); this->rare_recovery_table = table_for_json_t>(json.at("RareRecoveryTable")); this->tech_disk_table = table_for_json_t>(json.at("TechDiskTable")); this->tech_disk_level_table = table_for_json_t(json.at("TechDiskLevelTable")); } template void ToolShopRandomSet::parse_t(const void* data, size_t size) { std::set start_offsets; phosg::StringReader r(data, size); uint32_t root_offset = r.pget>(size - 0x10); start_offsets.emplace(root_offset); const auto& root = r.pget>(root_offset); start_offsets.emplace(root.common_recovery_table); start_offsets.emplace(root.subroot); start_offsets.emplace(root.tech_disk_level_table); const auto& common_recovery_table_spec = r.pget>(root.common_recovery_table); start_offsets.emplace(common_recovery_table_spec.offset); const auto& subroot = r.pget>(root.subroot); start_offsets.emplace(subroot.rare_recovery_table.offset); start_offsets.emplace(subroot.tech_disk_table.offset); const auto& tech_disk_level_table_spec = r.pget>(root.tech_disk_level_table); start_offsets.emplace(tech_disk_level_table_spec.offset); this->common_recovery_table = parse_table_t( r, common_recovery_table_spec.offset, common_recovery_table_spec.row_size, start_offsets); this->rare_recovery_table = parse_table_t>( r, subroot.rare_recovery_table.offset, subroot.rare_recovery_table.row_size, start_offsets); this->tech_disk_table = parse_table_t>( r, subroot.tech_disk_table.offset, subroot.tech_disk_table.row_size, start_offsets); this->tech_disk_level_table = parse_table_t( r, tech_disk_level_table_spec.offset, tech_disk_level_table_spec.row_size, start_offsets); } template std::string ToolShopRandomSet::serialize_binary_t() const { RELFileWriter rel; ToolSubRootT subroot; auto common_recovery_table_spec = serialize_table_t(rel, this->common_recovery_table); subroot.rare_recovery_table = serialize_table_t>(rel, this->rare_recovery_table); subroot.tech_disk_table = serialize_table_t>(rel, this->tech_disk_table); auto tech_disk_level_table_spec = serialize_table_t(rel, this->tech_disk_level_table); rel.align(4); ToolRootT root; root.subroot = rel.put(subroot); rel.relocations.emplace(rel.w.size() - 0x10); rel.relocations.emplace(rel.w.size() - 0x08); root.common_recovery_table = rel.put(common_recovery_table_spec); rel.relocations.emplace(rel.w.size() - 0x08); root.tech_disk_level_table = rel.put(tech_disk_level_table_spec); rel.relocations.emplace(rel.w.size() - 0x08); uint32_t root_offset = rel.put(root); rel.relocations.emplace(rel.w.size() - 0x0C); rel.relocations.emplace(rel.w.size() - 0x08); rel.relocations.emplace(rel.w.size() - 0x04); return rel.finalize(root_offset); } std::string ToolShopRandomSet::serialize_binary(bool big_endian) const { return big_endian ? this->serialize_binary_t() : this->serialize_binary_t(); } phosg::JSON ToolShopRandomSet::json() const { return phosg::JSON::dict({ {"CommonRecoveryTable", json_for_table_t(this->common_recovery_table)}, {"RareRecoveryTable", json_for_table_t(this->rare_recovery_table)}, {"TechDiskTable", json_for_table_t(this->tech_disk_table)}, {"TechDiskLevelTable", json_for_table_t(this->tech_disk_level_table)}, }); } void ToolShopRandomSet::print(FILE* stream) const { phosg::fwrite_fmt(stream, "ToolShopRandomSet\n"); phosg::fwrite_fmt(stream, " Common recovery table\n"); for (size_t z = 0; z < this->common_recovery_table.size(); z++) { phosg::fwrite_fmt(stream, " {:02X}:", z); for (uint8_t cell : this->common_recovery_table[z]) { if (cell == 0x0F) { phosg::fwrite_fmt(stream, " {:02X} (------)", cell); } else { const auto& def = this->item_defs.at(cell); phosg::fwrite_fmt(stream, " {:02X} (03{:02X}{:02X})", cell, def.first, def.second); } } phosg::fwrite_fmt(stream, "\n"); } phosg::fwrite_fmt(stream, " Rare recovery table\n"); for (size_t z = 0; z < this->rare_recovery_table.size(); z++) { phosg::fwrite_fmt(stream, " {:02X}:", z); for (const auto& cell : this->rare_recovery_table[z]) { if (cell.value == 0x0F) { phosg::fwrite_fmt(stream, " {:02X} (------) @ {:03}", cell.value, cell.weight); } else { const auto& def = this->item_defs.at(cell.value); phosg::fwrite_fmt(stream, " {:02X} (03{:02X}{:02X}) @ {:03}", cell.value, def.first, def.second, cell.weight); } } phosg::fwrite_fmt(stream, "\n"); } phosg::fwrite_fmt(stream, " Tech disk table\n"); for (size_t z = 0; z < this->tech_disk_table.size(); z++) { phosg::fwrite_fmt(stream, " {:02X}:", z); for (const auto& cell : this->tech_disk_table[z]) { phosg::fwrite_fmt(stream, " {:02X}({:>8}) @ {:03}", cell.first, name_for_technique(this->tech_num_map.at(cell.value)), cell.second); } phosg::fwrite_fmt(stream, "\n"); } phosg::fwrite_fmt(stream, " Tech disk level table\n"); phosg::fwrite_fmt(stream, " "); for (const auto& tech_num : this->tech_num_map) { phosg::fwrite_fmt(stream, " {:<9}", name_for_technique(tech_num)); } phosg::fwrite_fmt(stream, "\n"); for (size_t z = 0; z < this->tech_disk_level_table.size(); z++) { phosg::fwrite_fmt(stream, " {:02X}:", z); for (const auto& cell : this->tech_disk_level_table[z]) { switch (cell.mode) { case TechDiskLevelEntry::Mode::LEVEL_1: phosg::fwrite_fmt(stream, " LEVEL_1 "); break; case TechDiskLevelEntry::Mode::PLAYER_LEVEL_DIVISOR: phosg::fwrite_fmt(stream, " PLV / {:03}", cell.player_level_divisor_or_min_level); break; case TechDiskLevelEntry::Mode::RANDOM_IN_RANGE: phosg::fwrite_fmt(stream, " {:03} - {:03}", cell.player_level_divisor_or_min_level, cell.max_level); break; } } phosg::fwrite_fmt(stream, "\n"); } std::vector> tech_disk_level_table; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Weapon shop set template struct RangeTableEntryT { U32T min; U32T max; } __packed_ws_be__(RangeTableEntryT, 8); template struct WeaponRootT { U32T weapon_type_table; // {c, o -> (table)}[...(offsets)] U32T bonus_type_table1; // {u32 value, u32 weight}[9][6] U32T bonus_type_table2; // {u32 value, u32 weight}[9][6] U32T bonus_range_table1; // {u32 min_index, u32 max_index}[9] U32T bonus_range_table2; // {u32 min_index, u32 max_index}[9] U32T special_mode_table; // {u32 value, u32 weight}[8][3] U32T default_grind_range_table; // {u32 min, u32 max}[6] U32T favored_grind_range_table; // {u32 min, u32 max}[6] } __packed_ws_be__(WeaponRootT, 0x20); const std::array, 0x48> WeaponShopRandomSet::type_defs({ /* 00 */ {0x01, 0x00}, // Saber /* 01 */ {0x01, 0x01}, // Brand /* 02 */ {0x01, 0x02}, // Buster /* 03 */ {0x01, 0x03}, // Pallasch /* 04 */ {0x01, 0x04}, // Gladius /* 05 */ {0x03, 0x00}, // Dagger /* 06 */ {0x03, 0x01}, // Knife /* 07 */ {0x03, 0x02}, // Blade /* 08 */ {0x03, 0x03}, // Edge /* 09 */ {0x03, 0x04}, // Ripper /* 0A */ {0x02, 0x00}, // Sword /* 0B */ {0x02, 0x01}, // Gigush /* 0C */ {0x02, 0x02}, // Breaker /* 0D */ {0x02, 0x03}, // Claymore /* 0E */ {0x02, 0x04}, // Calibur /* 0F */ {0x05, 0x00}, // Slicer /* 10 */ {0x05, 0x01}, // Spinner /* 11 */ {0x05, 0x02}, // Cutter /* 12 */ {0x05, 0x03}, // Sawcer /* 13 */ {0x05, 0x04}, // Diska /* 14 */ {0x04, 0x00}, // Partisan /* 15 */ {0x04, 0x01}, // Halbert /* 16 */ {0x04, 0x02}, // Glaive /* 17 */ {0x04, 0x03}, // Berdys /* 18 */ {0x04, 0x04}, // Gungnir /* 19 */ {0x06, 0x00}, // Handgun /* 1A */ {0x06, 0x01}, // Autogun /* 1B */ {0x06, 0x02}, // Lockgun /* 1C */ {0x06, 0x03}, // Railgun /* 1D */ {0x06, 0x04}, // Raygun /* 1E */ {0x07, 0x00}, // Rifle /* 1F */ {0x07, 0x01}, // Sniper /* 20 */ {0x07, 0x02}, // Blaster /* 21 */ {0x07, 0x03}, // Beam /* 22 */ {0x07, 0x04}, // Laser /* 23 */ {0x08, 0x00}, // Mechgun /* 24 */ {0x08, 0x01}, // Assault /* 25 */ {0x08, 0x02}, // Repeater /* 26 */ {0x08, 0x03}, // Gatling /* 27 */ {0x08, 0x04}, // Vulcan /* 28 */ {0x09, 0x00}, // Shot /* 29 */ {0x09, 0x01}, // Spread /* 2A */ {0x09, 0x02}, // Cannon /* 2B */ {0x09, 0x03}, // Launcher /* 2C */ {0x09, 0x04}, // Arms /* 2D */ {0x0A, 0x00}, // Cane /* 2E */ {0x0A, 0x01}, // Stick /* 2F */ {0x0A, 0x02}, // Mace /* 30 */ {0x0A, 0x03}, // Club /* 31 */ {0x0B, 0x00}, // Rod /* 32 */ {0x0B, 0x01}, // Pole /* 33 */ {0x0B, 0x02}, // Pillar /* 34 */ {0x0B, 0x03}, // Striker /* 35 */ {0x0C, 0x00}, // Wand /* 36 */ {0x0C, 0x01}, // Staff /* 37 */ {0x0C, 0x02}, // Baton /* 38 */ {0x0C, 0x03}, // Scepter /* 39 */ {0xFF, 0xFF}, // Special-cased in type_defs_39 (depends on section ID) /* 3A */ {0xFF, 0xFF}, // Special-cased in type_defs_3A (depends on section ID) /* 3B */ {0x01, 0x05}, // DB'S SABER /* 3C */ {0x02, 0x05}, // FLOWEN'S SWORD /* 3D */ {0x06, 0x05}, // VARISTA /* 3E */ {0x08, 0x05}, // M&A60 VISE /* 3F */ {0x0A, 0x04}, // CLUB OF LACONIUM /* 40 */ {0x0C, 0x04}, // FIRE SCEPTER:AGNI /* 41 */ {0x0B, 0x04}, // BATTLE VERGE /* 42 */ {0x01, 0x06}, // KALADBOLG /* 43 */ {0x03, 0x05}, // BLADE DANCE /* 44 */ {0x07, 0x05}, // VISK-235W /* 45 */ {0x0A, 0x05}, // MACE OF ADAMAN /* 46 */ {0x0C, 0x05}, // ICE STAFF:DAGON /* 47 */ {0x0B, 0x05}, // BRAVE HAMMER }); const std::array, 10> WeaponShopRandomSet::type_defs_39({ // Indexed by section_id {0x28, 0x00}, // HARISEN BATTLE FAN {0x2A, 0x00}, // AKIKO'S WOK {0x2B, 0x00}, // TOY HAMMER {0x35, 0x00}, // CRAZY TUNE {0x52, 0x00}, // FLOWER CANE {0x48, 0x00}, // SAMBA MARACAS {0x64, 0x00}, // CHAMELEON SCYTHE {0x59, 0x00}, // BROOM {0x8A, 0x00}, // SANGE {0x99, 0x00}, // ANGEL HARP }); const std::array, 10> WeaponShopRandomSet::type_defs_3A({ // Indexed by section_id {0x99, 0x00}, // ANGEL HARP {0x64, 0x00}, // CHAMELEON SCYTHE {0x8A, 0x00}, // SANGE {0x28, 0x00}, // HARISEN BATTLE FAN {0x59, 0x00}, // BROOM {0x2B, 0x00}, // TOY HAMMER {0x52, 0x00}, // FLOWER CANE {0x2A, 0x00}, // AKIKO'S WOK {0x48, 0x00}, // SAMBA MARACAS {0x35, 0x00}, // CRAZY TUNE }); const std::array WeaponShopRandomSet::bonus_values{ -50, -45, -40, -35, -30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50}; WeaponShopRandomSet::WeaponShopRandomSet(const void* data, size_t size, bool big_endian) { if (big_endian) { this->parse_t(data, size); } else { this->parse_t(data, size); } } WeaponShopRandomSet::WeaponShopRandomSet(const std::string& data, bool big_endian) : WeaponShopRandomSet(data.data(), data.size(), big_endian) {} WeaponShopRandomSet::WeaponShopRandomSet(const phosg::JSON& json) { for (const auto& it : json.get_list("WeaponTypeWeightTables")) { this->weapon_type_weight_tables.emplace_back(table_for_json_t>(*it)); } this->bonus_type_table1 = fixed_table_for_json_t, 9, 6>(json.at("BonusTypeTable1")); this->bonus_type_table2 = fixed_table_for_json_t, 9, 6>(json.at("BonusTypeTable2")); this->bonus_range_table1 = fixed_table_for_json_t, 9>(json.at("BonusRangeTable1")); this->bonus_range_table2 = fixed_table_for_json_t, 9>(json.at("BonusRangeTable2")); this->special_mode_table = fixed_table_for_json_t, 8, 3>(json.at("SpecialModeTable")); this->default_grind_range_table = fixed_table_for_json_t, 6>(json.at("DefaultDringRangeTable")); this->favored_grind_range_table = fixed_table_for_json_t, 6>(json.at("FavoredDringRangeTable")); } template void WeaponShopRandomSet::parse_t(const void* data, size_t size) { std::set start_offsets; phosg::StringReader r(data, size); uint32_t root_offset = r.pget>(size - 0x10); start_offsets.emplace(root_offset); const auto& root = r.pget>(root_offset); start_offsets.emplace(root.weapon_type_table); start_offsets.emplace(root.bonus_type_table1); start_offsets.emplace(root.bonus_type_table2); start_offsets.emplace(root.bonus_range_table1); start_offsets.emplace(root.bonus_range_table2); start_offsets.emplace(root.special_mode_table); start_offsets.emplace(root.default_grind_range_table); start_offsets.emplace(root.favored_grind_range_table); // Count the weapon types tables if (start_offsets.upper_bound(root.weapon_type_table) == start_offsets.end()) { throw std::runtime_error("Weapon type table is out of range"); } for (uint32_t offset = root.weapon_type_table; offset < *start_offsets.upper_bound(root.weapon_type_table); offset += sizeof(TableSpecT)) { start_offsets.emplace(r.pget>(offset).offset); } size_t num_weapon_types_tables = (*start_offsets.upper_bound(root.weapon_type_table) - root.weapon_type_table) / sizeof(TableSpecT); while (this->weapon_type_weight_tables.size() < num_weapon_types_tables) { const auto& spec = r.pget>( root.weapon_type_table + this->weapon_type_weight_tables.size() * sizeof(TableSpecT)); this->weapon_type_weight_tables.emplace_back(parse_table_t>( r, spec.offset, spec.row_size, start_offsets)); } auto parse_fixed_table_into_1d = [&](std::array, Count>& ret, uint32_t offset) -> void { auto sub_r = r.sub(offset, sizeof(IntPairT) * Count); for (size_t z = 0; z < Count; z++) { ret[z] = sub_r.get>>(); } }; auto parse_fixed_table_into_2d = [&](std::array, RowSize>, RowCount>& ret, uint32_t offset) -> void { auto sub_r = r.sub(offset, sizeof(IntPairT) * RowSize * RowCount); for (size_t y = 0; y < RowCount; y++) { for (size_t x = 0; x < RowSize; x++) { ret[y][x] = sub_r.get>>(); } } }; parse_fixed_table_into_2d(this->bonus_type_table1, root.bonus_type_table1); parse_fixed_table_into_2d(this->bonus_type_table2, root.bonus_type_table2); parse_fixed_table_into_1d(this->bonus_range_table1, root.bonus_range_table1); parse_fixed_table_into_1d(this->bonus_range_table2, root.bonus_range_table2); parse_fixed_table_into_2d(this->special_mode_table, root.special_mode_table); parse_fixed_table_into_1d(this->default_grind_range_table, root.default_grind_range_table); parse_fixed_table_into_1d(this->favored_grind_range_table, root.favored_grind_range_table); } template std::string WeaponShopRandomSet::serialize_binary_t() const { RELFileWriter rel; WeaponRootT root; std::vector> weapon_type_weight_table_specs; weapon_type_weight_table_specs.reserve(this->weapon_type_weight_tables.size()); for (const auto& table : this->weapon_type_weight_tables) { weapon_type_weight_table_specs.emplace_back(serialize_table_t>(rel, table)); } root.weapon_type_table = rel.w.size(); for (const auto& spec : weapon_type_weight_table_specs) { rel.put(spec); rel.relocations.emplace(rel.w.size() - 8); } auto serialize_fixed_table_1d = [&](const std::array, Count>& table) -> uint32_t { uint32_t ret = rel.w.size(); for (size_t z = 0; z < Count; z++) { rel.template put>>(table[z]); } return ret; }; auto serialize_fixed_table_2d = [&](const std::array, RowSize>, RowCount>& table) -> uint32_t { uint32_t ret = rel.w.size(); for (size_t y = 0; y < RowCount; y++) { for (size_t x = 0; x < RowSize; x++) { rel.template put>>(table[y][x]); } } return ret; }; rel.align(4); root.bonus_type_table1 = serialize_fixed_table_2d(this->bonus_type_table1); root.bonus_type_table2 = serialize_fixed_table_2d(this->bonus_type_table2); root.special_mode_table = serialize_fixed_table_2d(this->special_mode_table); root.bonus_range_table1 = serialize_fixed_table_1d(this->bonus_range_table1); root.bonus_range_table2 = serialize_fixed_table_1d(this->bonus_range_table2); root.default_grind_range_table = serialize_fixed_table_1d(this->default_grind_range_table); root.favored_grind_range_table = serialize_fixed_table_1d(this->favored_grind_range_table); rel.align(4); uint32_t root_offset = rel.put(root); for (size_t z = 1; z <= 8; z++) { rel.relocations.emplace(rel.w.size() - (z * 4)); } return rel.finalize(root_offset); } std::string WeaponShopRandomSet::serialize_binary(bool big_endian) const { return big_endian ? this->serialize_binary_t() : this->serialize_binary_t(); } phosg::JSON WeaponShopRandomSet::json() const { auto weapon_type_weight_tables_json = phosg::JSON::list(); for (const auto& table : this->weapon_type_weight_tables) { weapon_type_weight_tables_json.emplace_back(json_for_table_t(table)); } return phosg::JSON::dict({ {"WeaponTypeWeightTables", std::move(weapon_type_weight_tables_json)}, {"BonusTypeTable1", json_for_fixed_table_t(this->bonus_type_table1)}, {"BonusTypeTable2", json_for_fixed_table_t(this->bonus_type_table2)}, {"BonusRangeTable1", json_for_fixed_table_t(this->bonus_range_table1)}, {"BonusRangeTable2", json_for_fixed_table_t(this->bonus_range_table2)}, {"SpecialModeTable", json_for_fixed_table_t(this->special_mode_table)}, {"DefaultDringRangeTable", json_for_fixed_table_t(this->default_grind_range_table)}, {"FavoredDringRangeTable", json_for_fixed_table_t(this->favored_grind_range_table)}, }); } void WeaponShopRandomSet::print(FILE* stream) const { phosg::fwrite_fmt(stream, "WeaponShopRandomSet\n"); for (size_t table_index = 0; table_index < this->weapon_type_weight_tables.size(); table_index++) { phosg::fwrite_fmt(stream, " Weapon type weight table {}\n", table_index); for (size_t z = 0; z < this->weapon_type_weight_tables[table_index].size(); z++) { phosg::fwrite_fmt(stream, " {:02X} ({:<10}):", z, name_for_section_id(z)); for (const auto& cell : this->weapon_type_weight_tables[table_index][z]) { if (cell.value == 0x39) { phosg::fwrite_fmt(stream, " {:02X} (SECID1) @ {:03}", cell.value, cell.weight); } else if (cell.value == 0x3A) { phosg::fwrite_fmt(stream, " {:02X} (SECID2) @ {:03}", cell.value, cell.weight); } else { const auto& def = this->type_defs.at(cell.value); phosg::fwrite_fmt(stream, " {:02X} (00{:02X}{:02X}) @ {:03}", cell.value, def.first, def.second, cell.weight); } } phosg::fwrite_fmt(stream, "\n"); } } auto print_fixed_table_1d = [&](const std::array, Count>& table) -> void { for (size_t z = 0; z < Count; z++) { const auto& cell = table[z]; phosg::fwrite_fmt(stream, " {:02}: {:03}-{:03}\n", z, cell.first, cell.second); } }; auto print_fixed_table_2d = [&](const std::array, RowSize>, RowCount>& table) -> void { for (size_t y = 0; y < RowCount; y++) { phosg::fwrite_fmt(stream, " {:02X}:", y); for (size_t x = 0; x < RowSize; x++) { const auto& cell = table[y][x]; phosg::fwrite_fmt(stream, " {:02} @ {:03}", cell.first, cell.second); } phosg::fwrite_fmt(stream, "\n"); } }; phosg::fwrite_fmt(stream, " Bonus type table 1\n"); print_fixed_table_2d(this->bonus_type_table1); phosg::fwrite_fmt(stream, " Bonus type table 2\n"); print_fixed_table_2d(this->bonus_type_table2); phosg::fwrite_fmt(stream, " Bonus range table 1\n"); print_fixed_table_1d(this->bonus_range_table1); phosg::fwrite_fmt(stream, " Bonus range table 2\n"); print_fixed_table_1d(this->bonus_range_table2); phosg::fwrite_fmt(stream, " Special mode table\n"); print_fixed_table_2d(this->special_mode_table); phosg::fwrite_fmt(stream, " Default grind range table\n"); print_fixed_table_1d(this->default_grind_range_table); phosg::fwrite_fmt(stream, " Favored grind range table\n"); print_fixed_table_1d(this->favored_grind_range_table); }