diff --git a/README.md b/README.md index dcd7e2d7..52e051ee 100644 --- a/README.md +++ b/README.md @@ -780,36 +780,37 @@ newserv has many CLI options, which can be used to access functionality other th The data formats that newserv can convert to/from are: -| Format | Encode/compress action | Decode/extract action | -|-------------------------------------|---------------------------|------------------------------| -| PRS compression | `compress-prs` | `decompress-prs` | -| PR2/PRC compression | `compress-pr2` | `decompress-pr2` | -| BC0 compression | `compress-bc0` | `decompress-bc0` | -| Raw encrypted data | `encrypt-data` | `decrypt-data` | -| Episode 3 command mask | `encrypt-trivial-data` | `decrypt-trivial-data` | -| Challenge Mode rank text | `encrypt-challenge-data` | `decrypt-challenge-data` | -| PSO DC quest file (.vms) | None | `decode-vms` | -| PSO GC quest file (.gci) | None | `decode-gci` | -| Download quest file (.dlq) | None | `decode-dlq` | -| Server quest file (.qst) | `encode-qst` | `decode-qst` | -| PSO DC save file (.vms) | `encrypt-vms-save` | `decrypt-vms-save` | -| PSO PC save file | `encrypt-pc-save` | `decrypt-pc-save` | -| PSO GC save file (.gci) | `encrypt-gci-save` | `decrypt-gci-save` | -| PSO Xbox save file | None | `decrypt-xbox-save` | -| PSO GC snapshot file | None | `decode-gci-snapshot` | -| Quest script (.bin) | `assemble-quest-script` | `disassemble-quest-script` | -| Quest map (.dat) | None | `disassemble-quest-map` | -| AFS archive (.afs) | None | `extract-afs` | -| BML archive (.bml) | None | `extract-bml` | -| PPK archive (.ppk) | None | `extract-ppk` | -| GSL archive (.gsl) | `generate-gsl` | `extract-gsl` | -| GVM texture (.gvm) | `encode-gvm` | None | -| Bitmap font (.fon) | `encode-bitmap-font` | `decode-bitmap-font` | -| Text archive | `encode-text-archive` | `decode-text-archive` | -| Unicode text set | `encode-unicode-text-set` | `decode-unicode-text-set` | -| Word Select data set | None | `decode-word-select-set` | -| Set data table | None | `disassemble-set-data-table` | -| Rare item table (AFS/GSL/JSON/HTML) | `convert-rare-item-set` | `convert-rare-item-set` | +| Format | Encode/compress action | Decode/extract action | +|-------------------------------------|-------------------------------|-------------------------------| +| PRS compression | `compress-prs` | `decompress-prs` | +| PR2/PRC compression | `compress-pr2` | `decompress-pr2` | +| BC0 compression | `compress-bc0` | `decompress-bc0` | +| Raw encrypted data | `encrypt-data` | `decrypt-data` | +| Episode 3 command mask | `encrypt-trivial-data` | `decrypt-trivial-data` | +| Challenge Mode rank text | `encrypt-challenge-data` | `decrypt-challenge-data` | +| PSO DC quest file (.vms) | None | `decode-vms` | +| PSO GC quest file (.gci) | None | `decode-gci` | +| Download quest file (.dlq) | None | `decode-dlq` | +| Server quest file (.qst) | `encode-qst` | `decode-qst` | +| PSO DC save file (.vms) | `encrypt-vms-save` | `decrypt-vms-save` | +| PSO PC save file | `encrypt-pc-save` | `decrypt-pc-save` | +| PSO GC save file (.gci) | `encrypt-gci-save` | `decrypt-gci-save` | +| PSO Xbox save file | None | `decrypt-xbox-save` | +| PSO GC snapshot file | None | `decode-gci-snapshot` | +| Quest script (.bin) | `assemble-quest-script` | `disassemble-quest-script` | +| Quest map (.dat) | None | `disassemble-quest-map` | +| AFS archive (.afs) | None | `extract-afs` | +| BML archive (.bml) | None | `extract-bml` | +| PPK archive (.ppk) | None | `extract-ppk` | +| GSL archive (.gsl) | `generate-gsl` | `extract-gsl` | +| GVM texture (.gvm) | `encode-gvm` | None (use resource_dasm) | +| Bitmap font (.fon) | `encode-bitmap-font` | `decode-bitmap-font` | +| Text archive | `encode-text-archive` | `decode-text-archive` | +| Unicode text set | `encode-unicode-text-set` | `decode-unicode-text-set` | +| Word Select data set | None | `decode-word-select-set` | +| Set data table | None | `disassemble-set-data-table` | +| Rare item table (AFS/GSL/JSON/HTML) | `convert-rare-item-set` | `convert-rare-item-set` | +| Item definitions (ItemPMT) | `encode-item-parameter-table` | `decode-item-parameter-table` | There are several actions that don't fit well into the table above, which let you do other things: diff --git a/TODO.md b/TODO.md index 67d10977..63eb3710 100644 --- a/TODO.md +++ b/TODO.md @@ -30,8 +30,6 @@ ## PSOBB -- Figure out why Pouilly Slime EXP doesn't work - Make server-specified rare enemies work with maps loaded by the proxy -- Implement serialization for various table types (ItemPMT, ItemPT, etc.) - Record some BB tests - Add all necessary Guild Card number rewrites in BB commands on the proxy diff --git a/src/ItemNameIndex.cc b/src/ItemNameIndex.cc index ad3d66bf..1c79c9bf 100644 --- a/src/ItemNameIndex.cc +++ b/src/ItemNameIndex.cc @@ -1086,7 +1086,7 @@ void ItemNameIndex::print_table(FILE* stream) const { phosg::fwrite_fmt(stream, "ITEM COMBINATIONS\n"); phosg::fwrite_fmt(stream, " ---USE + -EQUIP => RESULT MLV GND LVL CLS\n"); - for (const auto& combo_list_it : pmt->all_item_combinations()) { + for (const auto& combo_list_it : pmt->item_combinations_index()) { for (const auto& combo : combo_list_it.second) { phosg::fwrite_fmt(stream, " {:02X}{:02X}{:02X} + {:02X}{:02X}{:02X} => {:02X}{:02X}{:02X}", combo.used_item[0], combo.used_item[1], combo.used_item[2], @@ -1222,22 +1222,22 @@ void ItemNameIndex::print_table(FILE* stream) const { phosg::fwrite_fmt(stream, "SOUND REMAPS\n"); phosg::fwrite_fmt(stream, " -SOUND1- => RT:[...] CC:[...]\n"); - for (const auto& [sound_id, remaps] : pmt->get_all_sound_remaps()) { + for (const auto& remap : pmt->get_all_sound_remaps()) { std::string rt_str; - for (uint32_t rt_sound_id : remaps.by_rt_index) { + for (uint32_t rt_sound_id : remap.by_rt_index) { if (!rt_str.empty()) { rt_str += ","; } rt_str += std::format("{:08X}", rt_sound_id); } std::string cc_str; - for (uint32_t cc_sound_id : remaps.by_char_class) { + for (uint32_t cc_sound_id : remap.by_char_class) { if (!cc_str.empty()) { cc_str += ","; } cc_str += std::format("{:08X}", cc_sound_id); } - phosg::fwrite_fmt(stream, " {:08X} => RT:[{}] CC:[{}]\n", sound_id, rt_str, cc_str); + phosg::fwrite_fmt(stream, " {:08X} => RT:[{}] CC:[{}]\n", remap.sound_id, rt_str, cc_str); } phosg::fwrite_fmt(stream, "TECH BOOSTS\n"); diff --git a/src/ItemParameterTable.cc b/src/ItemParameterTable.cc index fcfebd02..8788762f 100644 --- a/src/ItemParameterTable.cc +++ b/src/ItemParameterTable.cc @@ -4,6 +4,77 @@ using namespace std; +/* General notes on the ItemPMT formats: + * + * Sega apparently serialized the fields in this order, so we do the same in BinaryItemParameterTableT::serialize. + * There's likely no reason for this order, though it appears they made an effort to place fields that don't + * contain pointers before fields that do. + * DCTE 112K V1 V2 GCTE GCV3 XBV3 V4 R + * 0000 0000 0000 0008 0040 0040 0040 00040 armor_table[0][1] + * 030C 030C ---- ---- ---- ---- ---- ----- * shield_stat_boost_index_table // -> uint8_t[armor_table[1].count] + * 0334 0334 03A8 0590 0874 0EE8 0EE8 01500 armor_table[0][0] + * 0668 0668 ---- ---- ---- ---- ---- ----- * armor_stat_boost_index_table // -> uint8_t[armor_table[0].count] + * 0694 0694 0780 0AA0 0E5C 14D0 14D0 02020 unit_table[0] + * 08B4 08B4 0AB0 0DDC 12DC 1960 1960 02804 mag_table[0] + * 0B74 0B74 0D70 1264 16B4 1FA8 1FA8 03118 tool_table[0][0] (the rest follow immediately) + * 0E54 0F24 1120 1874 1B3C 2C8C 2C8C 04348 weapon_table[0][0] (the rest follow immediately) + * 1908 199C ---- ---- ---- ---- ---- ----- * weapon_stat_boost_index_table // -> uint8_t[max(weapon.id : weapon_table) - (ItemStarsFirstID - 1)] + * 1994 1A28 1DB0 2E4C 37A4 A7FC A7FC 0DE7C * photon_color_table // -> PhotonColorEntry[...] + * 1C64 1CF8 2080 32CC 3A74 AACC AACC 0E194 * weapon_range_table // -> WeaponRange[...] (indexed by data1_1, but also by RangedSpecial::weapon_range_index + a version-dependent constant, and also by the result of a vfunc call on some TItemWeapons) + * 1F98 202C 23C8 3DF8 47BC B88C B8A0 0F4B8 * weapon_kind_table // -> uint8_t[...] + * 1FBF 2053 23F0 3E84 484C B938 B94C 0F5A8 * weapon_sale_divisor_table // -> uint8_t[...] on DC protos; float[...] on all other versions + * 1FE6 207A 248C 40A8 4A80 BBCC BBE0 0F83C * non_weapon_sale_divisor_table // -> NonWeaponSaleDivisors + * 1FE9 20D5 249C 40B8 4A90 BBDC BBF0 0F84C mag_feed_table (data) + * 22A9 233D 275C 4378 4D50 BE9C BEB0 0FB0C * star_value_table // -> uint8_t[...] (indexed by .id from weapon, armor, etc.) + * 23EE 2484 28A2 ---- ---- ---- ---- ----- * unknown_a1 // TODO + * 275E 27F4 2C12 4540 4F72 C100 C114 0FE3C * special_table // -> Special[...] + * 2804 2898 2CB8 45E4 5018 C1A4 C1B8 0FEE0 * weapon_effect_table // -> WeaponEffect[...] + * ---- ---- ---- 5704 61B8 D6E4 D6F8 11C80 * shield_effect_table // -> ShieldEffect[...] (indexed by data1[2]) + * ---- ---- ---- ---- 68B0 DE48 DE5C 12754 * sound_remap_table // -> {count, offset -> {sound_id, by_rt_index_offset -> uint32_t[SoundRemapRTTableSize], by_char_class_offset -> uint32_t[12]}} // Leaves first, then array refs going up the tree; forward order on the leaves (as if it matters); everything just before this offset + * 2CE4 2D78 3198 58DC 68B8 DE50 DE64 1275C * stat_boost_table // -> StatBoost[...] + * ---- ---- ---- ---- 69D8 DF88 DF9C 12894 * max_tech_level_table // -> MaxTechniqueLevels + * ---- ---- ---- ---- 6ABC E06C E080 12978 combination_table[0] + * ---- ---- ---- ---- 6B1C EB8C EBA0 14278 * tech_boost_table // -> TechBoostEntry[...] + * ---- ---- ---- ---- ---- EEBC EED0 14698 unwrap_table[0][0] (the rest follow immediately) + * ---- ---- ---- ---- ---- EF24 EF38 14700 unsealable_table[0] + * ---- ---- ---- ---- ---- EF28 EF3C 14710 ranged_special_table[0] + * 2D94 2E28 3258 5A5C 6E4C EF90 EF9C 1478C * armor_table // -> {count, offset -> ArmorOrShieldV*[count]}][2] + * 2DA4 2E38 3268 5A6C 6E5C EFA0 EFAC 1479C * unit_table // -> {count, offset -> UnitV*[count]} (last if out of range) + * 2DAC 2E40 3270 5A74 6E64 EFA8 EFB4 147A4 * mag_table // -> {count, offset -> MagV*[count]} + * 2DB4 2E48 3278 5A7C 6E6C EFB0 EFBC 147AC * tool_table // -> {count, offset -> ToolV*[count]}[...] (last if out of range) + * 2E1C 2EB8 32E8 5AFC 6F0C F078 F084 14884 * weapon_table // -> {count, offset -> WeaponV*[count]}[...] + * ---- ---- ---- ---- 737C F5D0 F5DC 14FF4 * combination_table // -> {count, offset -> ItemCombination[count]} + * ---- ---- ---- ---- ---- F5D8 F5E4 14FFC unwrap_table[0] (the rest follow immediately) + * ---- ---- ---- ---- ---- F5F0 F5FC 15014 * unwrap_table // -> {count, offset -> {count, offset -> EventItem[count]}[count] + * ---- ---- ---- ---- ---- F5F8 F604 1501C * unsealable_table // -> {count, offset -> UnsealableItem[count]} + * ---- ---- ---- ---- ---- F600 F60C 15024 * ranged_special_table // -> {count, offset -> RangedSpecial[count]} + * 2F54 2FF0 3420 5F4C 7384 F608 F614 1502C * mag_feed_table (offsets) // -> MagFeedResultsTable + * + * Some hardcoded constants are different across versions. Our parser/serializer doesn't use some of them, but + * these constants are (all values in hex): + * DCTE / 112K / V1 / V2 / GCTE / GCV3 / XBV3 / V4 + * Weapon class count: 27 / 27 / 27 / 89 / 8D / AA / AA / ED + * Tool class count: 0D / 0E / 0E / 10 / 13 / 18 / 18 / 1B + * Item stars first ID: 22 / 26 / 26 / 4E / 76 / 94 / 94 / B1 + * Item stars last ID: 168 / 16C / 16C / 215 / 298 / 2F7 / 2F7 / 437 + * Special stars start index: AA / AE / AE / 138 / 1A3 / 1CB / 1CB / 256 + * Special count: 28 / 28 / 29 / 29 / 29 / 29 / 29 / 29 + * Photon color count: 14 / 14 / 14 / 20 / 20 / 20 / 20 / 20 + * Sound RT table size: 00 / 00 / 00 / 00 / 4F / 58 / 58 / 6A + * + * Oddities discovered in the various formats: + * - DC NTE through DC V1: + * - The mag count is wrong; there are 0x2C mags defined in the file but the ArrayRef has a count of only 0x28. + * - A few of the tool table entries are encoded out of order. This isn't a bug, just a curiosity. + * - A few of the weapon table entries are encoded out of order as well. + * - One of the weapon classes has an incorrect count, so it also includes the first weapon of the next class. + * - V2: + * - All of the quirks from V1, except the mag count is now correct. + * - The placeholder entries were added in most of the item tables (weapon, armor, shield, unit and mag, but not + * tool). The counts (major count, for weapons) are off by one for this reason, hence HasImplicitPlaceholders. + * - V3 and later: All of the oddities from V2 and earlier are gone. + */ + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Utilities @@ -90,10 +161,10 @@ ItemParameterTable::Weapon ItemParameterTable::Weapon::from_json(const phosg::JS ret.trail2_x = json.get_int("Trail2X"); ret.trail2_y = json.get_int("Trail2Y"); ret.color = json.get_int("Color"); - const auto& unknown_a1 = json.get_list("UnknownA1"); - ret.unknown_a1[0] = unknown_a1[0]->as_int(); - ret.unknown_a1[1] = unknown_a1[1]->as_int(); - ret.unknown_a1[2] = unknown_a1[2]->as_int(); + uint32_t unknown_a1 = json.get_int("UnknownA1"); + ret.unknown_a1[0] = unknown_a1 >> 16; + ret.unknown_a1[1] = unknown_a1 >> 8; + ret.unknown_a1[2] = unknown_a1; ret.unknown_a4 = json.get_int("UnknownA4"); ret.unknown_a5 = json.get_int("UnknownA5"); ret.tech_boost_entry_index = json.get_int("TechBoostEntryIndex"); @@ -120,7 +191,7 @@ phosg::JSON ItemParameterTable::Weapon::json() const { ret.emplace("Trail2X", this->trail2_x); ret.emplace("Trail2Y", this->trail2_y); ret.emplace("Color", this->color); - ret.emplace("UnknownA1", phosg::JSON::list({this->unknown_a1[0], this->unknown_a1[1], this->unknown_a1[2]})); + ret.emplace("UnknownA1", (this->unknown_a1[0] << 16) | (this->unknown_a1[1] << 8) | this->unknown_a1[2]); ret.emplace("UnknownA4", this->unknown_a4); ret.emplace("UnknownA5", this->unknown_a5); ret.emplace("TechBoostEntryIndex", this->tech_boost_entry_index); @@ -315,13 +386,13 @@ ItemParameterTable::TechBoost ItemParameterTable::TechBoost::from_json(const pho ItemParameterTable::TechBoost ret; ret.tech_num1 = json.get_int("TechNum1"); ret.flags1 = json.get_int("Flags1"); - ret.amount1 = json.get_int("Amount1"); + ret.amount1 = json.get_float("Amount1"); ret.tech_num2 = json.get_int("TechNum2"); ret.flags2 = json.get_int("Flags2"); - ret.amount2 = json.get_int("Amount2"); + ret.amount2 = json.get_float("Amount2"); ret.tech_num3 = json.get_int("TechNum3"); ret.flags3 = json.get_int("Flags3"); - ret.amount3 = json.get_int("Amount3"); + ret.amount3 = json.get_float("Amount3"); return ret; } phosg::JSON ItemParameterTable::TechBoost::json() const { @@ -392,14 +463,14 @@ ItemParameterTable::PhotonColorEntry ItemParameterTable::PhotonColorEntry::from_ ret.unknown_a1 = json.get_int("UnknownA1"); const auto& unknown_a2 = json.get_list("UnknownA2"); const auto& unknown_a3 = json.get_list("UnknownA3"); - ret.unknown_a2.x = unknown_a2.at(0)->as_int(); - ret.unknown_a2.y = unknown_a2.at(1)->as_int(); - ret.unknown_a2.z = unknown_a2.at(2)->as_int(); - ret.unknown_a2.t = unknown_a2.at(3)->as_int(); - ret.unknown_a3.x = unknown_a3.at(0)->as_int(); - ret.unknown_a3.y = unknown_a3.at(1)->as_int(); - ret.unknown_a3.z = unknown_a3.at(2)->as_int(); - ret.unknown_a3.t = unknown_a3.at(3)->as_int(); + ret.unknown_a2.x = unknown_a2.at(0)->as_float(); + ret.unknown_a2.y = unknown_a2.at(1)->as_float(); + ret.unknown_a2.z = unknown_a2.at(2)->as_float(); + ret.unknown_a2.t = unknown_a2.at(3)->as_float(); + ret.unknown_a3.x = unknown_a3.at(0)->as_float(); + ret.unknown_a3.y = unknown_a3.at(1)->as_float(); + ret.unknown_a3.z = unknown_a3.at(2)->as_float(); + ret.unknown_a3.t = unknown_a3.at(3)->as_float(); return ret; } phosg::JSON ItemParameterTable::PhotonColorEntry::json() const { @@ -444,8 +515,8 @@ phosg::JSON ItemParameterTable::WeaponEffect::json() const { ItemParameterTable::WeaponRange ItemParameterTable::WeaponRange::from_json(const phosg::JSON& json) { ItemParameterTable::WeaponRange ret; - ret.unknown_a1 = json.get_int("UnknownA1"); - ret.unknown_a2 = json.get_int("UnknownA2"); + ret.unknown_a1 = json.get_float("UnknownA1"); + ret.unknown_a2 = json.get_float("UnknownA2"); ret.unknown_a3 = json.get_int("UnknownA3"); ret.unknown_a4 = json.get_int("UnknownA4"); ret.unknown_a5 = json.get_int("UnknownA5"); @@ -512,11 +583,18 @@ phosg::JSON ItemParameterTable::json() const { for (size_t data1_1 = 0; data1_1 < this->num_weapon_classes(); data1_1++) { size_t class_size = this->num_weapons_in_class(data1_1); uint8_t weapon_kind = this->get_weapon_kind(data1_1); - float sale_divisor = this->get_sale_divisor(0, data1_1); + std::optional sale_divisor; + if (data1_1 < this->num_weapon_sale_divisors()) { + sale_divisor = this->get_sale_divisor(0, data1_1); + } for (size_t data1_2 = 0; data1_2 < class_size; data1_2++) { auto weapon_dict = this->get_weapon(data1_1, data1_2).json(); weapon_dict.emplace("WeaponKind", weapon_kind); - weapon_dict.emplace("SaleDivisor", sale_divisor); + if (sale_divisor.has_value()) { + weapon_dict.emplace("SaleDivisor", *sale_divisor); + } else { + weapon_dict.emplace("SaleDivisor", phosg::JSON(nullptr)); + } items_json.emplace(std::format("00{:02X}{:02X}", data1_1, data1_2), std::move(weapon_dict)); } } @@ -618,15 +696,13 @@ phosg::JSON ItemParameterTable::json() const { } auto combination_table_json = phosg::JSON::list(); - for (const auto& [used_item, combos] : this->all_item_combinations()) { - for (const auto& combo : combos) { - combination_table_json.emplace_back(combo.json()); - } + for (size_t z = 0; z < this->num_item_combinations(); z++) { + combination_table_json.emplace_back(this->get_item_combination(z).json()); } auto sound_remaps_json = phosg::JSON::list(); - for (const auto& [_, remaps] : get_all_sound_remaps()) { - sound_remaps_json.emplace_back(remaps.json()); + for (const auto& remap : get_all_sound_remaps()) { + sound_remaps_json.emplace_back(remap.json()); } auto tech_boosts_json = phosg::JSON::list(); @@ -637,9 +713,11 @@ phosg::JSON ItemParameterTable::json() const { auto unwrap_tables_json = phosg::JSON::list(); for (size_t event = 0; event < this->num_events(); event++) { auto [items, count] = this->get_event_items(event); + auto this_unwrap_table_json = phosg::JSON::list(); for (size_t z = 0; z < count; z++) { - unwrap_tables_json.emplace_back(items[z].json()); + this_unwrap_table_json.emplace_back(items[z].json()); } + unwrap_tables_json.emplace_back(std::move(this_unwrap_table_json)); } auto unsealable_items_json = phosg::JSON::list(); @@ -693,14 +771,14 @@ public: explicit JSONItemParameterTable(const phosg::JSON& json) { for (const auto& [key, item_json] : json.get_dict("Items")) { auto item_code = phosg::parse_data_string(key); - if (item_code.size() != 3) { + if (item_code.size() < 2 || item_code.size() > 3) { throw std::runtime_error("invalid item code in Items dict"); } uint8_t data1_0 = item_code[0]; uint8_t data1_1 = item_code[1]; - uint8_t data1_2 = item_code[2]; + uint8_t data1_2 = (data1_0 != 2) ? item_code.at(2) : 0; switch (data1_0) { - case 0: + case 0: { if (this->weapons.size() <= data1_1) { this->weapons.resize(data1_1 + 1); } @@ -712,11 +790,15 @@ public: this->weapon_kinds.resize(data1_1 + 1, 0); } this->weapon_kinds[data1_1] = item_json->get_int("WeaponKind"); - if (this->weapon_sale_divisors.size() <= data1_1) { - this->weapon_sale_divisors.resize(data1_1 + 1, 0); + auto sale_divisor_json = item_json->at("SaleDivisor"); + if (!sale_divisor_json.is_null()) { + if (this->weapon_sale_divisors.size() <= data1_1) { + this->weapon_sale_divisors.resize(data1_1 + 1, 0); + } + this->weapon_sale_divisors[data1_1] = sale_divisor_json.as_float(); } - this->weapon_sale_divisors[data1_1] = item_json->get_int("WeaponKind"); break; + } case 1: switch (item_code[1]) { case 1: @@ -770,10 +852,10 @@ public: this->weapon_ranges.emplace_back(WeaponRange::from_json(*weapon_range_json)); } - this->armor_sale_divisor = json.get_int("ArmorSaleDivisor"); - this->shield_sale_divisor = json.get_int("ShieldSaleDivisor"); - this->unit_sale_divisor = json.get_int("UnitSaleDivisor"); - this->mag_sale_divisor = json.get_int("MagSaleDivisor"); + this->armor_sale_divisor = json.get_float("ArmorSaleDivisor"); + this->shield_sale_divisor = json.get_float("ShieldSaleDivisor"); + this->unit_sale_divisor = json.get_float("UnitSaleDivisor"); + this->mag_sale_divisor = json.get_float("MagSaleDivisor"); const auto& mag_feed_results_json = json.get_list("MagFeedResults"); for (size_t table_index = 0; table_index < 8; table_index++) { @@ -828,13 +910,11 @@ public: } for (const auto& combo_json : json.get_list("ItemCombinations")) { - auto combo = ItemCombination::from_json(*combo_json); - this->item_combinations[item_code_to_u32(combo.used_item)].emplace_back(std::move(combo)); + this->item_combinations.emplace_back(ItemCombination::from_json(*combo_json)); } for (const auto& remaps_json : json.get_list("SoundRemaps")) { - auto remaps = SoundRemaps::from_json(*remaps_json); - this->sound_remaps.emplace(remaps.sound_id, std::move(remaps)); + this->sound_remaps.emplace_back(SoundRemaps::from_json(*remaps_json)); } for (const auto& it : json.get_list("TechBoosts")) { @@ -873,13 +953,13 @@ public: if (data1_1 < 1 || data1_1 > 2) { throw std::logic_error("invalid armor/shield class"); } - return (data1_1 > 1) ? this->armors.size() : this->shields.size(); + return (data1_1 > 1) ? this->shields.size() : this->armors.size(); } virtual const ArmorOrShield& get_armor_or_shield(uint8_t data1_1, uint8_t data1_2) const { if (data1_1 < 1 || data1_1 > 2) { throw std::logic_error("invalid armor/shield class"); } - return (data1_1 > 1) ? this->armors.at(data1_2) : this->shields.at(data1_2); + return (data1_1 > 1) ? this->shields.at(data1_2) : this->armors.at(data1_2); } virtual size_t num_units() const { @@ -909,6 +989,9 @@ public: return this->mags.at(data1_1); } + virtual size_t num_weapon_kinds() const { + return this->weapon_kinds.size(); + } virtual uint8_t get_weapon_kind(uint8_t data1_1) const { return this->weapon_kinds.at(data1_1); } @@ -927,6 +1010,10 @@ public: return this->weapon_ranges.at(index); } + virtual size_t num_weapon_sale_divisors() const { + return this->weapon_sale_divisors.size(); + } + virtual float get_sale_divisor(uint8_t data1_0, uint8_t data1_1) const { switch (data1_0) { case 0: @@ -1020,11 +1107,15 @@ public: return this->max_tech_levels.at(tech_num).at(char_class); } - virtual const std::map>& all_item_combinations() const { - return this->item_combinations; + virtual size_t num_item_combinations() const { + return this->item_combinations.size(); } - virtual const std::unordered_map& get_all_sound_remaps() const { + virtual const ItemCombination& get_item_combination(size_t index) const { + return this->item_combinations.at(index); + } + + virtual const std::vector& get_all_sound_remaps() const { return this->sound_remaps; } @@ -1083,8 +1174,8 @@ protected: std::vector stat_boosts; std::vector shield_effects; MaxTechniqueLevels max_tech_levels; - std::map> item_combinations; - std::unordered_map sound_remaps; + std::vector item_combinations; + std::vector sound_remaps; std::vector tech_boosts; std::vector> unwrap_table; std::unordered_set unsealable_items; @@ -1098,6 +1189,8 @@ template struct ItemBaseV2T { /* 00 */ U32T id = 0xFFFFFFFF; /* 04 */ + ItemBaseV2T() = default; + ItemBaseV2T(const ItemParameterTable::ItemBase& b) : id(b.id) {} void parse_into(ItemParameterTable::ItemBase& ret) const { ret.id = this->id; } @@ -1108,6 +1201,8 @@ struct ItemBaseV3T : ItemBaseV2T { /* 04 */ U16T type = 0; /* 06 */ U16T skin = 0; /* 08 */ + ItemBaseV3T() = default; + ItemBaseV3T(const ItemParameterTable::ItemBase& b) : ItemBaseV2T(b), type(b.type), skin(b.skin) {} void parse_into(ItemParameterTable::ItemBase& ret) const { this->ItemBaseV2T::parse_into(ret); ret.type = this->type; @@ -1118,6 +1213,8 @@ struct ItemBaseV3T : ItemBaseV2T { struct ItemBaseV4 : ItemBaseV3T { /* 08 */ le_uint32_t team_points = 0; /* 0C */ + ItemBaseV4() = default; + ItemBaseV4(const ItemParameterTable::ItemBase& b) : ItemBaseV3T(b), team_points(b.team_points) {} void parse_into(ItemParameterTable::ItemBase& ret) const { this->ItemBaseV3T::parse_into(ret); ret.team_points = this->team_points; @@ -1137,6 +1234,19 @@ struct WeaponDCProtos { /* 12 */ uint8_t special = 0; /* 13 */ uint8_t ata = 0; /* 14 */ + WeaponDCProtos() = default; + WeaponDCProtos(const ItemParameterTable::Weapon& w) + : base(w), + class_flags(w.class_flags), + atp_min(w.atp_min), + atp_max(w.atp_max), + atp_required(w.atp_required), + mst_required(w.mst_required), + ata_required(w.ata_required), + max_grind(w.max_grind), + photon(w.photon), + special(w.special), + ata(w.ata) {} operator ItemParameterTable::Weapon() const { ItemParameterTable::Weapon ret; this->base.parse_into(ret); @@ -1158,9 +1268,13 @@ struct WeaponV1V2 : WeaponDCProtos { /* 14 */ uint8_t stat_boost_entry_index = 0; // TODO: This could be larger (16 or 32 bits) /* 15 */ parray unknown_a9; /* 18 */ + WeaponV1V2() = default; + WeaponV1V2(const ItemParameterTable::Weapon& w) + : WeaponDCProtos(w), stat_boost_entry_index(w.stat_boost_entry_index), unknown_a9(w.v2_unknown_a9) {} operator ItemParameterTable::Weapon() const { ItemParameterTable::Weapon ret = this->WeaponDCProtos::operator ItemParameterTable::Weapon(); ret.stat_boost_entry_index = this->stat_boost_entry_index; + ret.v2_unknown_a9 = this->unknown_a9; return ret; } } __packed_ws__(WeaponV1V2, 0x18); @@ -1188,6 +1302,28 @@ struct WeaponGCNTET { /* 20 */ int8_t color = 0; /* 21 */ parray unknown_a1 = 0; /* 24 */ + WeaponGCNTET() = default; + WeaponGCNTET(const ItemParameterTable::Weapon& w) + : base(w), + class_flags(w.class_flags), + atp_min(w.atp_min), + atp_max(w.atp_max), + atp_required(w.atp_required), + mst_required(w.mst_required), + ata_required(w.ata_required), + mst(w.mst), + max_grind(w.max_grind), + photon(w.photon), + special(w.special), + ata(w.ata), + stat_boost_entry_index(w.stat_boost_entry_index), + projectile(w.projectile), + trail1_x(w.trail1_x), + trail1_y(w.trail1_y), + trail2_x(w.trail2_x), + trail2_y(w.trail2_y), + color(w.color), + unknown_a1(w.unknown_a1) {} operator ItemParameterTable::Weapon() const { ItemParameterTable::Weapon ret; this->base.parse_into(ret); @@ -1222,6 +1358,13 @@ struct WeaponV3T : WeaponGCNTET { /* 26 */ uint8_t tech_boost_entry_index = 0; /* 27 */ uint8_t behavior_flags = 0; /* 28 */ + WeaponV3T() = default; + WeaponV3T(const ItemParameterTable::Weapon& w) + : WeaponGCNTET(w), + unknown_a4(w.unknown_a4), + unknown_a5(w.unknown_a5), + tech_boost_entry_index(w.tech_boost_entry_index), + behavior_flags(w.behavior_flags) {} operator ItemParameterTable::Weapon() const { ItemParameterTable::Weapon ret = this->WeaponGCNTET::operator ItemParameterTable::Weapon(); ret.unknown_a4 = this->unknown_a4; @@ -1260,6 +1403,32 @@ struct WeaponV4 { /* 2A */ uint8_t tech_boost_entry_index = 0; /* 2B */ uint8_t behavior_flags = 0; /* 2C */ + WeaponV4() = default; + WeaponV4(const ItemParameterTable::Weapon& w) + : base(w), + class_flags(w.class_flags), + atp_min(w.atp_min), + atp_max(w.atp_max), + atp_required(w.atp_required), + mst_required(w.mst_required), + ata_required(w.ata_required), + mst(w.mst), + max_grind(w.max_grind), + photon(w.photon), + special(w.special), + ata(w.ata), + stat_boost_entry_index(w.stat_boost_entry_index), + projectile(w.projectile), + trail1_x(w.trail1_x), + trail1_y(w.trail1_y), + trail2_x(w.trail2_x), + trail2_y(w.trail2_y), + color(w.color), + unknown_a1(w.unknown_a1), + unknown_a4(w.unknown_a4), + unknown_a5(w.unknown_a5), + tech_boost_entry_index(w.tech_boost_entry_index), + behavior_flags(w.behavior_flags) {} operator ItemParameterTable::Weapon() const { ItemParameterTable::Weapon ret; this->base.parse_into(ret); @@ -1308,6 +1477,22 @@ struct ArmorOrShieldT { /* 12 */ uint8_t dfp_range = 0; /* 13 */ uint8_t evp_range = 0; /* 14 */ + ArmorOrShieldT() = default; + ArmorOrShieldT(const ItemParameterTable::ArmorOrShield& as) + : base(as), + dfp(as.dfp), + evp(as.evp), + block_particle(as.block_particle), + block_effect(as.block_effect), + class_flags(as.class_flags), + required_level(as.required_level), + efr(as.efr), + eth(as.eth), + eic(as.eic), + edk(as.edk), + elt(as.elt), + dfp_range(as.dfp_range), + evp_range(as.evp_range) {} operator ItemParameterTable::ArmorOrShield() const { ItemParameterTable::ArmorOrShield ret; this->base.parse_into(ret); @@ -1341,6 +1526,13 @@ struct ArmorOrShieldFinalT : ArmorOrShieldT { /* 16 */ uint8_t flags_type = 0; /* 17 */ uint8_t unknown_a4 = 0; /* 18 */ + ArmorOrShieldFinalT() = default; + ArmorOrShieldFinalT(const ItemParameterTable::ArmorOrShield& as) + : ArmorOrShieldT(as), + stat_boost_entry_index(as.stat_boost_entry_index), + tech_boost_entry_index(as.tech_boost_entry_index), + flags_type(as.flags_type), + unknown_a4(as.unknown_a4) {} operator ItemParameterTable::ArmorOrShield() const { ItemParameterTable::ArmorOrShield ret = this->ArmorOrShieldT::operator ItemParameterTable::ArmorOrShield(); ret.stat_boost_entry_index = this->stat_boost_entry_index; @@ -1367,6 +1559,8 @@ struct UnitT { /* 04 */ U16T stat = 0; /* 06 */ U16T stat_amount = 0; /* 08 */ + UnitT() = default; + UnitT(const ItemParameterTable::Unit& u) : base(u), stat(u.stat), stat_amount(u.stat_amount) {} operator ItemParameterTable::Unit() const { ItemParameterTable::Unit ret; this->base.parse_into(ret); @@ -1385,8 +1579,10 @@ using UnitDCProtos = UnitT, false>; template struct UnitFinalT : UnitT { /* 08 */ S16T modifier_amount = 0; - /* 0A */ parray unused; + /* 0A */ parray unused = 0; /* 0C */ + UnitFinalT() = default; + UnitFinalT(const ItemParameterTable::Unit& u) : UnitT(u), modifier_amount(u.modifier_amount) {} operator ItemParameterTable::Unit() const { ItemParameterTable::Unit ret = this->UnitT::operator ItemParameterTable::Unit(); ret.modifier_amount = this->modifier_amount; @@ -1419,6 +1615,20 @@ struct MagT { /* 0E */ uint8_t on_death_flag = 0; /* 0F */ uint8_t on_boss_flag = 0; /* 10 */ + MagT() = default; + MagT(const ItemParameterTable::Mag& m) + : base(m), + feed_table(m.feed_table), + photon_blast(m.photon_blast), + activation(m.activation), + on_pb_full(m.on_pb_full), + on_low_hp(m.on_low_hp), + on_death(m.on_death), + on_boss(m.on_boss), + on_pb_full_flag(m.on_pb_full_flag), + on_low_hp_flag(m.on_low_hp_flag), + on_death_flag(m.on_death_flag), + on_boss_flag(m.on_boss_flag) {} operator ItemParameterTable::Mag() const { ItemParameterTable::Mag ret; this->base.parse_into(ret); @@ -1446,7 +1656,9 @@ using MagV1 = MagT, false>; template struct MagV2V3V4T : MagT { U16T class_flags = 0x00FF; - parray unused; + parray unused = 0; + MagV2V3V4T() = default; + MagV2V3V4T(const ItemParameterTable::Mag& m) : MagT(m), class_flags(m.class_flags) {} operator ItemParameterTable::Mag() const { ItemParameterTable::Mag ret = this->MagT::operator ItemParameterTable::Mag(); ret.class_flags = this->class_flags; @@ -1472,6 +1684,9 @@ struct ToolT { /* 08 */ S32T cost = 0; /* 0C */ U32T item_flags = 0; /* 10 */ + ToolT() = default; + ToolT(const ItemParameterTable::Tool& t) + : base(t), amount(t.amount), tech(t.tech), cost(t.cost), item_flags(t.item_flags) {} operator ItemParameterTable::Tool() const { ItemParameterTable::Tool ret; this->base.parse_into(ret); @@ -1501,6 +1716,8 @@ template struct SpecialT { U16T type = 0xFFFF; U16T amount = 0; + SpecialT() = default; + SpecialT(const ItemParameterTable::Special& t) : type(t.type), amount(t.amount) {} operator ItemParameterTable::Special() const { return {this->type, this->amount}; } @@ -1508,15 +1725,20 @@ struct SpecialT { template struct StatBoostT { - parray stats = 0; - parray, 2> amounts = 0; + uint8_t stat1 = 0; + uint8_t stat2 = 0; + U16T amount1 = 0; + U16T amount2 = 0; + StatBoostT() = default; + StatBoostT(const ItemParameterTable::StatBoost& sb) + : stat1(sb.stat1), stat2(sb.stat2), amount1(sb.amount1), amount2(sb.amount2) {} operator ItemParameterTable::StatBoost() const { - return {this->stats[0], this->amounts[0], this->stats[1], this->amounts[1]}; + return {this->stat1, this->amount1, this->stat2, this->amount2}; } } __packed_ws_be__(StatBoostT, 6); template -struct TechBoostEntryT { +struct TechBoostT { uint8_t tech_num1 = 0; uint8_t flags1 = 0; parray unused1; @@ -1529,6 +1751,17 @@ struct TechBoostEntryT { uint8_t flags3 = 0; parray unused3; F32T amount3 = 0.0f; + TechBoostT() = default; + TechBoostT(const ItemParameterTable::TechBoost& tb) + : tech_num1(tb.tech_num1), + flags1(tb.flags1), + amount1(tb.amount1), + tech_num2(tb.tech_num2), + flags2(tb.flags2), + amount2(tb.amount2), + tech_num3(tb.tech_num3), + flags3(tb.flags3), + amount3(tb.amount3) {} operator ItemParameterTable::TechBoost() const { return { this->tech_num1, @@ -1542,12 +1775,15 @@ struct TechBoostEntryT { this->amount3, }; } -} __packed_ws_be__(TechBoostEntryT, 0x18); +} __packed_ws_be__(TechBoostT, 0x18); struct NonWeaponSaleDivisorsDCProtos { uint8_t armor_divisor = 0; uint8_t shield_divisor = 0; uint8_t unit_divisor = 0; + NonWeaponSaleDivisorsDCProtos() = default; + NonWeaponSaleDivisorsDCProtos(const ItemParameterTable::NonWeaponSaleDivisors& sd) + : armor_divisor(sd.armor_divisor), shield_divisor(sd.shield_divisor), unit_divisor(sd.unit_divisor) {} operator ItemParameterTable::NonWeaponSaleDivisors() const { return { static_cast(this->armor_divisor), @@ -1563,6 +1799,12 @@ struct NonWeaponSaleDivisorsT { F32T shield_divisor = 0.0f; F32T unit_divisor = 0.0f; F32T mag_divisor = 0.0f; + NonWeaponSaleDivisorsT() = default; + NonWeaponSaleDivisorsT(const ItemParameterTable::NonWeaponSaleDivisors& sd) + : armor_divisor(sd.armor_divisor), + shield_divisor(sd.shield_divisor), + unit_divisor(sd.unit_divisor), + mag_divisor(sd.mag_divisor) {} operator ItemParameterTable::NonWeaponSaleDivisors() const { return {this->armor_divisor, this->shield_divisor, this->unit_divisor, this->mag_divisor}; } @@ -1572,6 +1814,8 @@ template struct ShieldEffectT { U32T sound_id; U32T unknown_a1; + ShieldEffectT() = default; + ShieldEffectT(const ItemParameterTable::ShieldEffect& se) : sound_id(se.sound_id), unknown_a1(se.unknown_a1) {} operator ItemParameterTable::ShieldEffect() const { return {this->sound_id, this->unknown_a1}; } @@ -1583,6 +1827,18 @@ struct PhotonColorEntryT { /* 04 */ parray, 4> unknown_a2; /* 14 */ parray, 4> unknown_a3; /* 24 */ + PhotonColorEntryT() = default; + PhotonColorEntryT(const ItemParameterTable::PhotonColorEntry pc) { + this->unknown_a1 = pc.unknown_a1; + this->unknown_a2[0] = pc.unknown_a2.x; + this->unknown_a2[1] = pc.unknown_a2.y; + this->unknown_a2[2] = pc.unknown_a2.z; + this->unknown_a2[3] = pc.unknown_a2.t; + this->unknown_a3[0] = pc.unknown_a3.x; + this->unknown_a3[1] = pc.unknown_a3.y; + this->unknown_a3[2] = pc.unknown_a3.z; + this->unknown_a3[3] = pc.unknown_a3.t; + } operator ItemParameterTable::PhotonColorEntry() const { ItemParameterTable::PhotonColorEntry ret; ret.unknown_a1 = this->unknown_a1; @@ -1602,6 +1858,8 @@ template struct UnknownA1T { U16T unknown_a1; U16T unknown_a2; + UnknownA1T() = default; + UnknownA1T(const ItemParameterTable::UnknownA1 a1) : unknown_a1(a1.unknown_a1), unknown_a2(a1.unknown_a2) {} operator ItemParameterTable::UnknownA1() const { return {this->unknown_a1, this->unknown_a2}; } @@ -1614,6 +1872,13 @@ struct WeaponRangeT { U32T unknown_a3; U32T unknown_a4; U32T unknown_a5; + WeaponRangeT() = default; + WeaponRangeT(const ItemParameterTable::WeaponRange& wr) + : unknown_a1(wr.unknown_a1), + unknown_a2(wr.unknown_a2), + unknown_a3(wr.unknown_a3), + unknown_a4(wr.unknown_a4), + unknown_a5(wr.unknown_a5) {} operator ItemParameterTable::WeaponRange() const { return {this->unknown_a1, this->unknown_a2, this->unknown_a3, this->unknown_a4, this->unknown_a5}; } @@ -1626,6 +1891,13 @@ struct WeaponEffectT { U32T sound_id2; U32T eff_value2; parray unknown_a5; + WeaponEffectT() = default; + WeaponEffectT(const ItemParameterTable::WeaponEffect& we) + : sound_id1(we.sound_id1), + eff_value1(we.eff_value1), + sound_id2(we.sound_id2), + eff_value2(we.eff_value2), + unknown_a5(we.unknown_a5) {} operator ItemParameterTable::WeaponEffect() const { return {this->sound_id1, this->eff_value1, this->sound_id2, this->eff_value2, this->unknown_a5}; } @@ -1638,42 +1910,9 @@ struct SoundRemapTableOffsetsT { U32T remaps_for_char_class_table; } __packed_ws_be__(SoundRemapTableOffsetsT, 0x0C); -/* The fields in the root structures are as follows. See the specializations of BinaryItemParameterTableT for the - * values of the hardcoded constants in each version (NumWeaponClasses, etc.). - * DCTE / 112K / V1 / V2 / GCTE / GCV3 / XBV3 / V4 - * 0013 / 0013 / 0013 / 0013 / ---- / ---- / ---- / ----- entry_count // Count of pointers in root struct; unused - * 2E1C / 2EB8 / 32E8 / 5AFC / 6F0C / F078 / F084 / 14884 weapon_table // -> {count, offset -> WeaponV*[count]}[NumWeaponClasses] - * 2D94 / 2E28 / 3258 / 5A5C / 6E4C / EF90 / EF9C / 1478C armor_table // -> {count, offset -> ArmorOrShieldV*[count]}][2] - * 2DA4 / 2E38 / 3268 / 5A6C / 6E5C / EFA0 / EFAC / 1479C unit_table // -> {count, offset -> UnitV*[count]} (last if out of range) - * 2DB4 / 2E48 / 3278 / 5A7C / 6E6C / EFB0 / EFBC / 147AC tool_table // -> {count, offset -> ToolV*[count]}[NumToolClasses] (last if out of range) - * 2DAC / 2E40 / 3270 / 5A74 / 6E64 / EFA8 / EFB4 / 147A4 mag_table // -> {count, offset -> MagV*[count]} - * 1F98 / 202C / 23C8 / 3DF8 / 47BC / B88C / B8A0 / 0F4B8 weapon_kind_table // -> uint8_t[NumWeaponClasses] - * 1994 / 1A28 / 1DB0 / 2E4C / 37A4 / A7FC / A7FC / 0DE7C photon_color_table // -> PhotonColorEntry[NumPhotonColors] - * 1C64 / 1CF8 / 2080 / 32CC / 3A74 / AACC / AACC / 0E194 weapon_range_table // -> WeaponRange[...] (indexed by data1_1, but also by RangedSpecial::weapon_range_index + a version-dependent constant, and also by the result of a vfunc call on some TItemWeapons) - * 1FBF / 2053 / 23F0 / 3E84 / 484C / B938 / B94C / 0F5A8 weapon_sale_divisor_table // -> uint8_t[NumWeaponClasses] on DC protos; float[NumWeaponClasses] on all other versions - * 1FE6 / 207A / 248C / 40A8 / 4A80 / BBCC / BBE0 / 0F83C non_weapon_sale_divisor_table // -> NonWeaponSaleDivisors - * 2F54 / 2FF0 / 3420 / 5F4C / 7384 / F608 / F614 / 1502C mag_feed_table // -> MagFeedResultsTable - * 22A9 / 233D / 275C / 4378 / 4D50 / BE9C / BEB0 / 0FB0C star_value_table // -> uint8_t[...] (indexed by .id from weapon, armor, etc.) - * 23EE / 2484 / 28A2 / ---- / ---- / ---- / ---- / ----- unknown_a1 // TODO - * 275E / 27F4 / 2C12 / 4540 / 4F72 / C100 / C114 / 0FE3C special_table // -> Special[NumSpecials] - * 2804 / 2898 / 2CB8 / 45E4 / 5018 / C1A4 / C1B8 / 0FEE0 weapon_effect_table // -> WeaponEffect[...] - * 1908 / 199C / ---- / ---- / ---- / ---- / ---- / ----- weapon_stat_boost_index_table // -> uint8_t[max(weapon.id : weapon_table) - (ItemStarsFirstID - 1)] - * 0668 / 0668 / ---- / ---- / ---- / ---- / ---- / ----- armor_stat_boost_index_table // -> uint8_t[armor_table[0].count] - * 030C / 030C / ---- / ---- / ---- / ---- / ---- / ----- shield_stat_boost_index_table // -> uint8_t[armor_table[1].count] - * 2CE4 / 2D78 / 3198 / 58DC / 68B8 / DE50 / DE64 / 1275C stat_boost_table // -> StatBoost[...] - * ---- / ---- / ---- / 5704 / 61B8 / D6E4 / D6F8 / 11C80 shield_effect_table // -> ShieldEffect[...] (indexed by data1[2]) - * ---- / ---- / ---- / ---- / 69D8 / DF88 / DF9C / 12894 max_tech_level_table // -> MaxTechniqueLevels - * ---- / ---- / ---- / ---- / 737C / F5D0 / F5DC / 14FF4 combination_table // -> {count, offset -> ItemCombination[count]} - * ---- / ---- / ---- / ---- / 68B0 / DE48 / DE5C / 12754 sound_remap_table // -> {count, offset -> {sound_id, by_rt_index_offset -> uint32_t[SoundRemapRTTableSize], by_char_class_offset -> uint32_t[12]}} - * ---- / ---- / ---- / ---- / 6B1C / EB8C / EBA0 / 14278 tech_boost_table // -> TechBoostEntry[...][3] - * ---- / ---- / ---- / ---- / ---- / F5F0 / F5FC / 15014 unwrap_table // -> {count, offset -> {count, offset -> EventItem[count]}[count] - * ---- / ---- / ---- / ---- / ---- / F5F8 / F604 / 1501C unsealable_table // -> {count, offset -> UnsealableItem[count]} - * ---- / ---- / ---- / ---- / ---- / F600 / F60C / 15024 ranged_special_table // -> {count, offset -> RangedSpecial[count]} - */ - struct RootDCProtos { /* ## / DCTE / 112K */ - /* 00 / 0013 / 0013 */ le_uint32_t entry_count; + /* 00 / 0013 / 0013 */ le_uint32_t entry_count = 0x13; /* 04 / 2E1C / 2EB8 */ le_uint32_t weapon_table; /* 08 / 2D94 / 2E28 */ le_uint32_t armor_table; /* 0C / 2DA4 / 2E38 */ le_uint32_t unit_table; @@ -1697,98 +1936,154 @@ struct RootDCProtos { struct RootV1 { /* ## / DCV1 */ - /* 00 / 0013 */ le_uint32_t entry_count; - /* 04 / 32E8 */ le_uint32_t weapon_table; - /* 08 / 3258 */ le_uint32_t armor_table; - /* 0C / 3268 */ le_uint32_t unit_table; - /* 10 / 3278 */ le_uint32_t tool_table; - /* 14 / 3270 */ le_uint32_t mag_table; - /* 18 / 23C8 */ le_uint32_t weapon_kind_table; - /* 1C / 1DB0 */ le_uint32_t photon_color_table; - /* 20 / 2080 */ le_uint32_t weapon_range_table; - /* 24 / 23F0 */ le_uint32_t weapon_sale_divisor_table; - /* 28 / 248C */ le_uint32_t non_weapon_sale_divisor_table; - /* 2C / 3420 */ le_uint32_t mag_feed_table; - /* 30 / 275C */ le_uint32_t star_value_table; - /* 34 / 28A2 */ le_uint32_t unknown_a1; - /* 38 / 2C12 */ le_uint32_t special_table; - /* 3C / 2CB8 */ le_uint32_t weapon_effect_table; - /* 40 / 3198 */ le_uint32_t stat_boost_table; + /* 00 / 0013 */ le_uint32_t entry_count = 0x13; + /* 04 / 32E8 */ le_uint32_t weapon_table = 0xFFFFFFFF; + /* 08 / 3258 */ le_uint32_t armor_table = 0xFFFFFFFF; + /* 0C / 3268 */ le_uint32_t unit_table = 0xFFFFFFFF; + /* 10 / 3278 */ le_uint32_t tool_table = 0xFFFFFFFF; + /* 14 / 3270 */ le_uint32_t mag_table = 0xFFFFFFFF; + /* 18 / 23C8 */ le_uint32_t weapon_kind_table = 0xFFFFFFFF; + /* 1C / 1DB0 */ le_uint32_t photon_color_table = 0xFFFFFFFF; + /* 20 / 2080 */ le_uint32_t weapon_range_table = 0xFFFFFFFF; + /* 24 / 23F0 */ le_uint32_t weapon_sale_divisor_table = 0xFFFFFFFF; + /* 28 / 248C */ le_uint32_t non_weapon_sale_divisor_table = 0xFFFFFFFF; + /* 2C / 3420 */ le_uint32_t mag_feed_table = 0xFFFFFFFF; + /* 30 / 275C */ le_uint32_t star_value_table = 0xFFFFFFFF; + /* 34 / 28A2 */ le_uint32_t unknown_a1 = 0xFFFFFFFF; + /* 38 / 2C12 */ le_uint32_t special_table = 0xFFFFFFFF; + /* 3C / 2CB8 */ le_uint32_t weapon_effect_table = 0xFFFFFFFF; + /* 40 / 3198 */ le_uint32_t stat_boost_table = 0xFFFFFFFF; } __packed_ws__(RootV1, 0x44); struct RootV2 { /* ## / DCV2 */ - /* 00 / 0013 */ le_uint32_t entry_count; - /* 04 / 5AFC */ le_uint32_t weapon_table; - /* 08 / 5A5C */ le_uint32_t armor_table; - /* 0C / 5A6C */ le_uint32_t unit_table; - /* 10 / 5A7C */ le_uint32_t tool_table; - /* 14 / 5A74 */ le_uint32_t mag_table; - /* 18 / 3DF8 */ le_uint32_t weapon_kind_table; - /* 1C / 2E4C */ le_uint32_t photon_color_table; - /* 20 / 32CC */ le_uint32_t weapon_range_table; - /* 24 / 3E84 */ le_uint32_t weapon_sale_divisor_table; - /* 28 / 40A8 */ le_uint32_t non_weapon_sale_divisor_table; - /* 2C / 5F4C */ le_uint32_t mag_feed_table; - /* 30 / 4378 */ le_uint32_t star_value_table; - /* 34 / 4540 */ le_uint32_t special_table; - /* 38 / 45E4 */ le_uint32_t weapon_effect_table; - /* 3C / 58DC */ le_uint32_t stat_boost_table; - /* 40 / 5704 */ le_uint32_t shield_effect_table; + /* 00 / 0013 */ le_uint32_t entry_count = 0x13; + /* 04 / 5AFC */ le_uint32_t weapon_table = 0xFFFFFFFF; + /* 08 / 5A5C */ le_uint32_t armor_table = 0xFFFFFFFF; + /* 0C / 5A6C */ le_uint32_t unit_table = 0xFFFFFFFF; + /* 10 / 5A7C */ le_uint32_t tool_table = 0xFFFFFFFF; + /* 14 / 5A74 */ le_uint32_t mag_table = 0xFFFFFFFF; + /* 18 / 3DF8 */ le_uint32_t weapon_kind_table = 0xFFFFFFFF; + /* 1C / 2E4C */ le_uint32_t photon_color_table = 0xFFFFFFFF; + /* 20 / 32CC */ le_uint32_t weapon_range_table = 0xFFFFFFFF; + /* 24 / 3E84 */ le_uint32_t weapon_sale_divisor_table = 0xFFFFFFFF; + /* 28 / 40A8 */ le_uint32_t non_weapon_sale_divisor_table = 0xFFFFFFFF; + /* 2C / 5F4C */ le_uint32_t mag_feed_table = 0xFFFFFFFF; + /* 30 / 4378 */ le_uint32_t star_value_table = 0xFFFFFFFF; + /* 34 / 4540 */ le_uint32_t special_table = 0xFFFFFFFF; + /* 38 / 45E4 */ le_uint32_t weapon_effect_table = 0xFFFFFFFF; + /* 3C / 58DC */ le_uint32_t stat_boost_table = 0xFFFFFFFF; + /* 40 / 5704 */ le_uint32_t shield_effect_table = 0xFFFFFFFF; } __packed_ws__(RootV2, 0x44); struct RootGCNTE { /* ## / GCTE */ - /* 00 / 6F0C */ be_uint32_t weapon_table; - /* 04 / 6E4C */ be_uint32_t armor_table; - /* 08 / 6E5C */ be_uint32_t unit_table; - /* 0C / 6E6C */ be_uint32_t tool_table; - /* 10 / 6E64 */ be_uint32_t mag_table; - /* 14 / 47BC */ be_uint32_t weapon_kind_table; - /* 18 / 37A4 */ be_uint32_t photon_color_table; - /* 1C / 3A74 */ be_uint32_t weapon_range_table; - /* 20 / 484C */ be_uint32_t weapon_sale_divisor_table; - /* 24 / 4A80 */ be_uint32_t non_weapon_sale_divisor_table; - /* 28 / 7384 */ be_uint32_t mag_feed_table; - /* 2C / 4D50 */ be_uint32_t star_value_table; - /* 30 / 4F72 */ be_uint32_t special_table; - /* 34 / 5018 */ be_uint32_t weapon_effect_table; - /* 38 / 68B8 */ be_uint32_t stat_boost_table; - /* 3C / 61B8 */ be_uint32_t shield_effect_table; - /* 40 / 69D8 */ be_uint32_t max_tech_level_table; - /* 44 / 737C */ be_uint32_t combination_table; - /* 48 / 68B0 */ be_uint32_t sound_remap_table; - /* 4C / 6B1C */ be_uint32_t tech_boost_table; + /* 00 / 6F0C */ be_uint32_t weapon_table = 0xFFFFFFFF; + /* 04 / 6E4C */ be_uint32_t armor_table = 0xFFFFFFFF; + /* 08 / 6E5C */ be_uint32_t unit_table = 0xFFFFFFFF; + /* 0C / 6E6C */ be_uint32_t tool_table = 0xFFFFFFFF; + /* 10 / 6E64 */ be_uint32_t mag_table = 0xFFFFFFFF; + /* 14 / 47BC */ be_uint32_t weapon_kind_table = 0xFFFFFFFF; + /* 18 / 37A4 */ be_uint32_t photon_color_table = 0xFFFFFFFF; + /* 1C / 3A74 */ be_uint32_t weapon_range_table = 0xFFFFFFFF; + /* 20 / 484C */ be_uint32_t weapon_sale_divisor_table = 0xFFFFFFFF; + /* 24 / 4A80 */ be_uint32_t non_weapon_sale_divisor_table = 0xFFFFFFFF; + /* 28 / 7384 */ be_uint32_t mag_feed_table = 0xFFFFFFFF; + /* 2C / 4D50 */ be_uint32_t star_value_table = 0xFFFFFFFF; + /* 30 / 4F72 */ be_uint32_t special_table = 0xFFFFFFFF; + /* 34 / 5018 */ be_uint32_t weapon_effect_table = 0xFFFFFFFF; + /* 38 / 68B8 */ be_uint32_t stat_boost_table = 0xFFFFFFFF; + /* 3C / 61B8 */ be_uint32_t shield_effect_table = 0xFFFFFFFF; + /* 40 / 69D8 */ be_uint32_t max_tech_level_table = 0xFFFFFFFF; + /* 44 / 737C */ be_uint32_t combination_table = 0xFFFFFFFF; + /* 48 / 68B0 */ be_uint32_t sound_remap_table = 0xFFFFFFFF; + /* 4C / 6B1C */ be_uint32_t tech_boost_table = 0xFFFFFFFF; } __packed_ws__(RootGCNTE, 0x50); template struct RootV3V4T { /* ## / GCV3 / XBV3 / BBV4 */ - /* 00 / F078 / F084 / 14884 */ U32T weapon_table; - /* 04 / EF90 / EF9C / 1478C */ U32T armor_table; - /* 08 / EFA0 / EFAC / 1479C */ U32T unit_table; - /* 0C / EFB0 / EFBC / 147AC */ U32T tool_table; - /* 10 / EFA8 / EFB4 / 147A4 */ U32T mag_table; - /* 14 / B88C / B8A0 / 0F4B8 */ U32T weapon_kind_table; - /* 18 / A7FC / A7FC / 0DE7C */ U32T photon_color_table; - /* 1C / AACC / AACC / 0E194 */ U32T weapon_range_table; - /* 20 / B938 / B94C / 0F5A8 */ U32T weapon_sale_divisor_table; - /* 24 / BBCC / BBE0 / 0F83C */ U32T non_weapon_sale_divisor_table; - /* 28 / F608 / F614 / 1502C */ U32T mag_feed_table; - /* 2C / BE9C / BEB0 / 0FB0C */ U32T star_value_table; - /* 30 / C100 / C114 / 0FE3C */ U32T special_table; - /* 34 / C1A4 / C1B8 / 0FEE0 */ U32T weapon_effect_table; - /* 38 / DE50 / DE64 / 1275C */ U32T stat_boost_table; - /* 3C / D6E4 / D6F8 / 11C80 */ U32T shield_effect_table; - /* 40 / DF88 / DF9C / 12894 */ U32T max_tech_level_table; - /* 44 / F5D0 / F5DC / 14FF4 */ U32T combination_table; - /* 48 / DE48 / DE5C / 12754 */ U32T sound_remap_table; - /* 4C / EB8C / EBA0 / 14278 */ U32T tech_boost_table; - /* 50 / F5F0 / F5FC / 15014 */ U32T unwrap_table; - /* 54 / F5F8 / F604 / 1501C */ U32T unsealable_table; - /* 58 / F600 / F60C / 15024 */ U32T ranged_special_table; + /* 00 / F078 / F084 / 14884 */ U32T weapon_table = 0xFFFFFFFF; + /* 04 / EF90 / EF9C / 1478C */ U32T armor_table = 0xFFFFFFFF; + /* 08 / EFA0 / EFAC / 1479C */ U32T unit_table = 0xFFFFFFFF; + /* 0C / EFB0 / EFBC / 147AC */ U32T tool_table = 0xFFFFFFFF; + /* 10 / EFA8 / EFB4 / 147A4 */ U32T mag_table = 0xFFFFFFFF; + /* 14 / B88C / B8A0 / 0F4B8 */ U32T weapon_kind_table = 0xFFFFFFFF; + /* 18 / A7FC / A7FC / 0DE7C */ U32T photon_color_table = 0xFFFFFFFF; + /* 1C / AACC / AACC / 0E194 */ U32T weapon_range_table = 0xFFFFFFFF; + /* 20 / B938 / B94C / 0F5A8 */ U32T weapon_sale_divisor_table = 0xFFFFFFFF; + /* 24 / BBCC / BBE0 / 0F83C */ U32T non_weapon_sale_divisor_table = 0xFFFFFFFF; + /* 28 / F608 / F614 / 1502C */ U32T mag_feed_table = 0xFFFFFFFF; + /* 2C / BE9C / BEB0 / 0FB0C */ U32T star_value_table = 0xFFFFFFFF; + /* 30 / C100 / C114 / 0FE3C */ U32T special_table = 0xFFFFFFFF; + /* 34 / C1A4 / C1B8 / 0FEE0 */ U32T weapon_effect_table = 0xFFFFFFFF; + /* 38 / DE50 / DE64 / 1275C */ U32T stat_boost_table = 0xFFFFFFFF; + /* 3C / D6E4 / D6F8 / 11C80 */ U32T shield_effect_table = 0xFFFFFFFF; + /* 40 / DF88 / DF9C / 12894 */ U32T max_tech_level_table = 0xFFFFFFFF; + /* 44 / F5D0 / F5DC / 14FF4 */ U32T combination_table = 0xFFFFFFFF; + /* 48 / DE48 / DE5C / 12754 */ U32T sound_remap_table = 0xFFFFFFFF; + /* 4C / EB8C / EBA0 / 14278 */ U32T tech_boost_table = 0xFFFFFFFF; + /* 50 / F5F0 / F5FC / 15014 */ U32T unwrap_table = 0xFFFFFFFF; + /* 54 / F5F8 / F604 / 1501C */ U32T unsealable_table = 0xFFFFFFFF; + /* 58 / F600 / F60C / 15024 */ U32T ranged_special_table = 0xFFFFFFFF; } __packed_ws_be__(RootV3V4T, 0x5C); +// All versions after v1 have a header before the actual data, which appears to be entirely unused by the game. +// TODO: Figure out what these bytes mean. +// V2: 29274435 3A44807F +// GC_NTE: FFFFFFFF 00000001 00010000 40400000 40C00000 FFFF0008 29274467 6C76807F 090C0D0F F0000000 00000003 00C80078 C8000000 00FA00C0 0000010C 00D20000 +// GC_V3: FFFFFFFF 00000001 00010000 40400000 40C00000 FFFF0008 29274467 6C768040 3F090C0D 0FF00000 00000003 00C80078 C8000000 010A00C7 0000011F 00DC0000 +// XB_V3: FFFFFFFF 01000000 00000100 00004040 0000C040 FFFF0800 29274467 6C768040 3F090C0D 0FF00000 03000000 C8007800 C8000000 0A01C700 00001F01 DC000000 +// BB_V4: FFFFFFFF 01000000 00000100 00004040 0000C040 FFFF0800 29274467 6C768040 3F090C0D 0FF00000 03000000 C8007800 C8000000 62010F01 00007A01 27010000 + +struct EmptyHeader {}; + +struct HeaderV2 { + le_uint32_t unknown_a1 = 0x35442729; + le_uint32_t unknown_a2 = 0x7F80443A; +} __packed_ws__(HeaderV2, 0x08); + +struct HeaderGCNTE { + /* 00 */ be_uint32_t unknown_a1 = 0xFFFFFFFF; + /* 04 */ be_uint32_t unknown_a2 = 0x00000001; + /* 08 */ be_uint32_t unknown_a3 = 0x00010000; + /* 0C */ be_float unknown_a4 = 3.0; + /* 10 */ be_float unknown_a5 = 6.0; + /* 14 */ be_uint16_t unknown_a6 = 0xFFFF; + /* 16 */ be_uint16_t unknown_a7 = 0x0008; + /* 18 */ parray unknown_a8 = {0x29, 0x27, 0x44, 0x67, 0x6C, 0x76, 0x80, 0x7F, 0x09, 0x0C, 0x0D, 0x0F, 0xF0, 0x00, 0x00, 0x00}; + /* 28 */ be_uint32_t unknown_a9 = 0x00000003; + /* 2C */ be_uint16_t unknown_a10 = 0x00C8; + /* 2E */ be_uint16_t unknown_a11 = 0x0078; + /* 30 */ parray unknown_a12 = {0xC8, 0x00, 0x00, 0x00}; + /* 34 */ parray unknown_a13 = {0x00FA, 0x00C0, 0x0000, 0x010C, 0x00D2, 0x0000}; +} __packed_ws__(HeaderGCNTE, 0x40); + +template +struct HeaderV3V4Base { + /* 00 */ U32T unknown_a1 = 0xFFFFFFFF; + /* 04 */ U32T unknown_a2 = 0x00000001; + /* 08 */ U32T unknown_a3 = 0x00010000; + /* 0C */ F32T unknown_a4 = 3.0; + /* 10 */ F32T unknown_a5 = 6.0; + /* 14 */ U16T unknown_a6 = 0xFFFF; + /* 16 */ U16T unknown_a7 = 0x0008; + /* 18 */ parray unknown_a8 = {0x29, 0x27, 0x44, 0x67, 0x6C, 0x76, 0x80, 0x40, 0x3F, 0x09, 0x0C, 0x0D, 0x0F, 0xF0, 0x00, 0x00}; + /* 28 */ U32T unknown_a9 = 0x00000003; + /* 2C */ U16T unknown_a10 = 0x00C8; + /* 2E */ U16T unknown_a11 = 0x0078; + /* 30 */ parray unknown_a12 = {0xC8, 0x00, 0x00, 0x00}; +} __packed_ws_be__(HeaderV3V4Base, 0x34); + +template +struct HeaderV3 : HeaderV3V4Base { + /* 34 */ parray, 6> unknown_a13 = {0x010A, 0x00C7, 0x0000, 0x011F, 0x00DC, 0x0000}; +} __packed_ws_be__(HeaderV3, 0x40); + +struct HeaderV4 : HeaderV3V4Base { + /* 34 */ parray unknown_a13 = {0x0162, 0x010F, 0x0000, 0x017A, 0x0127, 0x0000}; +} __packed_ws_be__(HeaderV3, 0x40); + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Reader implementation @@ -2006,6 +2301,28 @@ bool ItemParameterTable::is_unsealable_item(const ItemData& item) const { return this->is_unsealable_item(item.data1[0], item.data1[1], item.data1[2]); } +const std::map>& ItemParameterTable::item_combinations_index() const { + if (!this->item_combination_index.has_value()) { + auto& ret = this->item_combination_index.emplace(); + for (size_t z = 0; z < this->num_item_combinations(); z++) { + const auto& combo = this->get_item_combination(z); + ret[item_code_to_u32(combo.used_item)].emplace_back(combo); + } + } + return *this->item_combination_index; +} + +const std::vector& ItemParameterTable::all_combinations_for_used_item( + const ItemData& used_item) const { + try { + return this->item_combinations_index().at(item_code_to_u32( + used_item.data1[0], used_item.data1[1], used_item.data1[2])); + } catch (const out_of_range&) { + static const vector ret; + return ret; + } +} + const ItemParameterTable::ItemCombination& ItemParameterTable::get_item_combination( const ItemData& used_item, const ItemData& equipped_item) const { for (const auto& def : this->all_combinations_for_used_item(used_item)) { @@ -2018,17 +2335,6 @@ const ItemParameterTable::ItemCombination& ItemParameterTable::get_item_combinat throw out_of_range("no item combination applies"); } -const std::vector& ItemParameterTable::all_combinations_for_used_item( - const ItemData& used_item) const { - try { - return this->all_item_combinations().at(item_code_to_u32( - used_item.data1[0], used_item.data1[1], used_item.data1[2])); - } catch (const out_of_range&) { - static const vector ret; - return ret; - } -} - size_t ItemParameterTable::price_for_item(const ItemData& item) const { switch (item.data1[0]) { case 0: { @@ -2106,20 +2412,18 @@ size_t ItemParameterTable::price_for_item(const ItemData& item) const { } template < + typename HeaderT, typename RootT, typename WeaponT, - size_t NumWeaponClasses, typename ArmorOrShieldT, typename UnitT, typename ToolT, - size_t NumToolClasses, typename MagT, + bool HasImplicitPlaceholders, size_t ItemStarsFirstID, - size_t ItemStarsLastID, size_t SpecialStarsBeginIndex, - size_t NumSpecials, - size_t NumPhotonColors, size_t SoundRemapRTTableSize, + bool HasServerHeader, bool BE> class BinaryItemParameterTableT : public ItemParameterTable { public: @@ -2137,7 +2441,7 @@ public: template const T& indirect_lookup_2d(size_t base_offset, size_t co_index, size_t item_index) const { const auto& co = this->r.pget>(base_offset + sizeof(ArrayRefT) * co_index); - if (item_index >= co.count) { + if (item_index >= (co.count + HasImplicitPlaceholders)) { throw out_of_range("2-D array index out of range"); } return this->r.pget(co.offset + sizeof(T) * item_index); @@ -2161,18 +2465,18 @@ public: } virtual size_t num_weapon_classes() const { - return NumWeaponClasses; + return this->get_data_array_count>(this->root->weapon_table); } virtual size_t num_weapons_in_class(uint8_t data1_1) const { - if (data1_1 >= NumWeaponClasses) { + if (data1_1 >= this->num_weapon_classes()) { throw out_of_range("weapon ID out of range"); } return this->indirect_lookup_2d_count(this->root->weapon_table, data1_1); } virtual const Weapon& get_weapon(uint8_t data1_1, uint8_t data1_2) const { - if (data1_1 >= NumWeaponClasses) { + if (data1_1 >= this->num_weapon_classes()) { throw out_of_range("weapon ID out of range"); } uint16_t key = (data1_1 << 8) | data1_2; @@ -2188,7 +2492,7 @@ public: if ((data1_1 < 1) || (data1_1 > 2)) { throw out_of_range("armor/shield class ID out of range"); } - return this->indirect_lookup_2d_count(this->root->armor_table, data1_1 - 1); + return this->indirect_lookup_2d_count(this->root->armor_table, data1_1 - 1) + HasImplicitPlaceholders; } virtual const ArmorOrShield& get_armor_or_shield(uint8_t data1_1, uint8_t data1_2) const { @@ -2200,7 +2504,7 @@ public: } virtual size_t num_units() const { - return this->indirect_lookup_2d_count(this->root->unit_table, 0); + return this->indirect_lookup_2d_count(this->root->unit_table, 0) + HasImplicitPlaceholders; } virtual const Unit& get_unit(uint8_t data1_2) const { @@ -2208,18 +2512,18 @@ public: } virtual size_t num_tool_classes() const { - return NumToolClasses; + return this->get_data_array_count>(this->root->tool_table); } virtual size_t num_tools_in_class(uint8_t data1_1) const { - if (data1_1 >= NumToolClasses) { + if (data1_1 >= this->num_tool_classes()) { throw out_of_range("tool class ID out of range"); } return this->indirect_lookup_2d_count(this->root->tool_table, data1_1); } virtual const Tool& get_tool(uint8_t data1_1, uint8_t data1_2) const { - if (data1_1 >= NumToolClasses) { + if (data1_1 >= this->num_tool_classes()) { throw out_of_range("tool class ID out of range"); } uint16_t key = (data1_1 << 8) | data1_2; @@ -2232,8 +2536,9 @@ public: } virtual std::pair find_tool_by_id(uint32_t id) const { - const auto* cos = &this->r.pget>(this->root->tool_table, NumToolClasses * sizeof(ArrayRefT)); - for (size_t z = 0; z < NumToolClasses; z++) { + const auto* cos = &this->r.pget>( + this->root->tool_table, this->num_tool_classes() * sizeof(ArrayRefT)); + for (size_t z = 0; z < this->num_tool_classes(); z++) { const auto& co = cos[z]; const auto* defs = &this->r.pget(co.offset, sizeof(ToolT) * co.count); for (size_t y = 0; y < co.count; y++) { @@ -2246,25 +2551,26 @@ public: } virtual size_t num_mags() const { - return this->indirect_lookup_2d_count(this->root->mag_table, 0); + return this->indirect_lookup_2d_count(this->root->mag_table, 0) + HasImplicitPlaceholders; } virtual const Mag& get_mag(uint8_t data1_1) const { return this->add_to_vector_cache_2d_indirect(this->mags, this->root->mag_table, 0, data1_1); } + virtual size_t num_weapon_kinds() const { + return this->get_data_array_count(this->root->weapon_kind_table); + } + virtual uint8_t get_weapon_kind(uint8_t data1_1) const { - return (data1_1 < NumWeaponClasses) ? this->r.pget_u8(this->root->weapon_kind_table + data1_1) : 0x00; + return (data1_1 < this->num_weapon_kinds()) ? this->r.pget_u8(this->root->weapon_kind_table + data1_1) : 0x00; } virtual size_t num_photon_colors() const { - return NumPhotonColors; + return this->get_data_array_count>(this->root->photon_color_table); } virtual const PhotonColorEntry& get_photon_color(size_t index) const { - if (index >= NumPhotonColors) { - throw std::out_of_range("invalid photon color index"); - } return this->add_to_vector_cache>(this->photon_colors, this->root->photon_color_table, index); } @@ -2276,9 +2582,17 @@ public: return this->add_to_vector_cache>(this->weapon_ranges, this->root->weapon_range_table, index); } + virtual size_t num_weapon_sale_divisors() const { + if constexpr (requires { this->root->weapon_integral_sale_divisor_table; }) { + return this->get_data_array_count(this->root->weapon_integral_sale_divisor_table); + } else { + return this->get_data_array_count>(this->root->weapon_sale_divisor_table); + } + } + virtual float get_sale_divisor(uint8_t data1_0, uint8_t data1_1) const { if (data1_0 == 0) { - if (data1_1 >= NumWeaponClasses) { + if (data1_1 >= this->num_weapon_sale_divisors()) { return 0.0f; } if constexpr (requires { this->root->weapon_sale_divisor_table; }) { @@ -2332,7 +2646,8 @@ public: } virtual std::pair get_star_value_index_range() const { - return std::make_pair(ItemStarsFirstID, ItemStarsLastID); + return std::make_pair( + ItemStarsFirstID, this->get_data_array_count(this->root->star_value_table) + ItemStarsFirstID); } virtual uint32_t get_special_stars_base_index() const { @@ -2340,8 +2655,9 @@ public: } virtual uint8_t get_item_stars(uint32_t id) const { - return ((id >= ItemStarsFirstID) && (id < ItemStarsLastID)) - ? this->r.pget_u8(this->root->star_value_table + id - ItemStarsFirstID) + auto range = this->get_star_value_index_range(); + return ((id >= range.first) && (id < range.second)) + ? this->r.pget_u8(this->root->star_value_table + id - range.first) : 0; } @@ -2358,12 +2674,12 @@ public: } virtual size_t num_specials() const { - return NumSpecials; + return this->get_data_array_count>(this->root->special_table); } virtual const Special& get_special(uint8_t special) const { special &= 0x3F; - if (special >= NumSpecials) { + if (special >= this->num_specials()) { throw out_of_range("invalid special index"); } while (this->specials.size() <= special) { @@ -2471,31 +2787,34 @@ public: } } - virtual const std::map>& all_item_combinations() const { + virtual size_t num_item_combinations() const { if constexpr (requires { this->root->combination_table; }) { - if (!this->item_combination_index.has_value()) { - auto& ret = this->item_combination_index.emplace(); - const auto& co = this->r.pget>(this->root->combination_table); - const auto* defs = &this->r.pget(co.offset, co.count * sizeof(ItemCombination)); - for (size_t z = 0; z < co.count; z++) { - ret[item_code_to_u32(defs[z].used_item)].emplace_back(defs[z]); - } - } - return *this->item_combination_index; + return this->r.pget>(this->root->combination_table).count; } else { - static const std::map> empty_map{}; - return empty_map; + return 0; } } - virtual const std::unordered_map& get_all_sound_remaps() const { + virtual const ItemCombination& get_item_combination(size_t index) const { + if constexpr (requires { this->root->combination_table; }) { + const auto& co = this->r.pget>(this->root->combination_table); + if (index >= co.count) { + throw std::logic_error("Item combination index out of range"); + } + return this->r.pget(co.offset + index * sizeof(ItemCombination)); + } else { + throw std::logic_error("Item combinations not available"); + } + } + + virtual const std::vector& get_all_sound_remaps() const { if constexpr (requires { this->root->sound_remap_table; }) { if (!this->sound_remaps.has_value()) { auto& ret = this->sound_remaps.emplace(); const auto& co = this->r.pget>(this->root->sound_remap_table); const auto* entries = this->r.pget_array>(co.offset, co.count); for (size_t z = 0; z < co.count; z++) { - auto& remaps = ret.emplace(entries[z].sound_id, SoundRemaps{}).first->second; + auto& remaps = ret.emplace_back(); remaps.sound_id = entries[z].sound_id; auto sub_r = r.sub(entries[z].remaps_for_rt_index_table, SoundRemapRTTableSize * sizeof(U32T)); for (size_t z = 0; z < SoundRemapRTTableSize; z++) { @@ -2513,7 +2832,7 @@ public: virtual size_t num_tech_boosts() const { if constexpr (requires { this->root->tech_boost_table; }) { - return this->get_data_array_count>(this->root->tech_boost_table); + return this->get_data_array_count>(this->root->tech_boost_table); } else { return 0; } @@ -2521,7 +2840,7 @@ public: virtual const TechBoost& get_tech_boost(size_t index) const { if constexpr (requires { this->root->tech_boost_table; }) { - return this->add_to_vector_cache>(this->tech_boosts, this->root->tech_boost_table, index); + return this->add_to_vector_cache>(this->tech_boosts, this->root->tech_boost_table, index); } else { throw std::logic_error("tech boost table not available"); } @@ -2620,6 +2939,332 @@ public: return this->get_data_range_size(start_offset) / sizeof(T); } + static std::string serialize(const ItemParameterTable& pmt) { + set relocations; + RootT root; + phosg::StringWriter w; + + if constexpr (!std::is_same_v) { + w.put(HeaderT()); + } + + auto align = [&w](size_t alignment) -> void { + while (w.size() & (alignment - 1)) { + w.put_u8(0); + } + }; + auto write_ref = [&w, &relocations](const ArrayRefT& ref) -> void { + w.put>(ref); + relocations.emplace(w.size() - 4); + }; + + if constexpr (requires { root.entry_count; }) { + root.entry_count = 0x13; + } + + align(4); + ArrayRefT shields_ref{pmt.num_armors_or_shields_in_class(2) - HasImplicitPlaceholders, w.size()}; + for (size_t data1_2 = 0; data1_2 < (shields_ref.count + HasImplicitPlaceholders); data1_2++) { + w.put(pmt.get_armor_or_shield(2, data1_2)); + } + if constexpr (requires { root.shield_stat_boost_index_table; }) { + root.shield_stat_boost_index_table = w.size(); + w.write(pmt.get_shield_stat_boost_index_table()); + } + + align(4); + ArrayRefT armors_ref{pmt.num_armors_or_shields_in_class(1) - HasImplicitPlaceholders, w.size()}; + for (size_t data1_2 = 0; data1_2 < (armors_ref.count + HasImplicitPlaceholders); data1_2++) { + w.put(pmt.get_armor_or_shield(1, data1_2)); + } + if constexpr (requires { root.armor_stat_boost_index_table; }) { + root.armor_stat_boost_index_table = w.size(); + w.write(pmt.get_armor_stat_boost_index_table()); + } + + align(4); + ArrayRefT units_ref{pmt.num_units() - HasImplicitPlaceholders, w.size()}; + for (size_t data1_2 = 0; data1_2 < (units_ref.count + HasImplicitPlaceholders); data1_2++) { + w.put(pmt.get_unit(data1_2)); + } + + align(4); + ArrayRefT mags_ref{pmt.num_mags() - HasImplicitPlaceholders, w.size()}; + for (size_t data1_2 = 0; data1_2 < (mags_ref.count + HasImplicitPlaceholders); data1_2++) { + w.put(pmt.get_mag(data1_2)); + } + + align(4); + std::vector> tool_refs; + for (size_t data1_1 = 0; data1_1 < pmt.num_tool_classes(); data1_1++) { + auto& ref = tool_refs.emplace_back(ArrayRefT{pmt.num_tools_in_class(data1_1), w.size()}); + for (size_t data1_2 = 0; data1_2 < ref.count; data1_2++) { + w.put(pmt.get_tool(data1_1, data1_2)); + } + } + + align(4); + std::vector> weapon_refs; + for (size_t data1_1 = 0; data1_1 < pmt.num_weapon_classes(); data1_1++) { + auto& ref = weapon_refs.emplace_back(ArrayRefT{pmt.num_weapons_in_class(data1_1), w.size()}); + for (size_t data1_2 = 0; data1_2 < ref.count; data1_2++) { + w.put(pmt.get_weapon(data1_1, data1_2)); + } + } + if constexpr (requires { root.weapon_stat_boost_index_table; }) { + root.weapon_stat_boost_index_table = w.size(); + w.write(pmt.get_weapon_stat_boost_index_table()); + } + + align(4); + root.photon_color_table = w.size(); + for (size_t z = 0; z < pmt.num_photon_colors(); z++) { + w.put>(pmt.get_photon_color(z)); + } + + align(4); + root.weapon_range_table = w.size(); + for (size_t z = 0; z < pmt.num_weapon_ranges(); z++) { + w.put>(pmt.get_weapon_range(z)); + } + + root.weapon_kind_table = w.size(); + for (size_t z = 0; z < pmt.num_weapon_classes(); z++) { + w.put_u8(pmt.get_weapon_kind(z)); + } + + if constexpr (requires { root.weapon_integral_sale_divisor_table; }) { + root.weapon_integral_sale_divisor_table = w.size(); + for (size_t z = 0; z < pmt.num_weapon_classes(); z++) { + w.put_u8(pmt.get_sale_divisor(0, z)); + } + } else { + align(4); + root.weapon_sale_divisor_table = w.size(); + for (size_t z = 0; z < pmt.num_weapon_sale_divisors(); z++) { + w.put>(pmt.get_sale_divisor(0, z)); + } + } + + if constexpr (requires { root.non_weapon_integral_sale_divisor_table; }) { + root.non_weapon_integral_sale_divisor_table = w.size(); + NonWeaponSaleDivisorsDCProtos sds; + sds.armor_divisor = pmt.get_sale_divisor(1, 1); + sds.shield_divisor = pmt.get_sale_divisor(1, 2); + sds.unit_divisor = pmt.get_sale_divisor(1, 3); + w.put(sds); + } else { + align(4); + root.non_weapon_sale_divisor_table = w.size(); + NonWeaponSaleDivisorsT sds; + sds.armor_divisor = pmt.get_sale_divisor(1, 1); + sds.shield_divisor = pmt.get_sale_divisor(1, 2); + sds.unit_divisor = pmt.get_sale_divisor(1, 3); + sds.mag_divisor = pmt.get_sale_divisor(2, 0); + w.put>(sds); + } + + MagFeedResultsListOffsetsT mag_feed_result_offsets; + for (size_t table_index = 0; table_index < 8; table_index++) { + mag_feed_result_offsets[table_index] = w.size(); + for (size_t item_id = 0; item_id < 11; item_id++) { + w.put(pmt.get_mag_feed_result(table_index, item_id)); + } + } + + root.star_value_table = w.size(); + w.write(pmt.get_star_value_table()); + + if constexpr (requires { root.unknown_a1; }) { + align(2); + root.unknown_a1 = w.size(); + w.write(pmt.get_unknown_a1()); + } + + align(2); + root.special_table = w.size(); + for (size_t z = 0; z < pmt.num_specials(); z++) { + w.put>(pmt.get_special(z)); + } + + align(4); + root.weapon_effect_table = w.size(); + for (size_t z = 0; z < pmt.num_weapon_effects(); z++) { + w.put>(pmt.get_weapon_effect(z)); + } + + align(4); + if constexpr (requires { root.shield_effect_table; }) { + root.shield_effect_table = w.size(); + for (size_t z = 0; z < pmt.num_shield_effects(); z++) { + w.put>(pmt.get_shield_effect(z)); + } + } + + align(4); + if constexpr (requires { root.sound_remap_table; }) { + std::vector> remap_refs; + const auto& remaps = pmt.get_all_sound_remaps(); + for (const auto& remap : remaps) { + auto& remap_ref = remap_refs.emplace_back(); + remap_ref.sound_id = remap.sound_id; + remap_ref.remaps_for_rt_index_table = w.size(); + for (uint32_t remap_sound_id : remap.by_rt_index) { + w.put>(remap_sound_id); + } + remap_ref.remaps_for_char_class_table = w.size(); + for (uint32_t remap_sound_id : remap.by_char_class) { + w.put>(remap_sound_id); + } + } + ArrayRefT remap_vec{remaps.size(), w.size()}; + for (const auto& remap_ref : remap_refs) { + w.put>(remap_ref); + relocations.emplace(w.size() - 8); + relocations.emplace(w.size() - 4); + } + root.sound_remap_table = w.size(); + write_ref(remap_vec); + } + + align(4); + root.stat_boost_table = w.size(); + for (size_t z = 0; z < pmt.num_stat_boosts(); z++) { + w.put>(pmt.get_stat_boost(z)); + } + + if constexpr (requires { root.max_tech_level_table; }) { + root.max_tech_level_table = w.size(); + MaxTechniqueLevels max_tech_levels; + for (size_t tech_num = 0; tech_num < 0x13; tech_num++) { + for (size_t char_class = 0; char_class < 0x0C; char_class++) { + max_tech_levels[tech_num][char_class] = pmt.get_max_tech_level(char_class, tech_num); + } + } + w.put(max_tech_levels); + } + + ArrayRefT combination_table_ref; + if constexpr (requires { root.combination_table; }) { + combination_table_ref.offset = w.size(); + combination_table_ref.count = pmt.num_item_combinations(); + for (size_t z = 0; z < combination_table_ref.count; z++) { + w.put(pmt.get_item_combination(z)); + } + } + + if constexpr (requires { root.tech_boost_table; }) { + align(4); + root.tech_boost_table = w.size(); + for (size_t z = 0; z < pmt.num_tech_boosts(); z++) { + w.put>(pmt.get_tech_boost(z)); + } + } + + std::vector> unwrap_table_refs; + if constexpr (requires { root.unwrap_table; }) { + for (size_t event = 0; event < pmt.num_events(); event++) { + auto [event_items, num_items] = pmt.get_event_items(event); + unwrap_table_refs.emplace_back(ArrayRefT{num_items, w.size()}); + w.write(event_items, sizeof(EventItem) * num_items); + } + } + + ArrayRefT unsealable_table_ref; + if constexpr (requires { root.unsealable_table; }) { + const auto& items = pmt.all_unsealable_items(); + unsealable_table_ref.count = items.size(); + unsealable_table_ref.offset = w.size(); + for (const auto& item : items) { + UnsealableItem encoded; + u32_to_item_code(encoded.item, item); + w.put(encoded); + } + } + + ArrayRefT ranged_specials_ref; + if constexpr (requires { root.ranged_special_table; }) { + ranged_specials_ref.count = pmt.num_ranged_specials(); + ranged_specials_ref.offset = w.size(); + for (size_t z = 0; z < ranged_specials_ref.count; z++) { + w.put(pmt.get_ranged_special(z)); + } + } + + align(4); + root.armor_table = w.size(); + write_ref(armors_ref); + write_ref(shields_ref); + root.unit_table = w.size(); + write_ref(units_ref); + root.mag_table = w.size(); + write_ref(mags_ref); + root.tool_table = w.size(); + for (const auto& ref : tool_refs) { + write_ref(ref); + } + root.weapon_table = w.size(); + for (const auto& ref : weapon_refs) { + write_ref(ref); + } + if constexpr (requires { root.combination_table; }) { + root.combination_table = w.size(); + write_ref(combination_table_ref); + } + if constexpr (requires { root.unwrap_table; }) { + ArrayRefT event_ref{unwrap_table_refs.size(), w.size()}; + for (const auto& ref : unwrap_table_refs) { + write_ref(ref); + } + root.unwrap_table = w.size(); + write_ref(event_ref); + } + if constexpr (requires { root.unsealable_table; }) { + root.unsealable_table = w.size(); + write_ref(unsealable_table_ref); + } + if constexpr (requires { root.ranged_special_table; }) { + root.ranged_special_table = w.size(); + write_ref(ranged_specials_ref); + } + + root.mag_feed_table = w.size(); + w.put>(mag_feed_result_offsets); + for (size_t z = 1; z <= 8; z++) { + relocations.emplace(w.size() - (z * 4)); + } + + RELFileFooterT footer; + footer.root_offset = w.size(); + w.put(root); + constexpr size_t root_field_count = (sizeof(RootT) / 4) - ((requires { root.entry_count; }) ? 1 : 0); + for (size_t z = 1; z <= root_field_count; z++) { + relocations.emplace(w.size() - (z * 4)); + } + + align(0x20); + footer.relocations_offset = w.size(); + footer.num_relocations = relocations.size(); + footer.unused1[0] = 1; + uint32_t last_offset = 0; + for (uint32_t reloc_offset : relocations) { + if (reloc_offset & 3) { + throw logic_error("Relocation is not 4-byte aligned"); + } + size_t reloc_value = (reloc_offset - last_offset) >> 2; + if (reloc_value > 0xFFFF) { + throw runtime_error("Relocation offset is too far away from previous"); + } + w.put>(reloc_value); + last_offset = reloc_offset; + } + + align(0x20); + w.put>(footer); + + return std::move(w.str()); + } + protected: std::shared_ptr data; phosg::StringReader r; @@ -2640,7 +3285,7 @@ protected: mutable std::vector weapon_ranges; mutable std::vector weapon_effects; mutable std::vector shield_effects; - mutable std::optional> sound_remaps; + mutable std::optional> sound_remaps; mutable std::vector tech_boosts; // Key is used_item. We can't index on (used_item, equipped_item) because equipped_item may contain wildcards, and @@ -2651,132 +3296,116 @@ protected: }; using ItemParameterTableDCNTE = BinaryItemParameterTableT< + EmptyHeader, // typename HeaderT RootDCProtos, // typename RootT WeaponDCProtos, // typename WeaponT - 0x27, // size_t NumWeaponClasses ArmorOrShieldDCProtos, // typename ArmorOrShieldT UnitDCProtos, // typename UnitT ToolV1V2, // typename ToolT - 0x0D, // size_t NumToolClasses MagV1, // typename MagT + false, // bool HasImplicitPlaceholders 0x22, // size_t ItemStarsFirstID - 0x168, // size_t ItemStarsLastID 0xAA, // size_t SpecialStarsBeginIndex - 0x28, // size_t NumSpecials - 0x14, // size_t NumPhotonColors 0x00, // size_t SoundRemapRTTableSize + false, // bool HasServerHeader false>; // bool BE using ItemParameterTableDC112000 = BinaryItemParameterTableT< + EmptyHeader, // typename HeaderT RootDCProtos, // typename RootT WeaponDCProtos, // typename WeaponT - 0x27, // size_t NumWeaponClasses ArmorOrShieldDCProtos, // typename ArmorOrShieldT UnitDCProtos, // typename UnitT ToolV1V2, // typename ToolT - 0x0E, // size_t NumToolClasses MagV1, // typename MagT + false, // bool HasImplicitPlaceholders 0x26, // size_t ItemStarsFirstID - 0x16C, // size_t ItemStarsLastID 0xAE, // size_t SpecialStarsBeginIndex - 0x28, // size_t NumSpecials - 0x14, // size_t NumPhotonColors 0x00, // size_t SoundRemapRTTableSize + false, // bool HasServerHeader false>; // bool BE using ItemParameterTableV1 = BinaryItemParameterTableT< + EmptyHeader, // typename HeaderT RootV1, // typename RootT WeaponV1V2, // typename WeaponT - 0x27, // size_t NumWeaponClasses ArmorOrShieldV1V2, // typename ArmorOrShieldT UnitV1V2, // typename UnitT ToolV1V2, // typename ToolT - 0x0E, // size_t NumToolClasses MagV1, // typename MagT + false, // bool HasImplicitPlaceholders 0x26, // size_t ItemStarsFirstID - 0x16C, // size_t ItemStarsLastID 0xAE, // size_t SpecialStarsBeginIndex - 0x29, // size_t NumSpecials - 0x14, // size_t NumPhotonColors 0x00, // size_t SoundRemapRTTableSize + false, // bool HasServerHeader false>; // bool BE using ItemParameterTableV2 = BinaryItemParameterTableT< + HeaderV2, // typename HeaderT RootV2, // typename RootT WeaponV1V2, // typename WeaponT - 0x89, // size_t NumWeaponClasses ArmorOrShieldV1V2, // typename ArmorOrShieldT UnitV1V2, // typename UnitT ToolV1V2, // typename ToolT - 0x10, // size_t NumToolClasses MagV2, // typename MagT + true, // bool HasImplicitPlaceholders 0x4E, // size_t ItemStarsFirstID - 0x215, // size_t ItemStarsLastID 0x138, // size_t SpecialStarsBeginIndex - 0x29, // size_t NumSpecials - 0x20, // size_t NumPhotonColors 0x00, // size_t SoundRemapRTTableSize + false, // bool HasServerHeader false>; // bool BE using ItemParameterTableGCNTE = BinaryItemParameterTableT< + HeaderGCNTE, // typename HeaderT RootGCNTE, // typename RootT WeaponGCNTE, // typename WeaponT - 0x8D, // size_t NumWeaponClasses ArmorOrShieldGC, // typename ArmorOrShieldT UnitGC, // typename UnitT ToolGC, // typename ToolT - 0x13, // size_t NumToolClasses MagGC, // typename MagT + false, // bool HasImplicitPlaceholders 0x76, // size_t ItemStarsFirstID - 0x298, // size_t ItemStarsLastID 0x1A3, // size_t SpecialStarsBeginIndex - 0x29, // size_t NumSpecials - 0x20, // size_t NumPhotonColors 0x4F, // size_t SoundRemapRTTableSize + false, // bool HasServerHeader true>; // bool BE using ItemParameterTableGC = BinaryItemParameterTableT< + HeaderV3, // typename HeaderT RootV3V4T, // typename RootT WeaponGC, // typename WeaponT - 0xAA, // size_t NumWeaponClasses ArmorOrShieldGC, // typename ArmorOrShieldT UnitGC, // typename UnitT ToolGC, // typename ToolT - 0x18, // size_t NumToolClasses MagGC, // typename MagT + false, // bool HasImplicitPlaceholders 0x94, // size_t ItemStarsFirstID - 0x2F7, // size_t ItemStarsLastID 0x1CB, // size_t SpecialStarsBeginIndex - 0x29, // size_t NumSpecials - 0x20, // size_t NumPhotonColors 0x58, // size_t SoundRemapRTTableSize + false, // bool HasServerHeader true>; // bool BE using ItemParameterTableXB = BinaryItemParameterTableT< + HeaderV3, // typename HeaderT RootV3V4T, // typename RootT WeaponXB, // typename WeaponT - 0xAA, // size_t NumWeaponClasses ArmorOrShieldXB, // typename ArmorOrShieldT UnitXB, // typename UnitT ToolXB, // typename ToolT - 0x18, // size_t NumToolClasses MagXB, // typename MagT + false, // bool HasImplicitPlaceholders 0x94, // size_t ItemStarsFirstID - 0x2F7, // size_t ItemStarsLastID 0x1CB, // size_t SpecialStarsBeginIndex - 0x29, // size_t NumSpecials - 0x20, // size_t NumPhotonColors 0x58, // size_t SoundRemapRTTableSize + false, // bool HasServerHeader false>; // bool BE using ItemParameterTableV4 = BinaryItemParameterTableT< + HeaderV4, // typename HeaderT RootV3V4T, // typename RootT WeaponV4, // typename WeaponT - 0xED, // size_t NumWeaponClasses ArmorOrShieldV4, // typename ArmorOrShieldT UnitV4, // typename UnitT ToolV4, // typename ToolT - 0x1B, // size_t NumToolClasses MagV4, // typename MagT + false, // bool HasImplicitPlaceholders 0xB1, // size_t ItemStarsFirstID - 0x437, // size_t ItemStarsLastID 0x256, // size_t SpecialStarsBeginIndex - 0x29, // size_t NumSpecials - 0x20, // size_t NumPhotonColors 0x6A, // size_t SoundRemapRTTableSize + true, // bool HasServerHeader false>; // bool BE std::shared_ptr ItemParameterTable::from_binary( @@ -2810,3 +3439,30 @@ std::shared_ptr ItemParameterTable::from_binary( std::shared_ptr ItemParameterTable::from_json(const phosg::JSON& json) { return std::make_shared(json); } + +std::string ItemParameterTable::serialize_binary(Version version) const { + switch (version) { + case Version::DC_NTE: + return ItemParameterTableDCNTE::serialize(*this); + case Version::DC_11_2000: + return ItemParameterTableDC112000::serialize(*this); + case Version::DC_V1: + return ItemParameterTableV1::serialize(*this); + case Version::DC_V2: + case Version::PC_NTE: + case Version::PC_V2: + return ItemParameterTableV2::serialize(*this); + case Version::GC_NTE: + return ItemParameterTableGCNTE::serialize(*this); + case Version::GC_V3: + case Version::GC_EP3: + case Version::GC_EP3_NTE: + return ItemParameterTableGC::serialize(*this); + case Version::XB_V3: + return ItemParameterTableXB::serialize(*this); + case Version::BB_V4: + return ItemParameterTableV4::serialize(*this); + default: + throw std::logic_error("Cannot create item parameter table for this version"); + } +} diff --git a/src/ItemParameterTable.hh b/src/ItemParameterTable.hh index 47d3e2f0..854b83f5 100644 --- a/src/ItemParameterTable.hh +++ b/src/ItemParameterTable.hh @@ -68,6 +68,7 @@ public: uint8_t special = 0; uint8_t ata = 0; uint8_t stat_boost_entry_index = 0; + parray v2_unknown_a9; uint8_t projectile = 0; int8_t trail1_x = 0; int8_t trail1_y = 0; @@ -361,7 +362,7 @@ public: static std::shared_ptr from_json(const phosg::JSON& json); phosg::JSON json() const; - // std::string serialize_binary() const; // TODO + std::string serialize_binary(Version version) const; std::set compute_all_valid_primary_identifiers() const; @@ -389,6 +390,7 @@ public: virtual const Mag& get_mag(uint8_t data1_1) const = 0; // weapon_kind_table accessors (data1_1 in [0, num_weapon_classes()]) + virtual size_t num_weapon_kinds() const = 0; virtual uint8_t get_weapon_kind(uint8_t data1_1) const = 0; // photon_color_table accessors @@ -401,6 +403,7 @@ public: // weapon_sale_divisor_table and non_weapon_sale_divisor_table accessors (data1_0 in [0, 1, 2]; data1_1 in [0, // num_weapon_classes()] for weapons or ignored otherwise) + virtual size_t num_weapon_sale_divisors() const = 0; virtual float get_sale_divisor(uint8_t data1_0, uint8_t data1_1) const = 0; // mag_feed_table accessors (table_index in [0, 7], item_index in [0, 10]) @@ -451,12 +454,14 @@ public: virtual uint8_t get_max_tech_level(uint8_t char_class, uint8_t tech_num) const = 0; // combination_table accessors - virtual const std::map>& all_item_combinations() const = 0; + virtual size_t num_item_combinations() const = 0; + virtual const ItemCombination& get_item_combination(size_t index) const = 0; + const std::map>& item_combinations_index() const; const std::vector& all_combinations_for_used_item(const ItemData& used_item) const; const ItemCombination& get_item_combination(const ItemData& used_item, const ItemData& equipped_item) const; // sound_remap_table accessors - virtual const std::unordered_map& get_all_sound_remaps() const = 0; + virtual const std::vector& get_all_sound_remaps() const = 0; // tech_boost_table accessors virtual size_t num_tech_boosts() const = 0; @@ -487,4 +492,6 @@ public: protected: ItemParameterTable() = default; + + mutable std::optional>> item_combination_index; }; diff --git a/src/ItemTranslationTable.cc b/src/ItemTranslationTable.cc index 384db832..54aaaeae 100644 --- a/src/ItemTranslationTable.cc +++ b/src/ItemTranslationTable.cc @@ -46,18 +46,18 @@ ItemTranslationTable::ItemTranslationTable( uint32_t e_id = this->entries[z].id_for_version[v_s]; if (is_canonical(e_id)) { if (!entry_index.count(e_id)) { - throw logic_error(std::format("(row {} version {}) canonical ID {:X} is missing from the index", z, phosg::name_for_enum(v), e_id)); + throw logic_error(std::format("(row {} version {}) canonical ID {:08X} is missing from the index", z, phosg::name_for_enum(v), e_id)); } try { item_parameter_table->definition_for_primary_identifier(e_id); } catch (const out_of_range&) { - throw runtime_error(std::format("(row {} version {}) ID {:X} not defined in item parameter table", z, phosg::name_for_enum(v), e_id)); + throw runtime_error(std::format("(row {} version {}) ID {:08X} not defined in item parameter table", z, phosg::name_for_enum(v), e_id)); } if (!remaining_identifiers.erase(e_id)) { - throw runtime_error(std::format("(row {} version {}) ID {:X} not in item parameter table's primary identifier list", z, phosg::name_for_enum(v), e_id)); + throw runtime_error(std::format("(row {} version {}) ID {:08X} not in item parameter table's primary identifier list", z, phosg::name_for_enum(v), e_id)); } } else if (!entry_index.count(make_canonical(e_id))) { - throw runtime_error(std::format("(row {} version {}) ID {:X} refers to nonexistent canonical ID", z, phosg::name_for_enum(v), e_id)); + throw runtime_error(std::format("(row {} version {}) ID {:08X} refers to nonexistent canonical ID", z, phosg::name_for_enum(v), e_id)); } } diff --git a/src/Main.cc b/src/Main.cc index 644c4fd8..d07ec2f7 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -2387,10 +2387,20 @@ Action a_compare_common_item_set( cs1->print_diff(stdout, *cs2); }); -Action a_convert_item_parameter_table( - "decode-item-parameter-table", nullptr, +Action a_decode_item_parameter_table( + "decode-item-parameter-table", "\ + decode-item-parameter-table [INPUT-FILENAME [OUTPUT-FILENAME]] [OPTIONS...]\n\ + Converts an ItemPMT file into a JSON item parameter table. A version\n\ + option is required. Use --hex to make item codes in the output readable;\n\ + however, this option also uses nonstandard JSON syntax - newserv can parse\n\ + it, but many other JSON parsers can\'t. Expects compressed input (a .prs\n\ + file) by default; use --decompressed if the input is not compressed.\n", +[](phosg::Arguments& args) { - auto data = std::make_shared(read_input_data(args)); + auto input_data = read_input_data(args); + if (!args.get("decompressed")) { + input_data = prs_decompress(input_data); + } + auto data = std::make_shared(std::move(input_data)); auto pmt = ItemParameterTable::from_binary(data, get_cli_version(args, Version::BB_V4)); auto json = pmt->json(); uint32_t serialize_options = phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::SORT_DICT_KEYS; @@ -2401,6 +2411,23 @@ Action a_convert_item_parameter_table( write_output_data(args, json_data.data(), json_data.size(), nullptr); }); +Action a_encode_item_parameter_table( + "encode-item-parameter-table", "\ + encode-item-parameter-table [INPUT-FILENAME [OUTPUT-FILENAME]] [OPTIONS...]\n\ + Converts a JSON item parameter table into an ItemPMT file compatible with\n\ + the game client. A version option is required. By default the output will\n\ + be compressed, as the client expects; use --decompressed to get\n\ + uncompressed output.\n", + +[](phosg::Arguments& args) { + auto json = phosg::JSON::parse(read_input_data(args)); + auto pmt = ItemParameterTable::from_json(json); + string data = pmt->serialize_binary(get_cli_version(args, Version::BB_V4)); + if (!args.get("decompressed")) { + data = prs_compress_optimal(data); + } + write_output_data(args, data.data(), data.size(), nullptr); + }); + Action a_describe_item( "describe-item", "\ describe-item DATA-OR-DESCRIPTION\n\ diff --git a/src/SendCommands.cc b/src/SendCommands.cc index c850c239..5708ef40 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -690,42 +690,16 @@ void send_guild_card_chunk_bb(shared_ptr c, size_t chunk_index) { send_command(c, 0x02DC, 0x00000000, &cmd, sizeof(cmd) - sizeof(cmd.data) + data_size); } -static const vector stream_file_entries = { - "ItemMagEdit.prs", - "ItemPMT.prs", - "BattleParamEntry.dat", - "BattleParamEntry_on.dat", - "BattleParamEntry_lab.dat", - "BattleParamEntry_lab_on.dat", - "BattleParamEntry_ep4.dat", - "BattleParamEntry_ep4_on.dat", - "PlyLevelTbl.prs", -}; - void send_stream_file_index_bb(shared_ptr c) { auto s = c->require_server_state(); vector entries; - size_t offset = 0; - for (const string& filename : stream_file_entries) { - string key = "system/blueburst/" + filename; - auto cache_res = s->bb_stream_files_cache->get_or_load(key); + for (const auto& sf_entry : s->bb_stream_file->entries) { auto& e = entries.emplace_back(); - e.size = cache_res.file->data->size(); - // Computing the checksum can be slow, so we cache it along with the file data. If the cache result was just - // populated, then it may be different, so we always recompute the checksum in that case. - if (cache_res.generate_called) { - e.checksum = crc32(cache_res.file->data->data(), e.size); - s->bb_stream_files_cache->replace_obj(key + ".crc32", e.checksum); - } else { - auto compute_checksum = [&](const string&) -> uint32_t { - return crc32(cache_res.file->data->data(), e.size); - }; - e.checksum = s->bb_stream_files_cache->get_obj(key + ".crc32", compute_checksum).obj; - } - e.offset = offset; - e.filename.encode(filename); - offset += e.size; + e.size = sf_entry.size; + e.checksum = sf_entry.checksum; + e.offset = sf_entry.offset; + e.filename.encode(sf_entry.filename); } send_command_vt(c, 0x01EB, entries.size(), entries); } @@ -733,30 +707,14 @@ void send_stream_file_index_bb(shared_ptr c) { void send_stream_file_chunk_bb(shared_ptr c, uint32_t chunk_index) { auto s = c->require_server_state(); - auto cache_result = s->bb_stream_files_cache->get( - "", [&](const string&) -> string { - size_t bytes = 0; - for (const auto& name : stream_file_entries) { - bytes += s->bb_stream_files_cache->get_or_load("system/blueburst/" + name).file->data->size(); - } - - string ret; - ret.reserve(bytes); - for (const auto& name : stream_file_entries) { - ret += *s->bb_stream_files_cache->get_or_load("system/blueburst/" + name).file->data; - } - return ret; - }); - const auto& contents = cache_result.file->data; - S_StreamFileChunk_BB_02EB chunk_cmd; chunk_cmd.chunk_index = chunk_index; size_t offset = sizeof(chunk_cmd.data) * chunk_index; - if (offset > contents->size()) { + if (offset > s->bb_stream_file->data.size()) { throw runtime_error("client requested chunk beyond end of stream file"); } - size_t bytes = min(contents->size() - offset, sizeof(chunk_cmd.data)); - chunk_cmd.data.assign_range(reinterpret_cast(contents->data() + offset), bytes, 0); + size_t bytes = min(s->bb_stream_file->data.size() - offset, sizeof(chunk_cmd.data)); + chunk_cmd.data.assign_range(reinterpret_cast(s->bb_stream_file->data.data() + offset), bytes, 0); size_t cmd_size = offsetof(S_StreamFileChunk_BB_02EB, data) + bytes; cmd_size = (cmd_size + 3) & ~3; diff --git a/src/ServerState.cc b/src/ServerState.cc index 2740808a..4409fc5a 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -79,7 +79,6 @@ ServerState::ServerState(const string& config_filename, bool is_replay) config_filename(config_filename), is_replay(is_replay), thread_pool(make_unique()), - bb_stream_files_cache(new FileContentsCache(3600000000ULL)), bb_system_cache(new FileContentsCache(3600000000ULL)), gba_files_cache(new FileContentsCache(3600000000ULL)) {} @@ -1831,8 +1830,6 @@ vector> ServerState::supermaps_for_variations( } void ServerState::clear_file_caches() { - config_log.info_f("Clearing BB stream file cache"); - this->bb_stream_files_cache.reset(new FileContentsCache(3600000000ULL)); config_log.info_f("Clearing BB system cache"); this->bb_system_cache.reset(new FileContentsCache(3600000000ULL)); config_log.info_f("Clearing GBA file cache"); @@ -2148,10 +2145,9 @@ void ServerState::load_item_definitions() { config_log.info_f("Loading item definition tables"); for (size_t v_s = NUM_PATCH_VERSIONS; v_s < NUM_VERSIONS; v_s++) { Version v = static_cast(v_s); - string path = std::format("system/item-tables/ItemPMT-{}.prs", file_path_token_for_version(v)); + string path = std::format("system/item-tables/item-parameter-table-{}.json", file_path_token_for_version(v)); config_log.debug_f("Loading item definition table {}", path); - auto data = make_shared(prs_decompress(phosg::load_file(path))); - new_item_parameter_tables[v_s] = ItemParameterTable::from_binary(data, v); + new_item_parameter_tables[v_s] = ItemParameterTable::from_json(phosg::JSON::parse(phosg::load_file(path))); } auto json = phosg::JSON::parse(phosg::load_file("system/item-tables/translation-table.json")); @@ -2227,6 +2223,39 @@ void ServerState::load_dol_files() { this->dol_file_index = make_shared("system/dol"); } +void ServerState::generate_bb_stream_file() { + config_log.info_f("Generating BB stream file"); + auto sf = std::make_shared(); + + auto add_file = [&](const std::string& filename, std::string&& file_data = "") -> void { + if (file_data.empty()) { + file_data = phosg::load_file("system/blueburst/" + filename); + } + auto& e = sf->entries.emplace_back(); + e.size = file_data.size(); + e.checksum = phosg::crc32(file_data.data(), file_data.size()); + e.offset = sf->data.size(); + e.filename = filename; + sf->data += file_data; + config_log.debug_f( + "[BBStreamFile] Added file {} at offset {:08X} ({:08X} bytes) with checksum {:08X}; total size is now {:08X}", + filename, e.offset, e.size, e.checksum, sf->data.size()); + }; + + add_file("BattleParamEntry.dat"); + add_file("BattleParamEntry_on.dat"); + add_file("BattleParamEntry_lab.dat"); + add_file("BattleParamEntry_lab_on.dat"); + add_file("BattleParamEntry_ep4.dat"); + add_file("BattleParamEntry_ep4_on.dat"); + add_file("PlyLevelTbl.prs"); + add_file("ItemMagEdit.prs"); + auto pmt = this->item_parameter_table(Version::BB_V4); + add_file("ItemPMT.prs", prs_compress_optimal(pmt->serialize_binary(Version::BB_V4))); + + this->bb_stream_file = sf; +} + void ServerState::create_default_lobbies() { if (this->default_lobbies_created) { return; @@ -2306,6 +2335,7 @@ void ServerState::load_all(bool enable_thread_pool) { this->load_config_late(); this->load_teams(); this->load_quest_index(); + this->generate_bb_stream_file(); } void ServerState::disconnect_all_banned_clients() { diff --git a/src/ServerState.hh b/src/ServerState.hh index 41365247..5ec33031 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -65,6 +65,17 @@ struct CheatFlags { explicit CheatFlags(const phosg::JSON& json); }; +struct BBStreamFile { + struct Entry { + uint32_t offset; + uint32_t size; + uint32_t checksum; // crc32 + std::string filename; + }; + std::vector entries; + std::string data; +}; + struct ServerState : public std::enable_shared_from_this { enum class RunShellBehavior { DEFAULT = 0, @@ -180,7 +191,7 @@ struct ServerState : public std::enable_shared_from_this { std::unordered_map> supermap_for_source_hash_sum; std::unordered_map> supermap_for_free_play_key; std::shared_ptr room_layout_index; - std::shared_ptr bb_stream_files_cache; + std::shared_ptr bb_stream_file; std::shared_ptr bb_system_cache; std::shared_ptr gba_files_cache; std::shared_ptr dol_file_index; @@ -442,6 +453,7 @@ struct ServerState : public std::enable_shared_from_this { void load_quest_index(bool raise_on_any_failure = false); void compile_functions(bool raise_on_any_failure = false); void load_dol_files(); + void generate_bb_stream_file(); void load_all(bool enable_thread_pool); diff --git a/src/ShellCommands.cc b/src/ShellCommands.cc index d0aa8780..eb2e886a 100644 --- a/src/ShellCommands.cc +++ b/src/ShellCommands.cc @@ -211,14 +211,17 @@ ShellCommand c_reload( args.s->load_set_data_tables(); } else if (type == "battle-params") { args.s->load_battle_params(); + args.s->generate_bb_stream_file(); } else if (type == "level-tables") { args.s->load_level_tables(); + args.s->generate_bb_stream_file(); } else if (type == "text-index") { args.s->load_text_index(); } else if (type == "word-select") { args.s->load_word_select_table(); } else if (type == "item-definitions") { args.s->load_item_definitions(); + args.s->generate_bb_stream_file(); } else if (type == "item-name-index") { args.s->load_item_name_indexes(); } else if (type == "drop-tables") { diff --git a/system/blueburst/ItemPMT.prs b/system/blueburst/ItemPMT.prs deleted file mode 120000 index 93acdd34..00000000 --- a/system/blueburst/ItemPMT.prs +++ /dev/null @@ -1 +0,0 @@ -../item-tables/ItemPMT-bb-v4.prs \ No newline at end of file diff --git a/system/item-tables/ItemPMT-bb-v4.prs b/system/item-tables/ItemPMT-bb-v4.prs deleted file mode 100644 index 46a92384..00000000 Binary files a/system/item-tables/ItemPMT-bb-v4.prs and /dev/null differ diff --git a/system/item-tables/ItemPMT-dc-11-2000.prs b/system/item-tables/ItemPMT-dc-11-2000.prs deleted file mode 100644 index d6e163f0..00000000 Binary files a/system/item-tables/ItemPMT-dc-11-2000.prs and /dev/null differ diff --git a/system/item-tables/ItemPMT-dc-nte.prs b/system/item-tables/ItemPMT-dc-nte.prs deleted file mode 100644 index 631a379d..00000000 Binary files a/system/item-tables/ItemPMT-dc-nte.prs and /dev/null differ diff --git a/system/item-tables/ItemPMT-dc-v1.prs b/system/item-tables/ItemPMT-dc-v1.prs deleted file mode 100644 index 369d1580..00000000 Binary files a/system/item-tables/ItemPMT-dc-v1.prs and /dev/null differ diff --git a/system/item-tables/ItemPMT-dc-v2.prs b/system/item-tables/ItemPMT-dc-v2.prs deleted file mode 120000 index 1e1d0196..00000000 --- a/system/item-tables/ItemPMT-dc-v2.prs +++ /dev/null @@ -1 +0,0 @@ -ItemPMT-pc-v2.prs \ No newline at end of file diff --git a/system/item-tables/ItemPMT-gc-ep3-nte.prs b/system/item-tables/ItemPMT-gc-ep3-nte.prs deleted file mode 120000 index de681f45..00000000 --- a/system/item-tables/ItemPMT-gc-ep3-nte.prs +++ /dev/null @@ -1 +0,0 @@ -ItemPMT-gc-v3.prs \ No newline at end of file diff --git a/system/item-tables/ItemPMT-gc-ep3.prs b/system/item-tables/ItemPMT-gc-ep3.prs deleted file mode 120000 index de681f45..00000000 --- a/system/item-tables/ItemPMT-gc-ep3.prs +++ /dev/null @@ -1 +0,0 @@ -ItemPMT-gc-v3.prs \ No newline at end of file diff --git a/system/item-tables/ItemPMT-gc-nte.prs b/system/item-tables/ItemPMT-gc-nte.prs deleted file mode 100644 index 3435fb34..00000000 Binary files a/system/item-tables/ItemPMT-gc-nte.prs and /dev/null differ diff --git a/system/item-tables/ItemPMT-gc-v3.prs b/system/item-tables/ItemPMT-gc-v3.prs deleted file mode 100644 index 315360b7..00000000 Binary files a/system/item-tables/ItemPMT-gc-v3.prs and /dev/null differ diff --git a/system/item-tables/ItemPMT-pc-nte.prs b/system/item-tables/ItemPMT-pc-nte.prs deleted file mode 120000 index 1e1d0196..00000000 --- a/system/item-tables/ItemPMT-pc-nte.prs +++ /dev/null @@ -1 +0,0 @@ -ItemPMT-pc-v2.prs \ No newline at end of file diff --git a/system/item-tables/ItemPMT-pc-v2.prs b/system/item-tables/ItemPMT-pc-v2.prs deleted file mode 100644 index 9de81097..00000000 Binary files a/system/item-tables/ItemPMT-pc-v2.prs and /dev/null differ diff --git a/system/item-tables/ItemPMT-xb-v3.prs b/system/item-tables/ItemPMT-xb-v3.prs deleted file mode 100644 index fb00b644..00000000 Binary files a/system/item-tables/ItemPMT-xb-v3.prs and /dev/null differ diff --git a/system/item-tables/translation-table.json b/system/item-tables/translation-table.json index 68516999..70a342f1 100644 --- a/system/item-tables/translation-table.json +++ b/system/item-tables/translation-table.json @@ -1,19 +1,15 @@ [ - // This table maps in-game primary identifiers across client versions. The - // high bit of the entries in this table specifies the entry type; if this - // bit is set, the entry is not "canonical"; that is, it's a one-way mapping - // and the client does not actually have that item defined. Each primary - // identifier must have at most one "canonical" listing for each version - // (but may have none). To convert an item from client A to client B: - // 1. Scan the column for A's version in the table to find the cell that - // contains the primary identifier sent by A. (We optimize this with an - // index.) + // This table maps in-game primary identifiers across client versions. The high bit of the entries in this table + // specifies the entry type; if this bit is set, the entry is not "canonical"; that is, it's a one-way mapping and + // the client does not actually have that item defined. Each primary identifier must have at most one "canonical" + // listing for each version (but may have none). To convert an item from client A to client B: + // 1. Scan the column for A's version in the table to find the cell that contains the primary identifier sent by A. + // (We optimize this with an index.) // 2. Move left or right to the column for B's version. - // 3. Take the value from that cell, clear the high bit if needed, and use - // that as the primary identifier to show to B. + // 3. Take the value from that cell, clear the high bit if needed, and use that as the primary identifier for B. // This logic is implemented in ItemTranslationTable.cc. - // DC_NTE-- 11/2000--- DC_V1----- DC_V2----- PC_NTE---- PC_V2----- GC_NTE---- GC_V3----- GC_EP3_NTE GC_EP3---- XB_V3----- BB_V4----- NAME + // DC_NTE-- 11/2000--- DC_V1----- DC_V2----- PC_NTE---- PC_V2----- GC_NTE---- GC_V3----- GC_EP3_NTE GC_EP3---- XB_V3----- BB_V4----- NAME [0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, "UNUSED"], [0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00010000, "Saber"], [0x00010100, 0x00010100, 0x00010100, 0x00010100, 0x00010100, 0x00010100, 0x00010100, 0x00010100, 0x00010100, 0x00010100, 0x00010100, 0x00010100, "Brand"], @@ -134,7 +130,6 @@ [0x000F0100, 0x000F0100, 0x000F0100, 0x000F0100, 0x000F0100, 0x000F0100, 0x800F0000, 0x000F0100, 0x000F0100, 0x000F0100, 0x000F0100, 0x000F0100, "ANGRY FIST"], [0x000F0200, 0x000F0200, 0x000F0200, 0x000F0200, 0x000F0200, 0x000F0200, 0x800F0000, 0x000F0200, 0x000F0200, 0x000F0200, 0x000F0200, 0x000F0200, "GOD HAND"], [0x000F0300, 0x000F0300, 0x000F0300, 0x000F0300, 0x000F0300, 0x000F0300, 0x800F0000, 0x000F0300, 0x000F0300, 0x000F0300, 0x000F0300, 0x000F0300, "SONIC KNUCKLE"], - [0x000F0400, 0x000F0400, 0x000F0400, 0x000F0400, 0x000F0400, 0x000F0400, 0x80020500, 0x80100000, 0x80100000, 0x80100000, 0x80100000, 0x80100000, "OROTIAGITO (v0-v2)"], [0x800F0300, 0x800F0300, 0x800F0300, 0x800F0300, 0x800F0300, 0x800F0300, 0x800F0000, 0x800F0300, 0x800F0300, 0x800F0300, 0x800F0300, 0x000F0400, "LOGiN"], [0x00100000, 0x00100000, 0x00100000, 0x00100000, 0x00100000, 0x00100000, 0x00100000, 0x00100000, 0x00100000, 0x00100000, 0x00100000, 0x00100000, "OROTIAGITO"], [0x00100100, 0x00100100, 0x00100100, 0x00100100, 0x00100100, 0x00100100, 0x80100000, 0x00100100, 0x00100100, 0x00100100, 0x00100100, 0x00100100, "AGITO (1975)"], @@ -439,7 +434,9 @@ [0x80080500, 0x80080500, 0x80080500, 0x80770000, 0x80770000, 0x80770000, 0x80080500, 0x80770000, 0x80770000, 0x80770000, 0x80770000, 0x00EA0000, "TypeME/MECHGUN"], [0x80090500, 0x80090500, 0x80090500, 0x80780000, 0x80780000, 0x80780000, 0x80090500, 0x80780000, 0x80780000, 0x80780000, 0x80780000, 0x00EB0000, "TypeSH/SHOT"], [0x800C0400, 0x800C0400, 0x800C0400, 0x807B0000, 0x807B0000, 0x807B0000, 0x800C0400, 0x807B0000, 0x807B0000, 0x807B0000, 0x807B0000, 0x00EC0000, "TypeWA/WAND"], + [0x80010000, 0x80010000, 0x80010000, 0x00890000, 0x00890000, 0x00890000, 0x008D0000, 0x00AA0000, 0x00AA0000, 0x00AA0000, 0x00AA0000, 0x00ED0000, "???? (WEAPON)"], + // DC_NTE-- 11/2000--- DC_V1----- DC_V2----- PC_NTE---- PC_V2----- GC_NTE---- GC_V3----- GC_EP3_NTE GC_EP3---- XB_V3----- BB_V4----- NAME [0x01010000, 0x01010000, 0x01010000, 0x01010000, 0x01010000, 0x01010000, 0x01010000, 0x01010000, 0x01010000, 0x01010000, 0x01010000, 0x01010000, "Frame"], [0x01010100, 0x01010100, 0x01010100, 0x01010100, 0x01010100, 0x01010100, 0x01010100, 0x01010100, 0x01010100, 0x01010100, 0x01010100, 0x01010100, "Armor"], [0x01010200, 0x01010200, 0x01010200, 0x01010200, 0x01010200, 0x01010200, 0x01010200, 0x01010200, 0x01010200, 0x01010200, 0x01010200, 0x01010200, "Psy Armor"], @@ -528,8 +525,9 @@ [0x81012800, 0x81012800, 0x81012800, 0x81013400, 0x81013400, 0x81013400, 0x81010C00, 0x81013400, 0x81013400, 0x81013400, 0x81013400, 0x01015500, "UNION FIELD"], [0x81012800, 0x81012800, 0x81012800, 0x81013400, 0x81013400, 0x81013400, 0x81010C00, 0x81013400, 0x81013400, 0x81013400, 0x81013400, 0x01015600, "SAMURAI ARMOR"], [0x81012800, 0x81012800, 0x81012800, 0x81013400, 0x81013400, 0x81013400, 0x81010C00, 0x81013400, 0x81013400, 0x81013400, 0x81013400, 0x01015700, "STEALTH SUIT"], - [0x81012800, 0x81012800, 0x81012800, 0x81013400, 0x81013400, 0x81013400, 0x01013500, 0x01013500, 0x01013500, 0x01013500, 0x01013500, 0x01015800, "???? (ARMOR)"], + [0x81012800, 0x81012800, 0x81012800, 0x01013500, 0x01013500, 0x01013500, 0x01013500, 0x01013500, 0x01013500, 0x01013500, 0x01013500, 0x01015800, "???? (ARMOR)"], + // DC_NTE-- 11/2000--- DC_V1----- DC_V2----- PC_NTE---- PC_V2----- GC_NTE---- GC_V3----- GC_EP3_NTE GC_EP3---- XB_V3----- BB_V4----- NAME [0x01020000, 0x01020000, 0x01020000, 0x01020000, 0x01020000, 0x01020000, 0x01020000, 0x01020000, 0x01020000, 0x01020000, 0x01020000, 0x01020000, "Barrier"], [0x01020100, 0x01020100, 0x01020100, 0x01020100, 0x01020100, 0x01020100, 0x01020100, 0x01020100, 0x01020100, 0x01020100, 0x01020100, 0x01020100, "Shield"], [0x01020200, 0x01020200, 0x01020200, 0x01020200, 0x01020200, 0x01020200, 0x01020200, 0x01020200, 0x01020200, 0x01020200, 0x01020200, 0x01020200, "Core Shield"], @@ -695,8 +693,9 @@ [0x81022600, 0x81022600, 0x81022600, 0x81023500, 0x81023500, 0x81023500, 0x81020C00, 0x81028400, 0x81028400, 0x81028400, 0x81028400, 0x0102A200, "GENPEI (unused)"], [0x81022600, 0x81022600, 0x81022600, 0x81023500, 0x81023500, 0x81023500, 0x81020C00, 0x81028400, 0x81028400, 0x81028400, 0x81028400, 0x0102A300, "GENPEI (unused)"], [0x81022600, 0x81022600, 0x81022600, 0x81023500, 0x81023500, 0x81023500, 0x81020C00, 0x81028400, 0x81028400, 0x81028400, 0x81028400, 0x0102A400, "GENPEI (unused)"], - [0x81022600, 0x81022600, 0x81022600, 0x81023500, 0x81023500, 0x81023500, 0x81020C00, 0x01028500, 0x01028500, 0x01028500, 0x01028500, 0x0102A500, "???? (SHIELD)"], + [0x81022600, 0x81022600, 0x81022600, 0x01023A00, 0x01023A00, 0x01023A00, 0x81020C00, 0x01028500, 0x01028500, 0x01028500, 0x01028500, 0x0102A500, "???? (SHIELD)"], + // DC_NTE-- 11/2000--- DC_V1----- DC_V2----- PC_NTE---- PC_V2----- GC_NTE---- GC_V3----- GC_EP3_NTE GC_EP3---- XB_V3----- BB_V4----- NAME [0x01030000, 0x01030000, 0x01030000, 0x01030000, 0x01030000, 0x01030000, 0x01030000, 0x01030000, 0x01030000, 0x01030000, 0x01030000, 0x01030000, "Knight/Power"], [0x01030100, 0x01030100, 0x01030100, 0x01030100, 0x01030100, 0x01030100, 0x01030100, 0x01030100, 0x01030100, 0x01030100, 0x01030100, 0x01030100, "General/Power"], [0x01030200, 0x01030200, 0x01030200, 0x01030200, 0x01030200, 0x01030200, 0x01030200, 0x01030200, 0x01030200, 0x01030200, 0x01030200, 0x01030200, "Ogre/Power"], @@ -799,8 +798,9 @@ [0x81033500, 0x81033500, 0x81033500, 0x81033500, 0x81033500, 0x81033500, 0x81033400, 0x81033500, 0x81033500, 0x81033500, 0x81033500, 0x01036100, "HP/Resurrection"], [0x81033800, 0x81033800, 0x81033800, 0x81033800, 0x81033800, 0x81033800, 0x81033700, 0x81033800, 0x81033800, 0x81033800, 0x81033800, 0x01036200, "TP/Resurrection"], [0x81033B00, 0x81033B00, 0x81033B00, 0x81033B00, 0x81033B00, 0x81033B00, 0x81033A00, 0x81033B00, 0x81033B00, 0x81033B00, 0x81033B00, 0x01036300, "PB/Increase"], - [0x81033B00, 0x81033B00, 0x81033B00, 0x81033B00, 0x81033B00, 0x81033B00, 0x01034700, 0x01034800, 0x01034800, 0x01034800, 0x01034800, 0x01036400, "???? (UNIT)"], + [0x81033B00, 0x81033B00, 0x81033B00, 0x01034400, 0x01034400, 0x01034400, 0x01034700, 0x01034800, 0x01034800, 0x01034800, 0x01034800, 0x01036400, "???? (UNIT)"], + // DC_NTE-- 11/2000--- DC_V1----- DC_V2----- PC_NTE---- PC_V2----- GC_NTE---- GC_V3----- GC_EP3_NTE GC_EP3---- XB_V3----- BB_V4----- NAME [0x02000000, 0x02000000, 0x02000000, 0x02000000, 0x02000000, 0x02000000, 0x02000000, 0x02000000, 0x02000000, 0x02000000, 0x02000000, 0x02000000, "Mag"], [0x02010000, 0x02010000, 0x02010000, 0x02010000, 0x02010000, 0x02010000, 0x02010000, 0x02010000, 0x02010000, 0x02010000, 0x02010000, 0x02010000, "Varuna"], [0x02020000, 0x02020000, 0x02020000, 0x02020000, 0x02020000, 0x02020000, 0x02020000, 0x02020000, 0x02020000, 0x02020000, 0x02020000, 0x02020000, "Mitra"], @@ -841,10 +841,10 @@ [0x02250000, 0x02250000, 0x02250000, 0x02250000, 0x02250000, 0x02250000, 0x02250000, 0x02250000, 0x02250000, 0x02250000, 0x02250000, 0x02250000, "Naraka"], [0x02260000, 0x02260000, 0x02260000, 0x02260000, 0x02260000, 0x02260000, 0x02260000, 0x02260000, 0x02260000, 0x02260000, 0x02260000, 0x02260000, "Madhu"], [0x02270000, 0x02270000, 0x02270000, 0x02270000, 0x02270000, 0x02270000, 0x02270000, 0x02270000, 0x02270000, 0x02270000, 0x02270000, 0x02270000, "Churel"], - [0x82000000, 0x82000000, 0x82000000, 0x02280000, 0x02280000, 0x02280000, 0x82000000, 0x02280000, 0x02280000, 0x02280000, 0x02280000, 0x02280000, "ROBOCHAO"], - [0x82000000, 0x82000000, 0x82000000, 0x02290000, 0x02290000, 0x02290000, 0x82000000, 0x02290000, 0x02290000, 0x02290000, 0x02290000, 0x02290000, "OPA-OPA"], - [0x82000000, 0x82000000, 0x82000000, 0x022A0000, 0x022A0000, 0x022A0000, 0x82000000, 0x022A0000, 0x022A0000, 0x022A0000, 0x022A0000, 0x022A0000, "PIAN"], - [0x82000000, 0x82000000, 0x82000000, 0x022B0000, 0x022B0000, 0x022B0000, 0x82000000, 0x022B0000, 0x022B0000, 0x022B0000, 0x022B0000, 0x022B0000, "CHAO"], + [0x02280000, 0x02280000, 0x02280000, 0x02280000, 0x02280000, 0x02280000, 0x82000000, 0x02280000, 0x02280000, 0x02280000, 0x02280000, 0x02280000, "ROBOCHAO"], + [0x02290000, 0x02290000, 0x02290000, 0x02290000, 0x02290000, 0x02290000, 0x82000000, 0x02290000, 0x02290000, 0x02290000, 0x02290000, 0x02290000, "OPA-OPA"], + [0x022A0000, 0x022A0000, 0x022A0000, 0x022A0000, 0x022A0000, 0x022A0000, 0x82000000, 0x022A0000, 0x022A0000, 0x022A0000, 0x022A0000, 0x022A0000, "PIAN"], + [0x022B0000, 0x022B0000, 0x022B0000, 0x022B0000, 0x022B0000, 0x022B0000, 0x82000000, 0x022B0000, 0x022B0000, 0x022B0000, 0x022B0000, 0x022B0000, "CHAO"], [0x82000000, 0x82000000, 0x82000000, 0x022C0000, 0x022C0000, 0x022C0000, 0x82000000, 0x022C0000, 0x022C0000, 0x022C0000, 0x022C0000, 0x022C0000, "CHU CHU"], [0x82000000, 0x82000000, 0x82000000, 0x022D0000, 0x022D0000, 0x022D0000, 0x82000000, 0x022D0000, 0x022D0000, 0x022D0000, 0x022D0000, 0x022D0000, "KAPU KAPU"], [0x82000000, 0x82000000, 0x82000000, 0x022E0000, 0x022E0000, 0x022E0000, 0x82000000, 0x022E0000, 0x022E0000, 0x022E0000, 0x022E0000, 0x022E0000, "ANGEL'S WING"], @@ -883,8 +883,9 @@ [0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x024F0000, "Cell of MAG 0505 (mag)"], [0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x02500000, "Cell of MAG 0506 (mag)"], [0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x02510000, "Cell of MAG 0507 (mag)"], - [0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x02280000, 0x02420000, 0x02420000, 0x02420000, 0x02420000, 0x02520000, "???? (MAG)"], + [0x82000000, 0x82000000, 0x82000000, 0x02390000, 0x02390000, 0x02390000, 0x02280000, 0x02420000, 0x02420000, 0x02420000, 0x02420000, 0x02520000, "???? (MAG)"], + // DC_NTE-- 11/2000--- DC_V1----- DC_V2----- PC_NTE---- PC_V2----- GC_NTE---- GC_V3----- GC_EP3_NTE GC_EP3---- XB_V3----- BB_V4----- NAME [0x03000000, 0x03000000, 0x03000000, 0x03000000, 0x03000000, 0x03000000, 0x03000000, 0x03000000, 0x03000000, 0x03000000, 0x03000000, 0x03000000, "Monomate"], [0x03000100, 0x03000100, 0x03000100, 0x03000100, 0x03000100, 0x03000100, 0x03000100, 0x03000100, 0x03000100, 0x03000100, 0x03000100, 0x03000100, "Dimate"], [0x03000200, 0x03000200, 0x03000200, 0x03000200, 0x03000200, 0x03000200, 0x03000200, 0x03000200, 0x03000200, 0x03000200, 0x03000200, 0x03000200, "Trimate"], @@ -1027,7 +1028,7 @@ [0x830C0100, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830E0000, 0x03100200, 0x03100200, 0x03100200, 0x03100200, 0x03100200, "Photon Crystal"], [0x830C0100, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830E0000, 0x83100000, 0x83100000, 0x83100000, 0x83100000, 0x03100300, "Secret Ticket"], [0x830C0100, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830E0000, 0x83100000, 0x83100000, 0x83100000, 0x83100000, 0x03100400, "Photon Ticket"], - [0x830C0100, 0x830D0500, 0x830D0500, 0x030E0700, 0x030E0700, 0x030E0700, 0x830E0000, 0x03120000, 0x03120000, 0x03120000, 0x03120000, 0x03120000, "Weapons Bronze Badge"], + [0x830C0100, 0x830D0500, 0x830D0500, 0x030E0700, 0x030E0700, 0x030E0700, 0x03120000, 0x03120000, 0x03120000, 0x03120000, 0x03120000, 0x03120000, "Weapons Bronze Badge"], [0x830C0100, 0x830D0500, 0x830D0500, 0x030E0800, 0x030E0800, 0x030E0800, 0x830E0000, 0x03120100, 0x03120100, 0x03120100, 0x03120100, 0x03120100, "Weapons Silver Badge"], [0x830C0100, 0x830D0500, 0x830D0500, 0x030E0900, 0x030E0900, 0x030E0900, 0x830E0000, 0x03120200, 0x03120200, 0x03120200, 0x03120200, 0x03120200, "Weapons Gold Badge"], [0x830C0100, 0x830D0500, 0x830D0500, 0x030E0A00, 0x030E0A00, 0x030E0A00, 0x830E0000, 0x03120300, 0x03120300, 0x03120300, 0x03120300, 0x03120300, "Weapons Crystal Badge"], @@ -1090,5 +1091,6 @@ [0x830C0100, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830E0000, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x03190100, "Team Points 1000"], [0x830C0100, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830E0000, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x03190200, "Team Points 5000"], [0x830C0100, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830E0000, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x03190300, "Team Points 10000"], - [0x830C0100, 0x830D0500, 0x830D0500, 0x030F0000, 0x030F0000, 0x030F0000, 0x03120000, 0x83170400, 0x83170400, 0x83170400, 0x83170400, 0x031A0000, "???? (TOOL)"], + [0x830C0100, 0x830D0500, 0x830D0500, 0x030F0000, 0x030F0000, 0x030F0000, 0x03130000, 0x03180000, 0x03180000, 0x03180000, 0x03180000, 0x031A0000, "???? (TOOL)"], + // DC_NTE-- 11/2000--- DC_V1----- DC_V2----- PC_NTE---- PC_V2----- GC_NTE---- GC_V3----- GC_EP3_NTE GC_EP3---- XB_V3----- BB_V4----- NAME ] diff --git a/tests/item-parameter-table.test.sh b/tests/item-parameter-table.test.sh new file mode 100755 index 00000000..ba111081 --- /dev/null +++ b/tests/item-parameter-table.test.sh @@ -0,0 +1,73 @@ +#!/bin/sh + +set -e + +EXECUTABLE="$1" +if [ -z "$EXECUTABLE" ]; then + EXECUTABLE="./newserv" +fi + +DIR=tests/item-parameter-tables + +echo "... DC NTE" +$EXECUTABLE decode-item-parameter-table --dc-nte $DIR/dc-nte.expected.bin --decompressed $DIR/dc-nte.json --hex +$EXECUTABLE encode-item-parameter-table --dc-nte $DIR/dc-nte.json $DIR/dc-nte.encoded.bin --decompressed +bindiff $DIR/dc-nte.expected.bin $DIR/dc-nte.encoded.bin + +echo "... DC 11/2000" +$EXECUTABLE decode-item-parameter-table --dc-11-2000 $DIR/dc-11-2000.expected.bin --decompressed $DIR/dc-11-2000.json --hex +$EXECUTABLE encode-item-parameter-table --dc-11-2000 $DIR/dc-11-2000.json $DIR/dc-11-2000.encoded.bin --decompressed +bindiff $DIR/dc-11-2000.expected.bin $DIR/dc-11-2000.encoded.bin + +echo "... DC V1" +$EXECUTABLE decode-item-parameter-table --dc-v1 $DIR/dc-v1.expected.bin --decompressed $DIR/dc-v1.json --hex +$EXECUTABLE encode-item-parameter-table --dc-v1 $DIR/dc-v1.json $DIR/dc-v1.encoded.bin --decompressed +bindiff $DIR/dc-v1.expected.bin $DIR/dc-v1.encoded.bin + +echo "... DC V2" +$EXECUTABLE decode-item-parameter-table --dc-v2 $DIR/dc-v2.expected.bin --decompressed $DIR/dc-v2.json --hex +$EXECUTABLE encode-item-parameter-table --dc-v2 $DIR/dc-v2.json $DIR/dc-v2.encoded.bin --decompressed +bindiff $DIR/dc-v2.expected.bin $DIR/dc-v2.encoded.bin + +echo "... PC NTE" +$EXECUTABLE decode-item-parameter-table --pc-nte $DIR/pc-nte.expected.bin --decompressed $DIR/pc-nte.json --hex +$EXECUTABLE encode-item-parameter-table --pc-nte $DIR/pc-nte.json $DIR/pc-nte.encoded.bin --decompressed +bindiff $DIR/pc-nte.expected.bin $DIR/pc-nte.encoded.bin + +echo "... PC V2" +$EXECUTABLE decode-item-parameter-table --pc-v2 $DIR/pc-v2.expected.bin --decompressed $DIR/pc-v2.json --hex +$EXECUTABLE encode-item-parameter-table --pc-v2 $DIR/pc-v2.json $DIR/pc-v2.encoded.bin --decompressed +bindiff $DIR/pc-v2.expected.bin $DIR/pc-v2.encoded.bin + +echo "... GC NTE" +$EXECUTABLE decode-item-parameter-table --gc-nte $DIR/gc-nte.expected.bin --decompressed $DIR/gc-nte.json --hex +$EXECUTABLE encode-item-parameter-table --gc-nte $DIR/gc-nte.json $DIR/gc-nte.encoded.bin --decompressed +bindiff $DIR/gc-nte.expected.bin $DIR/gc-nte.encoded.bin + +echo "... GC V3" +$EXECUTABLE decode-item-parameter-table --gc-v3 $DIR/gc-v3.expected.bin --decompressed $DIR/gc-v3.json --hex +$EXECUTABLE encode-item-parameter-table --gc-v3 $DIR/gc-v3.json $DIR/gc-v3.encoded.bin --decompressed +bindiff $DIR/gc-v3.expected.bin $DIR/gc-v3.encoded.bin + +echo "... GC Ep3 NTE" +$EXECUTABLE decode-item-parameter-table --gc-ep3-nte $DIR/gc-ep3-nte.expected.bin --decompressed $DIR/gc-ep3-nte.json --hex +$EXECUTABLE encode-item-parameter-table --gc-ep3-nte $DIR/gc-ep3-nte.json $DIR/gc-ep3-nte.encoded.bin --decompressed +bindiff $DIR/gc-ep3-nte.expected.bin $DIR/gc-ep3-nte.encoded.bin + +echo "... GC Ep3" +$EXECUTABLE decode-item-parameter-table --gc-ep3 $DIR/gc-ep3.expected.bin --decompressed $DIR/gc-ep3.json --hex +$EXECUTABLE encode-item-parameter-table --gc-ep3 $DIR/gc-ep3.json $DIR/gc-ep3.encoded.bin --decompressed +bindiff $DIR/gc-ep3.expected.bin $DIR/gc-ep3.encoded.bin + +echo "... XB" +$EXECUTABLE decode-item-parameter-table --xb-v3 $DIR/xb-v3.expected.bin --decompressed $DIR/xb-v3.json --hex +$EXECUTABLE encode-item-parameter-table --xb-v3 $DIR/xb-v3.json $DIR/xb-v3.encoded.bin --decompressed +bindiff $DIR/xb-v3.expected.bin $DIR/xb-v3.encoded.bin + +echo "... BB" +$EXECUTABLE decode-item-parameter-table --bb-v4 $DIR/bb-v4.expected.bin --decompressed $DIR/bb-v4.json --hex +$EXECUTABLE encode-item-parameter-table --bb-v4 $DIR/bb-v4.json $DIR/bb-v4.encoded.bin --decompressed +bindiff $DIR/bb-v4.expected.bin $DIR/bb-v4.encoded.bin + +echo "... clean up" +rm -f tests/item-parameter-tables/*.encoded.bin tests/item-parameter-tables/*.json diff --git a/tests/item-parameter-tables/bb-v4.expected.bin b/tests/item-parameter-tables/bb-v4.expected.bin new file mode 100644 index 00000000..69805772 Binary files /dev/null and b/tests/item-parameter-tables/bb-v4.expected.bin differ diff --git a/tests/item-parameter-tables/dc-11-2000.expected.bin b/tests/item-parameter-tables/dc-11-2000.expected.bin new file mode 100644 index 00000000..5086a404 Binary files /dev/null and b/tests/item-parameter-tables/dc-11-2000.expected.bin differ diff --git a/tests/item-parameter-tables/dc-nte.expected.bin b/tests/item-parameter-tables/dc-nte.expected.bin new file mode 100644 index 00000000..90445709 Binary files /dev/null and b/tests/item-parameter-tables/dc-nte.expected.bin differ diff --git a/tests/item-parameter-tables/dc-v1.expected.bin b/tests/item-parameter-tables/dc-v1.expected.bin new file mode 100644 index 00000000..86e88210 Binary files /dev/null and b/tests/item-parameter-tables/dc-v1.expected.bin differ diff --git a/tests/item-parameter-tables/dc-v2.expected.bin b/tests/item-parameter-tables/dc-v2.expected.bin new file mode 100644 index 00000000..5da46665 Binary files /dev/null and b/tests/item-parameter-tables/dc-v2.expected.bin differ diff --git a/tests/item-parameter-tables/gc-ep3-nte.expected.bin b/tests/item-parameter-tables/gc-ep3-nte.expected.bin new file mode 100644 index 00000000..e86ff836 Binary files /dev/null and b/tests/item-parameter-tables/gc-ep3-nte.expected.bin differ diff --git a/tests/item-parameter-tables/gc-ep3.expected.bin b/tests/item-parameter-tables/gc-ep3.expected.bin new file mode 100644 index 00000000..e86ff836 Binary files /dev/null and b/tests/item-parameter-tables/gc-ep3.expected.bin differ diff --git a/tests/item-parameter-tables/gc-nte.expected.bin b/tests/item-parameter-tables/gc-nte.expected.bin new file mode 100644 index 00000000..7cd88ba4 Binary files /dev/null and b/tests/item-parameter-tables/gc-nte.expected.bin differ diff --git a/tests/item-parameter-tables/gc-v3.expected.bin b/tests/item-parameter-tables/gc-v3.expected.bin new file mode 100644 index 00000000..e86ff836 Binary files /dev/null and b/tests/item-parameter-tables/gc-v3.expected.bin differ diff --git a/tests/item-parameter-tables/pc-nte.expected.bin b/tests/item-parameter-tables/pc-nte.expected.bin new file mode 100644 index 00000000..5da46665 Binary files /dev/null and b/tests/item-parameter-tables/pc-nte.expected.bin differ diff --git a/tests/item-parameter-tables/pc-v2.expected.bin b/tests/item-parameter-tables/pc-v2.expected.bin new file mode 100644 index 00000000..5da46665 Binary files /dev/null and b/tests/item-parameter-tables/pc-v2.expected.bin differ diff --git a/tests/item-parameter-tables/xb-v3.expected.bin b/tests/item-parameter-tables/xb-v3.expected.bin new file mode 100644 index 00000000..10e04962 Binary files /dev/null and b/tests/item-parameter-tables/xb-v3.expected.bin differ