diff --git a/CMakeLists.txt b/CMakeLists.txt index 70c86951..5773b953 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,6 +97,7 @@ set(SOURCES src/LevelTable.cc src/Lobby.cc src/Loggers.cc + src/MagEvolutionTable.cc src/Main.cc src/Map.cc src/Menu.cc diff --git a/src/BMLArchive.cc b/src/BMLArchive.cc index 0b441cf2..a3dd0eb2 100644 --- a/src/BMLArchive.cc +++ b/src/BMLArchive.cc @@ -14,11 +14,9 @@ struct BMLHeaderT { parray unknown_a1; U32T num_entries; parray unknown_a2; -} __attribute__((packed)); +} __packed_ws_be__(BMLHeaderT, 0x40); using BMLHeader = BMLHeaderT; using BMLHeaderBE = BMLHeaderT; -check_struct_size(BMLHeader, 0x40); -check_struct_size(BMLHeaderBE, 0x40); template struct BMLHeaderEntryT { @@ -29,11 +27,9 @@ struct BMLHeaderEntryT { U32T compressed_gvm_size; U32T decompressed_gvm_size; parray unknown_a2; -} __attribute__((packed)); +} __packed_ws_be__(BMLHeaderEntryT, 0x40); using BMLHeaderEntry = BMLHeaderEntryT; using BMLHeaderEntryBE = BMLHeaderEntryT; -check_struct_size(BMLHeaderEntry, 0x40); -check_struct_size(BMLHeaderEntryBE, 0x40); template void BMLArchive::load_t() { diff --git a/src/ChoiceSearch.hh b/src/ChoiceSearch.hh index 2af70a5f..3d6b988b 100644 --- a/src/ChoiceSearch.hh +++ b/src/ChoiceSearch.hh @@ -40,11 +40,9 @@ struct ChoiceSearchConfigT { } return ret; } -} __attribute__((packed)); +} __packed_ws_be__(ChoiceSearchConfigT, 0x18); using ChoiceSearchConfig = ChoiceSearchConfigT; using ChoiceSearchConfigBE = ChoiceSearchConfigT; -check_struct_size(ChoiceSearchConfig, 0x18); -check_struct_size(ChoiceSearchConfigBE, 0x18); struct ChoiceSearchCategory { struct Choice { diff --git a/src/Client.hh b/src/Client.hh index 5344214c..7ff49167 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -158,7 +158,7 @@ public: std::weak_ptr lobby; uint8_t lobby_client_id = 0; uint8_t lobby_arrow_color = 0; - int64_t preferred_lobby_id = -1; // <0 = no preference + int64_t preferred_lobby_id = -1; // <0 = none chosen asio::steady_timer save_game_data_timer; asio::steady_timer send_ping_timer; diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index e394edf0..7817681a 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -251,14 +251,13 @@ struct S_StartFileDownloads_Patch_11 { // 14 (S->C): Reconnect // Same format and usage as command 19 on the game server (described below), except the port field is big-endian. -template +template struct S_ReconnectT { be_uint32_t address = 0; - PortT port = 0; + U16T port = 0; le_uint16_t unused = 0; -} __attribute__((packed)); -using S_Reconnect_Patch_14 = S_ReconnectT; -check_struct_size(S_Reconnect_Patch_14, 0x08); +} __packed_ws_be__(S_ReconnectT, 0x08); +using S_Reconnect_Patch_14 = S_ReconnectT; // 15 (S->C): Login failure // No arguments. The client shows a message like "Incorrect game ID or password" and disconnects. @@ -724,8 +723,7 @@ struct C_WriteFileConfirmation_V3_BB_13_A7 { // connection; the server should send an appropriate command to enable it when the client connects. PSO Xbox seems to // ignore the address field, which makes sense given its networking architecture. -using S_Reconnect_19 = S_ReconnectT; -check_struct_size(S_Reconnect_19, 8); +using S_Reconnect_19 = S_ReconnectT; // Sylverant implements an IPv6 version of this command, but it's not obvious why. IPv6 technically did exist as a // draft standard at the time of PSO's development, but it wasn't widely used until over a decade later. IPv6 support @@ -3767,12 +3765,10 @@ struct G_UpdateEnemyStateT_6x0A { // 40000000 = entity is object (some entities have both this and 20000000 set; this appears to make TWindowLockOn // not show anything but the entity is still attackable, see TWindowLockOn_should_show_for_entity) // 80000000 = entity is item - typename std::conditional_t game_flags = 0; -} __attribute__((packed)); + U32T game_flags = 0; +} __packed_ws_be__(G_UpdateEnemyStateT_6x0A, 0x0C); using G_UpdateEnemyState_GC_6x0A = G_UpdateEnemyStateT_6x0A; using G_UpdateEnemyState_DC_PC_XB_BB_6x0A = G_UpdateEnemyStateT_6x0A; -check_struct_size(G_UpdateEnemyState_GC_6x0A, 0x0C); -check_struct_size(G_UpdateEnemyState_DC_PC_XB_BB_6x0A, 0x0C); // 6x0B: Update object state @@ -3838,11 +3834,9 @@ struct G_DragonBossActionsT_6x12 { le_uint32_t target_client_id = 0xFFFF; // 0xFFFF (not 0xFFFFFFFF) means no target F32T x = 0.0f; F32T z = 0.0f; -} __attribute__((packed)); +} __packed_ws_be__(G_DragonBossActionsT_6x12, 0x14); using G_DragonBossActions_DC_PC_XB_BB_6x12 = G_DragonBossActionsT_6x12; using G_DragonBossActions_GC_6x12 = G_DragonBossActionsT_6x12; -check_struct_size(G_DragonBossActions_DC_PC_XB_BB_6x12, 0x14); -check_struct_size(G_DragonBossActions_GC_6x12, 0x14); // 6x13: De Rol Le boss actions (not valid on Episode 3) @@ -4875,11 +4869,9 @@ struct G_WordSelectT_6x74 { uint8_t size = 0; U16T client_id = 0; WordSelectMessage message; -} __attribute__((packed)); +} __packed_ws_be__(G_WordSelectT_6x74, 0x20); using G_WordSelect_6x74 = G_WordSelectT_6x74; using G_WordSelectBE_6x74 = G_WordSelectT_6x74; -check_struct_size(G_WordSelect_6x74, 0x20); -check_struct_size(G_WordSelectBE_6x74, 0x20); // 6x75: Update quest flag // This command does nothing on Episode 3. @@ -5007,11 +4999,9 @@ struct G_BattleScoresT_6x7F { } __packed_ws__(Entry, 8); G_UnusedHeader header; parray entries; -} __attribute__((packed)); +} __packed_ws_be__(G_BattleScoresT_6x7F, 0x24); using G_BattleScores_6x7F = G_BattleScoresT_6x7F; using G_BattleScoresBE_6x7F = G_BattleScoresT_6x7F; -check_struct_size(G_BattleScores_6x7F, 0x24); -check_struct_size(G_BattleScoresBE_6x7F, 0x24); // 6x80: Trigger trap (not valid on Episode 3) @@ -5370,11 +5360,9 @@ struct G_GolDragonBossActionsT_6xA8 { F32T z = 0.0f; uint8_t unknown_a5 = 0; parray unused; -} __attribute__((packed)); +} __packed_ws_be__(G_GolDragonBossActionsT_6xA8, 0x18); using G_GolDragonBossActions_XB_BB_6xA8 = G_GolDragonBossActionsT_6xA8; using G_GolDragonBossActions_GC_6xA8 = G_GolDragonBossActionsT_6xA8; -check_struct_size(G_GolDragonBossActions_XB_BB_6xA8, 0x18); -check_struct_size(G_GolDragonBossActions_GC_6xA8, 0x18); // 6xA9: Barba Ray boss actions (not valid on pre-V3 or Episode 3) diff --git a/src/CommonFileFormats.hh b/src/CommonFileFormats.hh index 539701bd..60e8cfe6 100644 --- a/src/CommonFileFormats.hh +++ b/src/CommonFileFormats.hh @@ -131,11 +131,9 @@ struct ArrayRefT { /* 00 */ U32T count; /* 04 */ U32T offset; /* 08 */ -} __attribute__((packed)); +} __packed_ws_be__(ArrayRefT, 8); using ArrayRef = ArrayRefT; using ArrayRefBE = ArrayRefT; -check_struct_size(ArrayRef, 8); -check_struct_size(ArrayRefBE, 8); template struct RELFileFooterT { @@ -159,8 +157,6 @@ struct RELFileFooterT { parray, 2> unused1; U32T root_offset = 0; parray, 3> unused2; -} __attribute__((packed)); +} __packed_ws_be__(RELFileFooterT, 0x20); using RELFileFooter = RELFileFooterT; using RELFileFooterBE = RELFileFooterT; -check_struct_size(RELFileFooter, 0x20); -check_struct_size(RELFileFooterBE, 0x20); diff --git a/src/CommonItemSet.hh b/src/CommonItemSet.hh index 85a4fe82..a3b3c1aa 100644 --- a/src/CommonItemSet.hh +++ b/src/CommonItemSet.hh @@ -244,11 +244,9 @@ public: /* 50 */ U32T box_item_class_prob_table_offset; // There are several unused fields here. - } __attribute__((packed)); + } __packed_ws_be__(OffsetsT, 0x54); using Offsets = OffsetsT; using OffsetsBE = OffsetsT; - check_struct_size(Offsets, 0x54); - check_struct_size(OffsetsBE, 0x54); }; bool operator==(const CommonItemSet& other) const = default; @@ -337,7 +335,6 @@ public: ValueT value; WeightT weight; } __attribute__((packed)); - using WeightTableEntry8 = WeightTableEntry; using WeightTableEntry32 = WeightTableEntry; check_struct_size(WeightTableEntry8, 2); diff --git a/src/GSLArchive.cc b/src/GSLArchive.cc index 562df76c..3fa21d30 100644 --- a/src/GSLArchive.cc +++ b/src/GSLArchive.cc @@ -15,12 +15,9 @@ struct GSLHeaderEntryT { U32T offset; // In pages, so actual offset is this * 0x800 U32T size; uint64_t unused; -} __attribute__((packed)); - +} __packed_ws_be__(GSLHeaderEntryT, 0x30); using GSLHeaderEntry = GSLHeaderEntryT; using GSLHeaderEntryBE = GSLHeaderEntryT; -check_struct_size(GSLHeaderEntry, 0x30); -check_struct_size(GSLHeaderEntryBE, 0x30); template void GSLArchive::load_t() { diff --git a/src/ItemCreator.cc b/src/ItemCreator.cc index 4a83e576..81a8f7c4 100644 --- a/src/ItemCreator.cc +++ b/src/ItemCreator.cc @@ -835,7 +835,7 @@ uint8_t ItemCreator::choose_weapon_special(uint8_t det) { uint8_t det2 = this->rand_int(maxes[det]); this->log.info_f("Choosing special with det {:02X} and det2 {:02X}", det, det2); size_t index = 0; - for (size_t z = 1; z < this->item_parameter_table->num_specials; z++) { + for (size_t z = 1; z < this->item_parameter_table->num_specials(); z++) { if (det + 1 == this->item_parameter_table->get_special_stars(z)) { if (index == det2) { this->log.info_f("Chose special {:02X}", z); diff --git a/src/ItemData.cc b/src/ItemData.cc index 9a1850a2..c4ac5b99 100644 --- a/src/ItemData.cc +++ b/src/ItemData.cc @@ -486,7 +486,7 @@ void ItemData::encode_for_version(Version to_version, shared_ptrdata1[1] > 0x26)) { if (this->data1[1] < 0x89) { this->data1[5] = this->data1[1]; - this->data1[1] = item_parameter_table->get_weapon_v1_replacement(this->data1[1]); + this->data1[1] = item_parameter_table->get_weapon_class(this->data1[1]); if (this->data1[1] == 0x00) { this->data1[1] = 0x0F; } diff --git a/src/ItemNameIndex.cc b/src/ItemNameIndex.cc index c22ab83b..cf555c03 100644 --- a/src/ItemNameIndex.cc +++ b/src/ItemNameIndex.cc @@ -787,9 +787,9 @@ void ItemNameIndex::print_table(FILE* stream) const { auto pmt = this->item_parameter_table; phosg::fwrite_fmt(stream, "WEAPONS\n"); - phosg::fwrite_fmt(stream, " CODE => ---ID--- TYPE SKIN POINTS FLAG ATPLO ATPHI ATPRQ MSTRQ ATARQ -MST- GND PH SP ATA SB(S1:AMT1,S2:AMT2) PJ 1X 1Y 2X 2Y CL --A1-- A4 A5 TB BF V1 ST* USL ---DIVISOR--- NAME\n"); - for (size_t data1_1 = 0; data1_1 < pmt->num_weapon_classes; data1_1++) { - uint8_t v1_replacement = pmt->get_weapon_v1_replacement(data1_1); + phosg::fwrite_fmt(stream, " CODE => ---ID--- TYPE SKIN POINTS FLAG ATPLO ATPHI ATPRQ MSTRQ ATARQ -MST- GND PH SP ATA SB(S1:AMT1,S2:AMT2) PJ 1X 1Y 2X 2Y CR --A1-- A4 A5 TB BF CL ST* USL ---DIVISOR--- NAME\n"); + for (size_t data1_1 = 0; data1_1 < pmt->num_weapon_classes(); data1_1++) { + uint8_t weapon_class = pmt->get_weapon_class(data1_1); float sale_divisor = pmt->get_sale_divisor(0x00, data1_1); string divisor_str = std::format("{:g}", sale_divisor); divisor_str.resize(13, ' '); @@ -797,7 +797,7 @@ void ItemNameIndex::print_table(FILE* stream) const { size_t data1_2_limit = pmt->num_weapons_in_class(data1_1); for (size_t data1_2 = 0; data1_2 < data1_2_limit; data1_2++) { const auto& w = pmt->get_weapon(data1_1, data1_2); - uint8_t stars = pmt->get_item_stars(w.base.id); + uint8_t stars = pmt->get_item_stars(w.id); bool is_unsealable = pmt->is_unsealable_item(0x00, data1_1, data1_2); ItemData item; @@ -810,10 +810,10 @@ void ItemNameIndex::print_table(FILE* stream) const { phosg::fwrite_fmt(stream, " 00{:02X}{:02X} => {:08X} {:04X} {:04X} {:6} {:04X} {:5} {:5} {:5} {:5} {:5} {:5} {:3} {:02X} {:02X} {:3} {:02X}({:02X}:{:04X},{:02X}:{:04X}) {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}{:02X}{:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:2}* {} {} {}\n", data1_1, data1_2, - w.base.id, - w.base.type, - w.base.skin, - w.base.team_points, + w.id, + w.type, + w.skin, + w.team_points, w.class_flags, w.atp_min, w.atp_max, @@ -826,10 +826,10 @@ void ItemNameIndex::print_table(FILE* stream) const { w.special, w.ata, w.stat_boost_entry_index, - stat_boost.stats[0], - stat_boost.amounts[0], - stat_boost.stats[1], - stat_boost.amounts[1], + stat_boost.stat1, + stat_boost.amount1, + stat_boost.stat2, + stat_boost.amount2, w.projectile, w.trail1_x, w.trail1_y, @@ -843,7 +843,7 @@ void ItemNameIndex::print_table(FILE* stream) const { w.unknown_a5, w.tech_boost, w.behavior_flags, - v1_replacement, + weapon_class, stars, is_unsealable ? "YES" : " no", divisor_str, @@ -861,7 +861,7 @@ void ItemNameIndex::print_table(FILE* stream) const { size_t data1_2_limit = pmt->num_armors_or_shields_in_class(data1_1); for (size_t data1_2 = 0; data1_2 < data1_2_limit; data1_2++) { const auto& a = pmt->get_armor_or_shield(data1_1, data1_2); - uint8_t stars = pmt->get_item_stars(a.base.id); + uint8_t stars = pmt->get_item_stars(a.id); ItemData item; item.data1[0] = 0x01; @@ -873,10 +873,10 @@ void ItemNameIndex::print_table(FILE* stream) const { phosg::fwrite_fmt(stream, " 01{:02X}{:02X} => {:08X} {:04X} {:04X} {:6} {:5} {:5} {:02X} {:02X} {:04X} {:3} {:3} {:3} {:3} {:3} {:3} {:3} {:3} {:02X}({:02X}:{:04X},{:02X}:{:04X}) {:02X} {:02X} {:02X} {:2}* {} {}\n", data1_1, data1_2, - a.base.id, - a.base.type, - a.base.skin, - a.base.team_points, + a.id, + a.type, + a.skin, + a.team_points, a.dfp, a.evp, a.block_particle, @@ -891,10 +891,10 @@ void ItemNameIndex::print_table(FILE* stream) const { a.dfp_range, a.evp_range, a.stat_boost_entry_index, - stat_boost.stats[0], - stat_boost.amounts[0], - stat_boost.stats[1], - stat_boost.amounts[1], + stat_boost.stat1, + stat_boost.amount1, + stat_boost.stat2, + stat_boost.amount2, a.tech_boost, a.flags_type, a.unknown_a4, @@ -914,7 +914,7 @@ void ItemNameIndex::print_table(FILE* stream) const { size_t data1_2_limit = pmt->num_units(); for (size_t data1_2 = 0; data1_2 < data1_2_limit; data1_2++) { const auto& u = pmt->get_unit(data1_2); - uint8_t stars = pmt->get_item_stars(u.base.id); + uint8_t stars = pmt->get_item_stars(u.id); ItemData item; item.data1[0] = 0x01; @@ -924,10 +924,10 @@ void ItemNameIndex::print_table(FILE* stream) const { phosg::fwrite_fmt(stream, " 0103{:02X} => {:08X} {:04X} {:04X} {:6} {:04X} {:5} {:6} {:2}* {} {}\n", data1_2, - u.base.id, - u.base.type, - u.base.skin, - u.base.team_points, + u.id, + u.type, + u.skin, + u.team_points, u.stat, u.stat_amount, u.modifier_amount, @@ -956,10 +956,10 @@ void ItemNameIndex::print_table(FILE* stream) const { phosg::fwrite_fmt(stream, " 02{:02X}00 => {:08X} {:04X} {:04X} {:6} {:04X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:04X} {} {}\n", data1_1, - m.base.id, - m.base.type, - m.base.skin, - m.base.team_points, + m.id, + m.type, + m.skin, + m.team_points, m.feed_table, m.photon_blast, m.activation, @@ -979,7 +979,7 @@ void ItemNameIndex::print_table(FILE* stream) const { phosg::fwrite_fmt(stream, "TOOLS\n"); phosg::fwrite_fmt(stream, " CODE => ---ID--- TYPE SKIN POINTS COUNT TECH -COST- ITEMFLAG ---DIVISOR--- NAME\n"); - for (size_t data1_1 = 0; data1_1 < pmt->num_tool_classes; data1_1++) { + for (size_t data1_1 = 0; data1_1 < pmt->num_tool_classes(); data1_1++) { float sale_divisor = pmt->get_sale_divisor(0x03, data1_1); string divisor_str = std::format("{:g}", sale_divisor); divisor_str.resize(13, ' '); @@ -998,10 +998,10 @@ void ItemNameIndex::print_table(FILE* stream) const { phosg::fwrite_fmt(stream, " 03{:02X}{:02X} => {:08X} {:04X} {:04X} {:6} {:5} {:04X} {:6} {:08X} {} {}\n", data1_1, data1_2, - t.base.id, - t.base.type, - t.base.skin, - t.base.team_points, + t.id, + t.type, + t.skin, + t.team_points, t.amount, t.tech, t.cost, @@ -1041,7 +1041,7 @@ void ItemNameIndex::print_table(FILE* stream) const { phosg::fwrite_fmt(stream, "SPECIAL DEFINITIONS\n"); phosg::fwrite_fmt(stream, " SPECIAL => TYPE COUNT ST* NAME\n"); - for (size_t index = 0; index < pmt->num_specials; index++) { + for (size_t index = 0; index < pmt->num_specials(); index++) { const auto& sp = pmt->get_special(index); uint8_t stars = pmt->get_special_stars(index); const char* name = ""; diff --git a/src/ItemParameterTable.cc b/src/ItemParameterTable.cc index c4d91305..05356337 100644 --- a/src/ItemParameterTable.cc +++ b/src/ItemParameterTable.cc @@ -39,114 +39,664 @@ const char* phosg::name_for_enum(ServerDropMode value) { } } -ItemParameterTable::ItemParameterTable(shared_ptr data, Version version) - : version(version), - data(data), - r(*data), - offsets_dc_protos(nullptr), - offsets_v1_v2(nullptr), - offsets_gc_nte(nullptr), - offsets_v3_le(nullptr), - offsets_v3_be(nullptr), - offsets_v4(nullptr) { - size_t offset_table_offset = is_big_endian(version) - ? this->r.pget_u32b(this->data->size() - 0x10) - : this->r.pget_u32l(this->data->size() - 0x10); - - switch (this->version) { - case Version::DC_NTE: { - this->offsets_dc_protos = &this->r.pget(offset_table_offset); - this->num_weapon_classes = 0x27; - this->num_tool_classes = 0x0D; - this->item_stars_first_id = 0x22; - this->item_stars_last_id = 0x168; - this->special_stars_begin_index = 0xAA; - this->num_specials = 0x28; - // TODO: Check if first_rare_mag_index is the same on this version - break; - } - case Version::DC_11_2000: { - this->offsets_dc_protos = &this->r.pget(offset_table_offset); - this->num_weapon_classes = 0x27; - this->num_tool_classes = 0x0E; - this->item_stars_first_id = 0x26; - this->item_stars_last_id = 0x16C; - this->special_stars_begin_index = 0xAE; - this->num_specials = 0x28; - // TODO: Check if first_rare_mag_index is the same on this version - break; - } - case Version::DC_V1: { - this->offsets_v1_v2 = &this->r.pget(offset_table_offset); - this->num_weapon_classes = 0x27; - this->num_tool_classes = 0x0E; - this->item_stars_first_id = 0x26; - this->item_stars_last_id = 0x16C; - this->special_stars_begin_index = 0xAE; - this->num_specials = 0x29; - // TODO: Check if first_rare_mag_index is the same on this version - break; - } - case Version::DC_V2: - case Version::PC_NTE: - case Version::PC_V2: { - this->offsets_v1_v2 = &this->r.pget(offset_table_offset); - this->num_weapon_classes = 0x89; - this->num_tool_classes = 0x10; - this->item_stars_first_id = 0x4E; - this->item_stars_last_id = 0x215; - this->special_stars_begin_index = 0x138; - this->num_specials = 0x29; - break; - } - - case Version::GC_NTE: { - this->offsets_gc_nte = &this->r.pget(offset_table_offset); - this->num_weapon_classes = 0x8D; - this->num_tool_classes = 0x13; - this->item_stars_first_id = 0x76; - this->item_stars_last_id = 0x298; - this->special_stars_begin_index = 0x1A3; - this->num_specials = 0x29; - break; - } - - case Version::GC_EP3_NTE: - case Version::GC_EP3: - case Version::GC_V3: - case Version::XB_V3: { - if (is_big_endian(this->version)) { - this->offsets_v3_be = &this->r.pget(offset_table_offset); - } else { - this->offsets_v3_le = &this->r.pget(offset_table_offset); - } - this->num_weapon_classes = 0xAA; - this->num_tool_classes = 0x18; - this->item_stars_first_id = 0x94; - this->item_stars_last_id = 0x2F7; - this->special_stars_begin_index = 0x1CB; - this->num_specials = 0x29; - break; - } - - case Version::BB_V4: { - this->offsets_v4 = &this->r.pget(offset_table_offset); - this->num_weapon_classes = 0xED; - this->num_tool_classes = 0x1B; - this->item_stars_first_id = 0xB1; - this->item_stars_last_id = 0x437; - this->special_stars_begin_index = 0x256; - this->num_specials = 0x29; - break; - } - default: - throw logic_error("invalid item parameter table version"); +template +struct ItemBaseV2T { + /* 00 */ U32T id = 0xFFFFFFFF; + /* 04 */ + void parse_into(ItemParameterTable::ItemBase& ret) const { + ret.id = this->id; } +} __packed_ws_be__(ItemBaseV2T, 4); - this->first_rare_mag_index = 0x28; -} +template +struct ItemBaseV3T : ItemBaseV2T { + /* 04 */ U16T type = 0; + /* 06 */ U16T skin = 0; + /* 08 */ + void parse_into(ItemParameterTable::ItemBase& ret) const { + this->ItemBaseV2T::parse_into(ret); + ret.type = this->type; + ret.skin = this->skin; + } +} __packed_ws_be__(ItemBaseV3T, 8); -set ItemParameterTable::compute_all_valid_primary_identifiers() const { +struct ItemBaseV4 : ItemBaseV3T { + /* 08 */ le_uint32_t team_points = 0; + /* 0C */ + void parse_into(ItemParameterTable::ItemBase& ret) const { + this->ItemBaseV3T::parse_into(ret); + ret.team_points = this->team_points; + } +} __packed_ws__(ItemBaseV4, 0x0C); + +struct WeaponDCProtos { + /* 00 */ ItemBaseV2T base; + /* 04 */ le_uint16_t class_flags = 0; + /* 06 */ le_uint16_t atp_min = 0; + /* 08 */ le_uint16_t atp_max = 0; + /* 0A */ le_uint16_t atp_required = 0; + /* 0C */ le_uint16_t mst_required = 0; + /* 0E */ le_uint16_t ata_required = 0; + /* 10 */ uint8_t max_grind = 0; + /* 11 */ uint8_t photon = 0; + /* 12 */ uint8_t special = 0; + /* 13 */ uint8_t ata = 0; + /* 14 */ + operator ItemParameterTable::Weapon() const { + ItemParameterTable::Weapon ret; + this->base.parse_into(ret); + ret.class_flags = this->class_flags; + ret.atp_min = this->atp_min; + ret.atp_max = this->atp_max; + ret.atp_required = this->atp_required; + ret.mst_required = this->mst_required; + ret.ata_required = this->ata_required; + ret.max_grind = this->max_grind; + ret.photon = this->photon; + ret.special = this->special; + ret.ata = this->ata; + return ret; + } +} __packed_ws__(WeaponDCProtos, 0x14); + +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 */ + operator ItemParameterTable::Weapon() const { + ItemParameterTable::Weapon ret = this->WeaponDCProtos::operator ItemParameterTable::Weapon(); + ret.stat_boost_entry_index = this->stat_boost_entry_index; + return ret; + } +} __packed_ws__(WeaponV1V2, 0x18); + +template +struct WeaponGCNTET { + /* 00 */ ItemBaseV3T base; + /* 08 */ U16T class_flags = 0; + /* 0A */ U16T atp_min = 0; + /* 0C */ U16T atp_max = 0; + /* 0E */ U16T atp_required = 0; + /* 10 */ U16T mst_required = 0; + /* 12 */ U16T ata_required = 0; + /* 14 */ U16T mst = 0; + /* 16 */ uint8_t max_grind = 0; + /* 17 */ uint8_t photon = 0; + /* 18 */ uint8_t special = 0; + /* 19 */ uint8_t ata = 0; + /* 1A */ uint8_t stat_boost_entry_index = 0; + /* 1B */ uint8_t projectile = 0; + /* 1C */ int8_t trail1_x = 0; + /* 1D */ int8_t trail1_y = 0; + /* 1E */ int8_t trail2_x = 0; + /* 1F */ int8_t trail2_y = 0; + /* 20 */ int8_t color = 0; + /* 21 */ parray unknown_a1 = 0; + /* 24 */ + operator ItemParameterTable::Weapon() const { + ItemParameterTable::Weapon ret; + this->base.parse_into(ret); + ret.class_flags = this->class_flags; + ret.atp_min = this->atp_min; + ret.atp_max = this->atp_max; + ret.atp_required = this->atp_required; + ret.mst_required = this->mst_required; + ret.ata_required = this->ata_required; + ret.mst = this->mst; + ret.max_grind = this->max_grind; + ret.photon = this->photon; + ret.special = this->special; + ret.ata = this->ata; + ret.stat_boost_entry_index = this->stat_boost_entry_index; + ret.projectile = this->projectile; + ret.trail1_x = this->trail1_x; + ret.trail1_y = this->trail1_y; + ret.trail2_x = this->trail2_x; + ret.trail2_y = this->trail2_y; + ret.color = this->color; + ret.unknown_a1 = this->unknown_a1; + return ret; + } +} __packed_ws_be__(WeaponGCNTET, 0x24); +using WeaponGCNTE = WeaponGCNTET; + +template +struct WeaponV3T : WeaponGCNTET { + /* 24 */ uint8_t unknown_a4 = 0; + /* 25 */ uint8_t unknown_a5 = 0; + /* 26 */ uint8_t tech_boost = 0; + /* 27 */ uint8_t behavior_flags = 0; + /* 28 */ + operator ItemParameterTable::Weapon() const { + ItemParameterTable::Weapon ret = this->WeaponGCNTET::operator ItemParameterTable::Weapon(); + ret.unknown_a4 = this->unknown_a4; + ret.unknown_a5 = this->unknown_a5; + ret.tech_boost = this->tech_boost; + ret.behavior_flags = this->behavior_flags; + return ret; + } +} __packed_ws_be__(WeaponV3T, 0x28); +using WeaponGC = WeaponV3T; +using WeaponXB = WeaponV3T; + +struct WeaponV4 { + /* 00 */ ItemBaseV4 base; + /* 0C */ le_uint16_t class_flags = 0x00FF; + /* 0E */ le_uint16_t atp_min = 0; + /* 10 */ le_uint16_t atp_max = 0; + /* 12 */ le_uint16_t atp_required = 0; + /* 14 */ le_uint16_t mst_required = 0; + /* 16 */ le_uint16_t ata_required = 0; + /* 18 */ le_uint16_t mst = 0; + /* 1A */ uint8_t max_grind = 0; + /* 1B */ uint8_t photon = 0; + /* 1C */ uint8_t special = 0; + /* 1D */ uint8_t ata = 0; + /* 1E */ uint8_t stat_boost_entry_index = 0; + /* 1F */ uint8_t projectile = 0; + /* 20 */ int8_t trail1_x = 0; + /* 21 */ int8_t trail1_y = 0; + /* 22 */ int8_t trail2_x = 0; + /* 23 */ int8_t trail2_y = 0; + /* 24 */ int8_t color = 0; + /* 25 */ parray unknown_a1 = 0; + /* 28 */ uint8_t unknown_a4 = 0; + /* 29 */ uint8_t unknown_a5 = 0; + /* 2A */ uint8_t tech_boost = 0; + /* 2B */ uint8_t behavior_flags = 0; + /* 2C */ + operator ItemParameterTable::Weapon() const { + ItemParameterTable::Weapon ret; + this->base.parse_into(ret); + ret.class_flags = this->class_flags; + ret.atp_min = this->atp_min; + ret.atp_max = this->atp_max; + ret.atp_required = this->atp_required; + ret.mst_required = this->mst_required; + ret.ata_required = this->ata_required; + ret.mst = this->mst; + ret.max_grind = this->max_grind; + ret.photon = this->photon; + ret.special = this->special; + ret.ata = this->ata; + ret.stat_boost_entry_index = this->stat_boost_entry_index; + ret.projectile = this->projectile; + ret.trail1_x = this->trail1_x; + ret.trail1_y = this->trail1_y; + ret.trail2_x = this->trail2_x; + ret.trail2_y = this->trail2_y; + ret.color = this->color; + ret.unknown_a1 = this->unknown_a1; + ret.unknown_a4 = this->unknown_a4; + ret.unknown_a5 = this->unknown_a5; + ret.tech_boost = this->tech_boost; + ret.behavior_flags = this->behavior_flags; + return ret; + } +} __packed_ws__(WeaponV4, 0x2C); + +template +struct ArmorOrShieldT { + /* V1/V2 offsets */ + /* 00 */ BaseT base; + /* 04 */ U16T dfp = 0; + /* 06 */ U16T evp = 0; + /* 08 */ uint8_t block_particle = 0; + /* 09 */ uint8_t block_effect = 0; + /* 0A */ U16T class_flags = 0x00FF; + /* 0C */ uint8_t required_level = 0; + /* 0D */ uint8_t efr = 0; + /* 0E */ uint8_t eth = 0; + /* 0F */ uint8_t eic = 0; + /* 10 */ uint8_t edk = 0; + /* 11 */ uint8_t elt = 0; + /* 12 */ uint8_t dfp_range = 0; + /* 13 */ uint8_t evp_range = 0; + /* 14 */ + operator ItemParameterTable::ArmorOrShield() const { + ItemParameterTable::ArmorOrShield ret; + this->base.parse_into(ret); + ret.dfp = this->dfp; + ret.evp = this->evp; + ret.block_particle = this->block_particle; + ret.block_effect = this->block_effect; + ret.class_flags = this->class_flags; + ret.required_level = this->required_level; + ret.efr = this->efr; + ret.eth = this->eth; + ret.eic = this->eic; + ret.edk = this->edk; + ret.elt = this->elt; + ret.dfp_range = this->dfp_range; + ret.evp_range = this->evp_range; + return ret; + } +} __attribute__((packed)); +static_assert(sizeof(ArmorOrShieldT, false>) == 0x14, "Structure size is incorrect"); +static_assert(sizeof(ArmorOrShieldT, true>) == 0x14, "Structure size is incorrect"); +static_assert(sizeof(ArmorOrShieldT, false>) == 0x18, "Structure size is incorrect"); +static_assert(sizeof(ArmorOrShieldT, true>) == 0x18, "Structure size is incorrect"); +static_assert(sizeof(ArmorOrShieldT) == 0x1C, "Structure size is incorrect"); +using ArmorOrShieldDCProtos = ArmorOrShieldT, false>; + +template +struct ArmorOrShieldFinalT : ArmorOrShieldT { + /* 14 */ uint8_t stat_boost_entry_index = 0; + /* 15 */ uint8_t tech_boost = 0; + /* 16 */ uint8_t flags_type = 0; + /* 17 */ uint8_t unknown_a4 = 0; + /* 18 */ + operator ItemParameterTable::ArmorOrShield() const { + ItemParameterTable::ArmorOrShield ret = this->ArmorOrShieldT::operator ItemParameterTable::ArmorOrShield(); + ret.stat_boost_entry_index = this->stat_boost_entry_index; + ret.tech_boost = this->tech_boost; + ret.flags_type = this->flags_type; + ret.unknown_a4 = this->unknown_a4; + return ret; + } +} __attribute__((packed)); +static_assert(sizeof(ArmorOrShieldFinalT, false>) == 0x18, "Structure size is incorrect"); +static_assert(sizeof(ArmorOrShieldFinalT, true>) == 0x18, "Structure size is incorrect"); +static_assert(sizeof(ArmorOrShieldFinalT, false>) == 0x1C, "Structure size is incorrect"); +static_assert(sizeof(ArmorOrShieldFinalT, true>) == 0x1C, "Structure size is incorrect"); +static_assert(sizeof(ArmorOrShieldFinalT) == 0x20, "Structure size is incorrect"); +using ArmorOrShieldV1V2 = ArmorOrShieldFinalT, false>; +using ArmorOrShieldGC = ArmorOrShieldFinalT, true>; +using ArmorOrShieldXB = ArmorOrShieldFinalT, false>; +using ArmorOrShieldV4 = ArmorOrShieldFinalT; + +template +struct UnitT { + /* V1/V2 offsets */ + /* 00 */ BaseT base; + /* 04 */ U16T stat = 0; + /* 06 */ U16T stat_amount = 0; + /* 08 */ + operator ItemParameterTable::Unit() const { + ItemParameterTable::Unit ret; + this->base.parse_into(ret); + ret.stat = this->stat; + ret.stat_amount = this->stat_amount; + return ret; + } +} __attribute__((packed)); +static_assert(sizeof(UnitT, false>) == 8, "Structure size is incorrect"); +static_assert(sizeof(UnitT, true>) == 8, "Structure size is incorrect"); +static_assert(sizeof(UnitT, false>) == 0x0C, "Structure size is incorrect"); +static_assert(sizeof(UnitT, true>) == 0x0C, "Structure size is incorrect"); +static_assert(sizeof(UnitT) == 0x10, "Structure size is incorrect"); +using UnitDCProtos = UnitT, false>; + +template +struct UnitFinalT : UnitT { + /* 08 */ S16T modifier_amount = 0; + /* 0A */ parray unused; + /* 0C */ + operator ItemParameterTable::Unit() const { + ItemParameterTable::Unit ret = this->UnitT::operator ItemParameterTable::Unit(); + ret.modifier_amount = this->modifier_amount; + return ret; + } +} __attribute__((packed)); +static_assert(sizeof(UnitFinalT, false>) == 0x0C, "Structure size is incorrect"); +static_assert(sizeof(UnitFinalT, true>) == 0x0C, "Structure size is incorrect"); +static_assert(sizeof(UnitFinalT, false>) == 0x10, "Structure size is incorrect"); +static_assert(sizeof(UnitFinalT, true>) == 0x10, "Structure size is incorrect"); +static_assert(sizeof(UnitFinalT) == 0x14, "Structure size is incorrect"); +using UnitV1V2 = UnitFinalT, false>; +using UnitGC = UnitFinalT, true>; +using UnitXB = UnitFinalT, false>; +using UnitV4 = UnitFinalT; + +template +struct MagT { + /* V1/V2 offsets */ + /* 00 */ BaseT base; + /* 04 */ U16T feed_table = 0; + /* 06 */ uint8_t photon_blast = 0; + /* 07 */ uint8_t activation = 0; + /* 08 */ uint8_t on_pb_full = 0; + /* 09 */ uint8_t on_low_hp = 0; + /* 0A */ uint8_t on_death = 0; + /* 0B */ uint8_t on_boss = 0; + /* 0C */ uint8_t on_pb_full_flag = 0; + /* 0D */ uint8_t on_low_hp_flag = 0; + /* 0E */ uint8_t on_death_flag = 0; + /* 0F */ uint8_t on_boss_flag = 0; + /* 10 */ + operator ItemParameterTable::Mag() const { + ItemParameterTable::Mag ret; + this->base.parse_into(ret); + ret.feed_table = this->feed_table; + ret.photon_blast = this->photon_blast; + ret.activation = this->activation; + ret.on_pb_full = this->on_pb_full; + ret.on_low_hp = this->on_low_hp; + ret.on_death = this->on_death; + ret.on_boss = this->on_boss; + ret.on_pb_full_flag = this->on_pb_full_flag; + ret.on_low_hp_flag = this->on_low_hp_flag; + ret.on_death_flag = this->on_death_flag; + ret.on_boss_flag = this->on_boss_flag; + return ret; + } +} __attribute__((packed)); +static_assert(sizeof(MagT, false>) == 0x10, "Structure size is incorrect"); +static_assert(sizeof(MagT, true>) == 0x10, "Structure size is incorrect"); +static_assert(sizeof(MagT, false>) == 0x14, "Structure size is incorrect"); +static_assert(sizeof(MagT, true>) == 0x14, "Structure size is incorrect"); +static_assert(sizeof(MagT) == 0x18, "Structure size is incorrect"); +using MagV1 = MagT, false>; + +template +struct MagV2V3V4T : MagT { + U16T class_flags = 0x00FF; + parray unused; + operator ItemParameterTable::Mag() const { + ItemParameterTable::Mag ret = this->MagT::operator ItemParameterTable::Mag(); + ret.class_flags = this->class_flags; + return ret; + } +} __attribute__((packed)); +static_assert(sizeof(MagV2V3V4T, false>) == 0x14, "Structure size is incorrect"); +static_assert(sizeof(MagV2V3V4T, true>) == 0x14, "Structure size is incorrect"); +static_assert(sizeof(MagV2V3V4T, false>) == 0x18, "Structure size is incorrect"); +static_assert(sizeof(MagV2V3V4T, true>) == 0x18, "Structure size is incorrect"); +static_assert(sizeof(MagV2V3V4T) == 0x1C, "Structure size is incorrect"); +using MagV2 = MagV2V3V4T, false>; +using MagGC = MagV2V3V4T, true>; +using MagXB = MagV2V3V4T, false>; +using MagV4 = MagV2V3V4T; + +template +struct ToolT { + /* V1/V2 offsets */ + /* 00 */ BaseT base; + /* 04 */ U16T amount = 0; + /* 06 */ U16T tech = 0; + /* 08 */ S32T cost = 0; + /* 0C */ U32T item_flags = 0; + /* 10 */ + operator ItemParameterTable::Tool() const { + ItemParameterTable::Tool ret; + this->base.parse_into(ret); + ret.amount = this->amount; + ret.tech = this->tech; + ret.cost = this->cost; + ret.item_flags = this->item_flags; + return ret; + } +} __attribute__((packed)); +static_assert(sizeof(ToolT, false>) == 0x10, "Structure size is incorrect"); +static_assert(sizeof(ToolT, true>) == 0x10, "Structure size is incorrect"); +static_assert(sizeof(ToolT, false>) == 0x14, "Structure size is incorrect"); +static_assert(sizeof(ToolT, true>) == 0x14, "Structure size is incorrect"); +static_assert(sizeof(ToolT) == 0x18, "Structure size is incorrect"); +using ToolV1V2 = ToolT, false>; +using ToolGC = ToolT, true>; +using ToolXB = ToolT, false>; +using ToolV4 = ToolT; + +using MagFeedResultsList = parray; + +template +using MagFeedResultsListOffsetsT = parray, 8>; + +template +struct SpecialT { + U16T type = 0xFFFF; + U16T amount = 0; + operator ItemParameterTable::Special() const { + return {this->type, this->amount}; + } +} __packed_ws_be__(SpecialT, 4); + +template +struct StatBoostT { + parray stats = 0; + parray, 2> amounts; + operator ItemParameterTable::StatBoost() const { + return {this->stats[0], this->amounts[0], this->stats[1], this->amounts[1]}; + } +} __packed_ws_be__(StatBoostT, 6); + +template +struct TechniqueBoostEntryT { + uint8_t tech_num = 0; + uint8_t flags = 0; + parray unused; + F32T amount = 0.0f; + operator ItemParameterTable::TechniqueBoost() const { + return {this->tech_num, this->flags, this->amount}; + } +} __packed_ws_be__(TechniqueBoostEntryT, 8); + +struct NonWeaponSaleDivisorsDCProtos { + uint8_t armor_divisor = 0; + uint8_t shield_divisor = 0; + uint8_t unit_divisor = 0; + operator ItemParameterTable::NonWeaponSaleDivisors() const { + return { + static_cast(this->armor_divisor), + static_cast(this->shield_divisor), + static_cast(this->unit_divisor), + 0.0f}; + } +} __packed_ws__(NonWeaponSaleDivisorsDCProtos, 3); + +template +struct NonWeaponSaleDivisorsT { + F32T armor_divisor = 0.0f; + F32T shield_divisor = 0.0f; + F32T unit_divisor = 0.0f; + F32T mag_divisor = 0.0f; + operator ItemParameterTable::NonWeaponSaleDivisors() const { + return {this->armor_divisor, this->shield_divisor, this->unit_divisor, this->mag_divisor}; + } +} __packed_ws_be__(NonWeaponSaleDivisorsT, 0x10); + +template +struct ShieldEffectT { + U32T sound_id; + U32T unknown_a1; + operator ItemParameterTable::ShieldEffect() const { + return {this->sound_id, this->unknown_a1}; + } +} __packed_ws_be__(ShieldEffectT, 8); + +template +struct PhotonColorEntryT { + /* 00 */ U32T unknown_a1; + /* 04 */ parray, 4> unknown_a2; + /* 14 */ parray, 4> unknown_a3; + /* 24 */ + operator ItemParameterTable::PhotonColorEntry() const { + ItemParameterTable::PhotonColorEntry ret; + ret.unknown_a1 = this->unknown_a1; + ret.unknown_a2.x = this->unknown_a2[0]; + ret.unknown_a2.y = this->unknown_a2[1]; + ret.unknown_a2.z = this->unknown_a2[2]; + ret.unknown_a2.t = this->unknown_a2[3]; + ret.unknown_a3.x = this->unknown_a3[0]; + ret.unknown_a3.y = this->unknown_a3[1]; + ret.unknown_a3.z = this->unknown_a3[2]; + ret.unknown_a3.t = this->unknown_a3[3]; + return ret; + } +} __packed_ws_be__(PhotonColorEntryT, 0x24); + +template +struct UnknownA1T { + U16T unknown_a1; + U16T unknown_a2; + operator ItemParameterTable::UnknownA1() const { + return {this->unknown_a1, this->unknown_a2}; + } +} __packed_ws_be__(UnknownA1T, 4); + +template +struct UnknownA5T { + U32T target_param; + U32T unknown_a2; + U32T unknown_a3; + operator ItemParameterTable::UnknownA5() const { + return {this->target_param, this->unknown_a2, this->unknown_a3}; + } +} __packed_ws_be__(UnknownA5T, 0x0C); + +template +struct WeaponRangeT { + F32T unknown_a1; + F32T unknown_a2; + U32T unknown_a3; + U32T unknown_a4; + U32T unknown_a5; + operator ItemParameterTable::WeaponRange() const { + return {this->unknown_a1, this->unknown_a2, this->unknown_a3, this->unknown_a4, this->unknown_a5}; + } +} __packed_ws_be__(WeaponRangeT, 0x14); + +template +struct WeaponEffect { + U32T sound_id1; + U32T eff_value1; + U32T sound_id2; + U32T eff_value2; + parray unknown_a5; + operator ItemParameterTable::WeaponEffect() const { + return {this->sound_id1, this->eff_value1, this->sound_id2, this->eff_value2, this->unknown_a5}; + } +} __packed_ws_be__(WeaponEffect, 0x20); + +/* The fields in the root structure are: + * DCTE / 112K / V1 / V2 / GCTE / V3 / V4 + * 0013 / 0013 / 0013 / 0013 / / / entry_count // Count of pointers in root struct; unused + * 0668 / 0668 / / / / / armor_stat_boost_index_table // -> [uint8_t] + * 2D94 / 2E28 / 3258 / 5A5C / 6E4C / EF90 / 1478C armor_table // -> [{count, offset -> [ArmorOrShieldV*]}](2; armors and shields) + * / / / / 737C / F5D0 / 14FF4 combination_table // -> {count, offset -> [ItemCombination]} + * 2F54 / 2FF0 / 3420 / 5F4C / 7384 / F608 / 1502C mag_feed_table // -> MagFeedResultsTable + * 2DAC / 2E40 / 3270 / 5A74 / 6E64 / EFA8 / 147A4 mag_table // -> {count, offset -> [MagV*]} + * / / / / 69D8 / DF88 / 12894 max_tech_level_table // -> MaxTechniqueLevels + * 1FE6 / 207A / 248C / 40A8 / 4A80 / BBCC / 0F83C non_weapon_sale_divisor_table // -> NonWeaponSaleDivisors + * 1994 / 1A28 / 1DB0 / 2E4C / 37A4 / A7FC / 0DE7C photon_color_table // -> PhotonColorEntry[...] + * / / / / / F600 / 15024 ranged_special_table // -> {count, offset -> [RangedSpecial]} + * / / 3198 / 5704 / 61B8 / D6E4 / 11C80 shield_effect_table // -> ShieldEffect[...] (indexed by data1[2]) + * 030C / 030C / / / / / shield_stat_boost_index_table // -> [uint8_t] + * 275E / 27F4 / 2C12 / 4540 / 4F72 / C100 / 0FE3C special_table // -> [Special] + * 22A9 / 233D / 275C / 4378 / 4D50 / BE9C / 0FB0C star_value_table // -> [uint8_t] (indexed by .id from weapon, armor, etc.) + * 2CE4 / 2D78 / 2CB8 / 58DC / 68B8 / DE50 / 1275C stat_boost_table // -> [StatBoost] + * / / / / 6B1C / EB8C / 14278 tech_boost_table // -> [TechniqueBoostEntry[3]] + * 2DB4 / 2E48 / 3278 / 5A7C / 6E6C / EFB0 / 147AC tool_table // -> [{count, offset -> [ToolV*]}] (last if out of range) + * 2DA4 / 2E38 / 3268 / 5A6C / 6E5C / EFA0 / 1479C unit_table // -> {count, offset -> [UnitV*]} (last if out of range) + * 23EE / 2484 / 28A2 / 45E4 / / / unknown_a1 // TODO + * / / / / 68B0 / DE48 / 12754 unknown_a5 // -> {count, offset -> [UnknownA5]} + * / / / / / F5F8 / 1501C unsealable_table // -> {count, offset -> [UnsealableItem]} + * / / / / / F5F0 / 15014 unwrap_table // -> {count, offset -> [{count, offset -> [EventItem]}]} + * 1F98 / 202C / 23C8 / 3DF8 / 47BC / B88C / 0F4B8 weapon_class_table // -> [uint8_t](0x89) + * 2804 / 2898 / / / 5018 / C1A4 / 0FEE0 weapon_effect_table // -> [WeaponEffect] + * 1C64 / 1CF8 / 2080 / 32CC / 3A74 / AACC / 0E194 weapon_range_table // -> WeaponRange[...] + * 1FBF / 2053 / 23F0 / 3E84 / 484C / B938 / 0F5A8 weapon_sale_divisor_table // -> [uint8_t] on DC protos; [float] on all other versions + * 1908 / 199C / / / / / weapon_stat_boost_index_table // -> [StatBoost] + * 2E1C / 2EB8 / 32E8 / 5AFC / 6F0C / F078 / 14884 weapon_table // -> [{count, offset -> [WeaponV*]}] + */ + +struct RootDCProtos { + /* 00 */ le_uint32_t entry_count; + /* 04 */ le_uint32_t weapon_table; + /* 08 */ le_uint32_t armor_table; + /* 0C */ le_uint32_t unit_table; + /* 10 */ le_uint32_t tool_table; + /* 14 */ le_uint32_t mag_table; + /* 18 */ le_uint32_t weapon_class_table; + /* 1C */ le_uint32_t photon_color_table; + /* 20 */ le_uint32_t weapon_range_table; + /* 24 */ le_uint32_t weapon_integral_sale_divisor_table; + /* 28 */ le_uint32_t non_weapon_integral_sale_divisor_table; + /* 2C */ le_uint32_t mag_feed_table; + /* 30 */ le_uint32_t star_value_table; + /* 34 */ le_uint32_t unknown_a1; + /* 38 */ le_uint32_t special_table; + /* 3C */ le_uint32_t weapon_effect_table; + /* 40 */ le_uint32_t weapon_stat_boost_index_table; + /* 44 */ le_uint32_t armor_stat_boost_index_table; + /* 48 */ le_uint32_t shield_stat_boost_index_table; + /* 4C */ le_uint32_t stat_boost_table; +} __packed_ws__(RootDCProtos, 0x50); + +struct RootV1V2 { + /* 00 */ le_uint32_t entry_count; + /* 04 */ le_uint32_t weapon_table; + /* 08 */ le_uint32_t armor_table; + /* 0C */ le_uint32_t unit_table; + /* 10 */ le_uint32_t tool_table; + /* 14 */ le_uint32_t mag_table; + /* 18 */ le_uint32_t weapon_class_table; + /* 1C */ le_uint32_t photon_color_table; + /* 20 */ le_uint32_t weapon_range_table; + /* 24 */ le_uint32_t weapon_sale_divisor_table; + /* 28 */ le_uint32_t non_weapon_sale_divisor_table; + /* 2C */ le_uint32_t mag_feed_table; + /* 30 */ le_uint32_t star_value_table; + /* 34 */ le_uint32_t unknown_a1; + /* 38 */ le_uint32_t special_table; + /* 3C */ le_uint32_t stat_boost_table; + /* 40 */ le_uint32_t shield_effect_table; +} __packed_ws__(RootV1V2, 0x44); + +struct RootGCNTE { + /* 00 */ be_uint32_t weapon_table; + /* 04 */ be_uint32_t armor_table; + /* 08 */ be_uint32_t unit_table; + /* 0C */ be_uint32_t tool_table; + /* 10 */ be_uint32_t mag_table; + /* 14 */ be_uint32_t weapon_class_table; + /* 18 */ be_uint32_t photon_color_table; + /* 1C */ be_uint32_t weapon_range_table; + /* 20 */ be_uint32_t weapon_sale_divisor_table; + /* 24 */ be_uint32_t non_weapon_sale_divisor_table; + /* 28 */ be_uint32_t mag_feed_table; + /* 2C */ be_uint32_t star_value_table; + /* 30 */ be_uint32_t special_table; + /* 34 */ be_uint32_t weapon_effect_table; + /* 38 */ be_uint32_t stat_boost_table; + /* 3C */ be_uint32_t shield_effect_table; + /* 40 */ be_uint32_t max_tech_level_table; + /* 44 */ be_uint32_t combination_table; + /* 48 */ be_uint32_t unknown_a5; + /* 4C */ be_uint32_t tech_boost_table; +} __packed_ws__(RootGCNTE, 0x50); + +template +struct RootV3V4T { + /* 00 */ U32T weapon_table; + /* 04 */ U32T armor_table; + /* 08 */ U32T unit_table; + /* 0C */ U32T tool_table; + /* 10 */ U32T mag_table; + /* 14 */ U32T weapon_class_table; + /* 18 */ U32T photon_color_table; + /* 1C */ U32T weapon_range_table; + /* 20 */ U32T weapon_sale_divisor_table; + /* 24 */ U32T non_weapon_sale_divisor_table; + /* 28 */ U32T mag_feed_table; + /* 2C */ U32T star_value_table; + /* 30 */ U32T special_table; + /* 34 */ U32T weapon_effect_table; + /* 38 */ U32T stat_boost_table; + /* 3C */ U32T shield_effect_table; + /* 40 */ U32T max_tech_level_table; + /* 44 */ U32T combination_table; + /* 48 */ U32T unknown_a5; + /* 4C */ U32T tech_boost_table; + /* 50 */ U32T unwrap_table; + /* 54 */ U32T unsealable_table; + /* 58 */ U32T ranged_special_table; +} __packed_ws_be__(RootV3V4T, 0x5C); + +ItemParameterTable::ItemParameterTable(std::shared_ptr data) + : data(data), r(*this->data) {} + +std::set ItemParameterTable::compute_all_valid_primary_identifiers() const { set ret; auto find_items_1d = [&](uint64_t data1, size_t position) -> size_t { @@ -181,596 +731,12 @@ set ItemParameterTable::compute_all_valid_primary_identifiers() const return ret; } -ItemParameterTable::WeaponV4 ItemParameterTable::WeaponDCProtos::to_v4() const { - WeaponV4 ret; - ret.base.id = this->base.id; - ret.class_flags = this->class_flags; - ret.atp_min = this->atp_min; - ret.atp_max = this->atp_max; - ret.atp_required = this->atp_required; - ret.mst_required = this->mst_required; - ret.ata_required = this->ata_required; - ret.max_grind = this->max_grind; - ret.photon = this->photon; - ret.special = this->special; - ret.ata = this->ata; - return ret; -} - -ItemParameterTable::WeaponV4 ItemParameterTable::WeaponV1V2::to_v4() const { - WeaponV4 ret; - ret.base.id = this->base.id; - ret.class_flags = this->class_flags; - ret.atp_min = this->atp_min; - ret.atp_max = this->atp_max; - ret.atp_required = this->atp_required; - ret.mst_required = this->mst_required; - ret.ata_required = this->ata_required; - ret.max_grind = this->max_grind; - ret.photon = this->photon; - ret.special = this->special; - ret.ata = this->ata; - ret.stat_boost_entry_index = this->stat_boost_entry_index; - return ret; -} - -ItemParameterTable::WeaponV4 ItemParameterTable::WeaponGCNTE::to_v4() const { - WeaponV4 ret; - ret.base.id = this->base.id; - ret.base.type = this->base.type; - ret.base.skin = this->base.skin; - ret.class_flags = this->class_flags; - ret.atp_min = this->atp_min; - ret.atp_max = this->atp_max; - ret.atp_required = this->atp_required; - ret.mst_required = this->mst_required; - ret.ata_required = this->ata_required; - ret.mst = this->mst; - ret.max_grind = this->max_grind; - ret.photon = this->photon; - ret.special = this->special; - ret.ata = this->ata; - ret.stat_boost_entry_index = this->stat_boost_entry_index; - ret.projectile = this->projectile; - ret.trail1_x = this->trail1_x; - ret.trail1_y = this->trail1_y; - ret.trail2_x = this->trail2_x; - ret.trail2_y = this->trail2_y; - ret.color = this->color; - ret.unknown_a1 = this->unknown_a1; - return ret; -} - -template -ItemParameterTable::WeaponV4 ItemParameterTable::WeaponV3T::to_v4() const { - WeaponV4 ret; - ret.base.id = this->base.id; - ret.base.type = this->base.type; - ret.base.skin = this->base.skin; - ret.class_flags = this->class_flags; - ret.atp_min = this->atp_min; - ret.atp_max = this->atp_max; - ret.atp_required = this->atp_required; - ret.mst_required = this->mst_required; - ret.ata_required = this->ata_required; - ret.mst = this->mst; - ret.max_grind = this->max_grind; - ret.photon = this->photon; - ret.special = this->special; - ret.ata = this->ata; - ret.stat_boost_entry_index = this->stat_boost_entry_index; - ret.projectile = this->projectile; - ret.trail1_x = this->trail1_x; - ret.trail1_y = this->trail1_y; - ret.trail2_x = this->trail2_x; - ret.trail2_y = this->trail2_y; - ret.color = this->color; - ret.unknown_a1 = this->unknown_a1; - ret.unknown_a4 = this->unknown_a4; - ret.unknown_a5 = this->unknown_a5; - ret.tech_boost = this->tech_boost; - ret.behavior_flags = this->behavior_flags; - return ret; -} - -ItemParameterTable::ArmorOrShieldV4 ItemParameterTable::ArmorOrShieldDCProtos::to_v4() const { - ArmorOrShieldV4 ret; - ret.base.id = this->base.id; - ret.dfp = this->dfp; - ret.evp = this->evp; - ret.block_particle = this->block_particle; - ret.block_effect = this->block_effect; - ret.class_flags = this->class_flags; - ret.required_level = this->required_level; - ret.efr = this->efr; - ret.eth = this->eth; - ret.eic = this->eic; - ret.edk = this->edk; - ret.elt = this->elt; - ret.dfp_range = this->dfp_range; - ret.evp_range = this->evp_range; - return ret; -} - -ItemParameterTable::ArmorOrShieldV4 ItemParameterTable::ArmorOrShieldV1V2::to_v4() const { - ArmorOrShieldV4 ret; - ret.base.id = this->base.id; - ret.dfp = this->dfp; - ret.evp = this->evp; - ret.block_particle = this->block_particle; - ret.block_effect = this->block_effect; - ret.class_flags = this->class_flags; - ret.required_level = this->required_level; - ret.efr = this->efr; - ret.eth = this->eth; - ret.eic = this->eic; - ret.edk = this->edk; - ret.elt = this->elt; - ret.dfp_range = this->dfp_range; - ret.evp_range = this->evp_range; - ret.stat_boost_entry_index = this->stat_boost_entry_index; - ret.tech_boost = this->tech_boost; - ret.flags_type = this->flags_type; - ret.unknown_a4 = this->unknown_a4; - return ret; -} - -template -ItemParameterTable::ArmorOrShieldV4 ItemParameterTable::ArmorOrShieldV3T::to_v4() const { - ArmorOrShieldV4 ret; - ret.base.id = this->base.id; - ret.base.type = this->base.type; - ret.base.skin = this->base.skin; - ret.dfp = this->dfp; - ret.evp = this->evp; - ret.block_particle = this->block_particle; - ret.block_effect = this->block_effect; - ret.class_flags = this->class_flags; - ret.required_level = this->required_level; - ret.efr = this->efr; - ret.eth = this->eth; - ret.eic = this->eic; - ret.edk = this->edk; - ret.elt = this->elt; - ret.dfp_range = this->dfp_range; - ret.evp_range = this->evp_range; - ret.stat_boost_entry_index = this->stat_boost_entry_index; - ret.tech_boost = this->tech_boost; - ret.flags_type = this->flags_type; - ret.unknown_a4 = this->unknown_a4; - return ret; -} - -ItemParameterTable::UnitV4 ItemParameterTable::UnitDCProtos::to_v4() const { - UnitV4 ret; - ret.base.id = this->base.id; - ret.stat = this->stat; - ret.stat_amount = this->stat_amount; - return ret; -} - -ItemParameterTable::UnitV4 ItemParameterTable::UnitV1V2::to_v4() const { - UnitV4 ret; - ret.base.id = this->base.id; - ret.stat = this->stat; - ret.stat_amount = this->stat_amount; - ret.modifier_amount = this->modifier_amount; - return ret; -} - -template -ItemParameterTable::UnitV4 ItemParameterTable::UnitV3T::to_v4() const { - UnitV4 ret; - ret.base.id = this->base.id; - ret.base.type = this->base.type; - ret.base.skin = this->base.skin; - ret.stat = this->stat; - ret.stat_amount = this->stat_amount; - ret.modifier_amount = this->modifier_amount; - return ret; -} - -ItemParameterTable::MagV4 ItemParameterTable::MagV1::to_v4() const { - MagV4 ret; - ret.base.id = this->base.id; - ret.feed_table = this->feed_table; - ret.photon_blast = this->photon_blast; - ret.activation = this->activation; - ret.on_pb_full = this->on_pb_full; - ret.on_low_hp = this->on_low_hp; - ret.on_death = this->on_death; - ret.on_boss = this->on_boss; - ret.on_pb_full_flag = this->on_pb_full_flag; - ret.on_low_hp_flag = this->on_low_hp_flag; - ret.on_death_flag = this->on_death_flag; - ret.on_boss_flag = this->on_boss_flag; - return ret; -} - -ItemParameterTable::MagV4 ItemParameterTable::MagV2::to_v4() const { - MagV4 ret; - ret.base.id = this->base.id; - ret.feed_table = this->feed_table; - ret.photon_blast = this->photon_blast; - ret.activation = this->activation; - ret.on_pb_full = this->on_pb_full; - ret.on_low_hp = this->on_low_hp; - ret.on_death = this->on_death; - ret.on_boss = this->on_boss; - ret.on_pb_full_flag = this->on_pb_full_flag; - ret.on_low_hp_flag = this->on_low_hp_flag; - ret.on_death_flag = this->on_death_flag; - ret.on_boss_flag = this->on_boss_flag; - ret.class_flags = this->class_flags; - return ret; -} - -template -ItemParameterTable::MagV4 ItemParameterTable::MagV3T::to_v4() const { - MagV4 ret; - ret.base.id = this->base.id; - ret.base.type = this->base.type; - ret.base.skin = this->base.skin; - ret.feed_table = this->feed_table; - ret.photon_blast = this->photon_blast; - ret.activation = this->activation; - ret.on_pb_full = this->on_pb_full; - ret.on_low_hp = this->on_low_hp; - ret.on_death = this->on_death; - ret.on_boss = this->on_boss; - ret.on_pb_full_flag = this->on_pb_full_flag; - ret.on_low_hp_flag = this->on_low_hp_flag; - ret.on_death_flag = this->on_death_flag; - ret.on_boss_flag = this->on_boss_flag; - ret.class_flags = this->class_flags; - return ret; -} - -ItemParameterTable::ToolV4 ItemParameterTable::ToolV1V2::to_v4() const { - ToolV4 ret; - ret.base.id = this->base.id; - ret.amount = this->amount; - ret.tech = this->tech; - ret.cost = this->cost; - ret.item_flags = this->item_flags; - return ret; -} - -template -ItemParameterTable::ToolV4 ItemParameterTable::ToolV3T::to_v4() const { - ToolV4 ret; - ret.base.id = this->base.id; - ret.base.type = this->base.type; - ret.base.skin = this->base.skin; - ret.amount = this->amount; - ret.tech = this->tech; - ret.cost = this->cost; - ret.item_flags = this->item_flags; - return ret; -} - -template -size_t indirect_lookup_2d_count(const phosg::StringReader& r, size_t root_offset, size_t co_index) { - return r.pget>(root_offset + sizeof(ArrayRefT) * co_index).count; -} - -template -const T& indirect_lookup_2d(const phosg::StringReader& r, size_t root_offset, size_t co_index, size_t item_index) { - const auto& co = r.pget>(root_offset + sizeof(ArrayRefT) * co_index); - if (item_index >= co.count) { - throw out_of_range("item ID out of range"); - } - return r.pget(co.offset + sizeof(T) * item_index); -} - -size_t ItemParameterTable::num_weapons_in_class(uint8_t data1_1) const { - if (data1_1 >= this->num_weapon_classes) { - throw out_of_range("weapon ID out of range"); - } - if (this->offsets_dc_protos) { - return indirect_lookup_2d_count(this->r, this->offsets_dc_protos->weapon_table, data1_1); - } else if (this->offsets_v1_v2) { - return indirect_lookup_2d_count(this->r, this->offsets_v1_v2->weapon_table, data1_1); - } else if (this->offsets_gc_nte) { - return indirect_lookup_2d_count(this->r, this->offsets_gc_nte->weapon_table, data1_1); - } else if (this->offsets_v3_le) { - return indirect_lookup_2d_count(this->r, this->offsets_v3_le->weapon_table, data1_1); - } else if (this->offsets_v3_be) { - return indirect_lookup_2d_count(this->r, this->offsets_v3_be->weapon_table, data1_1); - } else if (this->offsets_v4) { - return indirect_lookup_2d_count(this->r, this->offsets_v4->weapon_table, data1_1); - } else { - throw logic_error("table is not v2, v3, or v4"); - } -} - -const ItemParameterTable::WeaponV4& ItemParameterTable::get_weapon(uint8_t data1_1, uint8_t data1_2) const { - if (data1_1 >= this->num_weapon_classes) { - throw out_of_range("weapon ID out of range"); - } - - if (this->offsets_v4) { - return indirect_lookup_2d(this->r, this->offsets_v4->weapon_table, data1_1, data1_2); - } - - uint16_t key = (data1_1 << 8) | data1_2; - try { - return this->parsed_weapons.at(key); - } catch (const std::out_of_range&) { - WeaponV4 def_v4; - if (this->offsets_dc_protos) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_dc_protos->weapon_table, data1_1, data1_2).to_v4(); - } else if (this->offsets_v1_v2) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_v1_v2->weapon_table, data1_1, data1_2).to_v4(); - } else if (this->offsets_gc_nte) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_gc_nte->weapon_table, data1_1, data1_2).to_v4(); - } else if (this->offsets_v3_le) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_le->weapon_table, data1_1, data1_2).to_v4(); - } else if (this->offsets_v3_be) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_be->weapon_table, data1_1, data1_2).to_v4(); - } else { - throw logic_error("table is not v2, v3, or v4"); - } - return this->parsed_weapons.emplace(key, def_v4).first->second; - } -} - -size_t ItemParameterTable::num_armors_or_shields_in_class(uint8_t data1_1) const { - if ((data1_1 < 1) || (data1_1 > 2)) { - throw out_of_range("armor/shield class ID out of range"); - } - if (this->offsets_dc_protos) { - return indirect_lookup_2d_count(this->r, this->offsets_dc_protos->armor_table, data1_1 - 1); - } else if (this->offsets_v1_v2) { - return indirect_lookup_2d_count(this->r, this->offsets_v1_v2->armor_table, data1_1 - 1); - } else if (this->offsets_gc_nte) { - return indirect_lookup_2d_count(this->r, this->offsets_gc_nte->armor_table, data1_1 - 1); - } else if (this->offsets_v3_le) { - return indirect_lookup_2d_count(this->r, this->offsets_v3_le->armor_table, data1_1 - 1); - } else if (this->offsets_v3_be) { - return indirect_lookup_2d_count(this->r, this->offsets_v3_be->armor_table, data1_1 - 1); - } else if (this->offsets_v4) { - return indirect_lookup_2d_count(this->r, this->offsets_v4->armor_table, data1_1 - 1); - } else { - throw logic_error("table is not v2, v3, or v4"); - } -} - -const ItemParameterTable::ArmorOrShieldV4& ItemParameterTable::get_armor_or_shield(uint8_t data1_1, uint8_t data1_2) const { - if ((data1_1 < 1) || (data1_1 > 2)) { - throw out_of_range("armor/shield class ID out of range"); - } - - if (this->offsets_v4) { - return indirect_lookup_2d(this->r, this->offsets_v4->armor_table, data1_1 - 1, data1_2); - } - - auto& parsed_vec = (data1_1 == 2) ? this->parsed_shields : this->parsed_armors; - try { - const auto& ret = parsed_vec.at(data1_2); - if (ret.base.id == 0xFFFFFFFF) { - throw out_of_range("cache entry not populated"); - } - return ret; - } catch (const std::out_of_range&) { - while (data1_2 >= parsed_vec.size()) { - auto& def_v4 = parsed_vec.emplace_back(); - if (this->offsets_dc_protos) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_dc_protos->armor_table, data1_1 - 1, parsed_vec.size() - 1).to_v4(); - } else if (this->offsets_v1_v2) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_v1_v2->armor_table, data1_1 - 1, parsed_vec.size() - 1).to_v4(); - } else if (this->offsets_gc_nte) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_gc_nte->armor_table, data1_1 - 1, parsed_vec.size() - 1).to_v4(); - } else if (this->offsets_v3_le) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_le->armor_table, data1_1 - 1, parsed_vec.size() - 1).to_v4(); - } else if (this->offsets_v3_be) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_be->armor_table, data1_1 - 1, parsed_vec.size() - 1).to_v4(); - } else { - throw logic_error("table is not v2, v3, or v4"); - } - } - return parsed_vec[data1_2]; - } -} - -size_t ItemParameterTable::num_units() const { - if (this->offsets_dc_protos) { - return indirect_lookup_2d_count(this->r, this->offsets_dc_protos->unit_table, 0); - } else if (this->offsets_v1_v2) { - return indirect_lookup_2d_count(this->r, this->offsets_v1_v2->unit_table, 0); - } else if (this->offsets_gc_nte) { - return indirect_lookup_2d_count(this->r, this->offsets_gc_nte->unit_table, 0); - } else if (this->offsets_v3_le) { - return indirect_lookup_2d_count(this->r, this->offsets_v3_le->unit_table, 0); - } else if (this->offsets_v3_be) { - return indirect_lookup_2d_count(this->r, this->offsets_v3_be->unit_table, 0); - } else if (this->offsets_v4) { - return indirect_lookup_2d_count(this->r, this->offsets_v4->unit_table, 0); - } else { - throw logic_error("table is not v2, v3, or v4"); - } -} - -const ItemParameterTable::UnitV4& ItemParameterTable::get_unit(uint8_t data1_2) const { - if (this->offsets_v4) { - return indirect_lookup_2d(this->r, this->offsets_v4->unit_table, 0, data1_2); - } - - try { - const auto& ret = this->parsed_units.at(data1_2); - if (ret.base.id == 0xFFFFFFFF) { - throw out_of_range("cache entry not populated"); - } - return ret; - } catch (const std::out_of_range&) { - while (data1_2 >= this->parsed_units.size()) { - auto& def_v4 = this->parsed_units.emplace_back(); - if (this->offsets_dc_protos) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_dc_protos->unit_table, 0, this->parsed_units.size() - 1).to_v4(); - } else if (this->offsets_v1_v2) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_v1_v2->unit_table, 0, this->parsed_units.size() - 1).to_v4(); - } else if (this->offsets_gc_nte) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_gc_nte->unit_table, 0, this->parsed_units.size() - 1).to_v4(); - } else if (this->offsets_v3_le) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_le->unit_table, 0, this->parsed_units.size() - 1).to_v4(); - } else if (this->offsets_v3_be) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_be->unit_table, 0, this->parsed_units.size() - 1).to_v4(); - } else { - throw logic_error("table is not v2, v3, or v4"); - } - } - return this->parsed_units[data1_2]; - } -} - -size_t ItemParameterTable::num_mags() const { - if (this->offsets_dc_protos) { - return indirect_lookup_2d_count(this->r, this->offsets_dc_protos->mag_table, 0); - } else if (this->offsets_v1_v2) { - return indirect_lookup_2d_count(this->r, this->offsets_v1_v2->mag_table, 0); - } else if (this->offsets_gc_nte) { - return indirect_lookup_2d_count(this->r, this->offsets_gc_nte->mag_table, 0); - } else if (this->offsets_v3_le) { - return indirect_lookup_2d_count(this->r, this->offsets_v3_le->mag_table, 0); - } else if (this->offsets_v3_be) { - return indirect_lookup_2d_count(this->r, this->offsets_v3_be->mag_table, 0); - } else if (this->offsets_v4) { - return indirect_lookup_2d_count(this->r, this->offsets_v4->mag_table, 0); - } else { - throw logic_error("table is not v2, v3, or v4"); - } -} - -const ItemParameterTable::MagV4& ItemParameterTable::get_mag(uint8_t data1_1) const { - if (this->offsets_v4) { - return indirect_lookup_2d(this->r, this->offsets_v4->mag_table, 0, data1_1); - } - - try { - const auto& ret = this->parsed_mags.at(data1_1); - if (ret.base.id == 0xFFFFFFFF) { - throw out_of_range("cache entry not populated"); - } - return ret; - } catch (const std::out_of_range&) { - while (data1_1 >= this->parsed_mags.size()) { - auto& def_v4 = this->parsed_mags.emplace_back(); - if (this->offsets_dc_protos) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_dc_protos->mag_table, 0, this->parsed_mags.size() - 1).to_v4(); - } else if (this->offsets_v1_v2) { - if (is_v1(this->version)) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_v1_v2->mag_table, 0, this->parsed_mags.size() - 1).to_v4(); - } else { - def_v4 = indirect_lookup_2d(this->r, this->offsets_v1_v2->mag_table, 0, this->parsed_mags.size() - 1).to_v4(); - } - } else if (this->offsets_gc_nte) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_gc_nte->mag_table, 0, this->parsed_mags.size() - 1).to_v4(); - } else if (this->offsets_v3_le) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_le->mag_table, 0, this->parsed_mags.size() - 1).to_v4(); - } else if (this->offsets_v3_be) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_be->mag_table, 0, this->parsed_mags.size() - 1).to_v4(); - } else { - throw logic_error("table is not v2, v3, or v4"); - } - } - return this->parsed_mags[data1_1]; - } -} - -size_t ItemParameterTable::num_tools_in_class(uint8_t data1_1) const { - if (data1_1 >= this->num_tool_classes) { - throw out_of_range("tool class ID out of range"); - } - if (this->offsets_dc_protos) { - return indirect_lookup_2d_count(this->r, this->offsets_dc_protos->tool_table, data1_1); - } else if (this->offsets_v1_v2) { - return indirect_lookup_2d_count(this->r, this->offsets_v1_v2->tool_table, data1_1); - } else if (this->offsets_gc_nte) { - return indirect_lookup_2d_count(this->r, this->offsets_gc_nte->tool_table, data1_1); - } else if (this->offsets_v3_le) { - return indirect_lookup_2d_count(this->r, this->offsets_v3_le->tool_table, data1_1); - } else if (this->offsets_v3_be) { - return indirect_lookup_2d_count(this->r, this->offsets_v3_be->tool_table, data1_1); - } else if (this->offsets_v4) { - return indirect_lookup_2d_count(this->r, this->offsets_v4->tool_table, data1_1); - } else { - throw logic_error("table is not v2, v3, or v4"); - } -} - -const ItemParameterTable::ToolV4& ItemParameterTable::get_tool(uint8_t data1_1, uint8_t data1_2) const { - if (data1_1 >= this->num_tool_classes) { - throw out_of_range("tool class ID out of range"); - } - - if (this->offsets_v4) { - return indirect_lookup_2d(this->r, this->offsets_v4->tool_table, data1_1, data1_2); - } - - uint16_t key = (data1_1 << 8) | data1_2; - try { - return this->parsed_tools.at(key); - } catch (const std::out_of_range&) { - ToolV4 def_v4; - - if (this->offsets_dc_protos) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_dc_protos->tool_table, data1_1, data1_2).to_v4(); - } else if (this->offsets_v1_v2) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_v1_v2->tool_table, data1_1, data1_2).to_v4(); - } else if (this->offsets_gc_nte) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_gc_nte->tool_table, data1_1, data1_2).to_v4(); - } else if (this->offsets_v3_le) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_le->tool_table, data1_1, data1_2).to_v4(); - } else if (this->offsets_v3_be) { - def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_be->tool_table, data1_1, data1_2).to_v4(); - } else { - throw logic_error("table is not v2, v3, or v4"); - } - - return this->parsed_tools.emplace(key, def_v4).first->second; - } -} - -template -pair ItemParameterTable::find_tool_by_id_t(uint32_t tool_table_offset, uint32_t item_id) const { - const auto* cos = &this->r.pget>( - tool_table_offset, 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(ToolDefT) * co.count); - for (size_t y = 0; y < co.count; y++) { - if (defs[y].base.id == item_id) { - return make_pair(z, y); - } - } - } - throw out_of_range(std::format("invalid tool class {:08X}", item_id)); -} - -pair ItemParameterTable::find_tool_by_id(uint32_t item_id) const { - if (this->offsets_dc_protos) { - return this->find_tool_by_id_t(this->offsets_dc_protos->tool_table, item_id); - } else if (this->offsets_v1_v2) { - return this->find_tool_by_id_t(this->offsets_v1_v2->tool_table, item_id); - } else if (this->offsets_gc_nte) { - return this->find_tool_by_id_t(this->offsets_gc_nte->tool_table, item_id); - } else if (this->offsets_v3_le) { - return this->find_tool_by_id_t(this->offsets_v3_le->tool_table, item_id); - } else if (this->offsets_v3_be) { - return this->find_tool_by_id_t(this->offsets_v3_be->tool_table, item_id); - } else if (this->offsets_v4) { - return this->find_tool_by_id_t(this->offsets_v4->tool_table, item_id); - } else { - throw logic_error("table is not v2, v3, or v4"); - } -} - -variant< - const ItemParameterTable::WeaponV4*, - const ItemParameterTable::ArmorOrShieldV4*, - const ItemParameterTable::UnitV4*, - const ItemParameterTable::MagV4*, - const ItemParameterTable::ToolV4*> +std::variant< + const ItemParameterTable::Weapon*, + const ItemParameterTable::ArmorOrShield*, + const ItemParameterTable::Unit*, + const ItemParameterTable::Mag*, + const ItemParameterTable::Tool*> ItemParameterTable::definition_for_primary_identifier(uint32_t primary_identifier) const { uint8_t data1_0 = (primary_identifier >> 24) & 0xFF; uint8_t data1_1 = (primary_identifier >> 16) & 0xFF; @@ -799,255 +765,24 @@ ItemParameterTable::definition_for_primary_identifier(uint32_t primary_identifie } } -template -float ItemParameterTable::get_sale_divisor_t(const OffsetsT* offsets, uint8_t data1_0, uint8_t data1_1) const { - switch (data1_0) { - case 0: - if (data1_1 >= this->num_weapon_classes) { - return 0.0f; - } - return this->r.pget>(offsets->weapon_sale_divisor_table + data1_1 * sizeof(F32T)); - - case 1: { - const auto& divisors = this->r.pget>(offsets->sale_divisor_table); - switch (data1_1) { - case 1: - return divisors.armor_divisor; - case 2: - return divisors.shield_divisor; - case 3: - return divisors.unit_divisor; - } - return 0.0f; - } - - case 2: { - const auto& divisors = this->r.pget>(offsets->sale_divisor_table); - return divisors.mag_divisor; - } - - default: - return 0.0f; - } -} - -float ItemParameterTable::get_sale_divisor(uint8_t data1_0, uint8_t data1_1) const { - if (this->offsets_dc_protos) { - return this->get_sale_divisor_t(this->offsets_dc_protos, data1_0, data1_1); - } else if (this->offsets_v1_v2) { - return this->get_sale_divisor_t(this->offsets_v1_v2, data1_0, data1_1); - } else if (this->offsets_gc_nte) { - return this->get_sale_divisor_t(this->offsets_gc_nte, data1_0, data1_1); - } else if (this->offsets_v3_le) { - return this->get_sale_divisor_t(this->offsets_v3_le, data1_0, data1_1); - } else if (this->offsets_v3_be) { - return this->get_sale_divisor_t(this->offsets_v3_be, data1_0, data1_1); - } else if (this->offsets_v4) { - return this->get_sale_divisor_t(this->offsets_v4, data1_0, data1_1); - } else { - throw logic_error("table is not v2, v3, or v4"); - } -} - -const ItemParameterTable::MagFeedResult& ItemParameterTable::get_mag_feed_result( - uint8_t table_index, uint8_t item_index) const { - if (table_index >= 8) { - throw out_of_range("invalid mag feed table index"); - } - if (item_index >= 11) { - throw out_of_range("invalid mag feed item index"); - } - - uint32_t offset; - if (this->offsets_dc_protos) { - const auto& table_offsets = this->r.pget(this->offsets_dc_protos->mag_feed_table); - offset = table_offsets.offsets[table_index]; - } else if (this->offsets_v1_v2) { - const auto& table_offsets = this->r.pget(this->offsets_v1_v2->mag_feed_table); - offset = table_offsets.offsets[table_index]; - } else if (this->offsets_gc_nte) { - const auto& table_offsets = this->r.pget(this->offsets_gc_nte->mag_feed_table); - offset = table_offsets.offsets[table_index]; - } else if (this->offsets_v3_le) { - const auto& table_offsets = this->r.pget(this->offsets_v3_le->mag_feed_table); - offset = table_offsets.offsets[table_index]; - } else if (this->offsets_v3_be) { - const auto& table_offsets = this->r.pget(this->offsets_v3_be->mag_feed_table); - offset = table_offsets.offsets[table_index]; - } else if (this->offsets_v4) { - const auto& table_offsets = this->r.pget(this->offsets_v4->mag_feed_table); - offset = table_offsets.offsets[table_index]; - } else { - throw logic_error("table is not v2, v3, or v4"); - } - - return this->r.pget(offset)[item_index]; -} - -uint8_t ItemParameterTable::get_item_stars(uint32_t item_id) const { - uint32_t base_offset; - if (this->offsets_dc_protos) { - base_offset = this->offsets_dc_protos->star_value_table; - } else if (this->offsets_v1_v2) { - base_offset = this->offsets_v1_v2->star_value_table; - } else if (this->offsets_gc_nte) { - base_offset = this->offsets_gc_nte->star_value_table; - } else if (this->offsets_v3_le) { - base_offset = this->offsets_v3_le->star_value_table; - } else if (this->offsets_v3_be) { - base_offset = this->offsets_v3_be->star_value_table; - } else if (this->offsets_v4) { - base_offset = this->offsets_v4->star_value_table; - } else { - throw logic_error("table is not v2, v3, or v4"); - } - - return ((item_id >= this->item_stars_first_id) && (item_id < this->item_stars_last_id)) - ? this->r.pget_u8(base_offset + item_id - this->item_stars_first_id) - : 0; -} - -uint8_t ItemParameterTable::get_special_stars(uint8_t special) const { - return ((special & 0x3F) && !(special & 0x80)) ? this->get_item_stars(special + this->special_stars_begin_index) : 0; -} - -const ItemParameterTable::Special& ItemParameterTable::get_special(uint8_t special) const { - special &= 0x3F; - if (special >= this->num_specials) { - throw out_of_range("invalid special index"); - } - - if (this->offsets_dc_protos) { - return this->r.pget(this->offsets_dc_protos->special_data_table + sizeof(Special) * special); - } else if (this->offsets_v1_v2) { - return this->r.pget(this->offsets_v1_v2->special_data_table + sizeof(Special) * special); - } else if (this->offsets_v3_le) { - return this->r.pget(this->offsets_v3_le->special_data_table + sizeof(Special) * special); - } else if (this->offsets_gc_nte) { - if ((special >= this->parsed_specials.size()) || (this->parsed_specials[special].type != 0xFFFF)) { - if (special >= this->parsed_specials.size()) { - this->parsed_specials.resize(special + 1); - } - const auto& sp_be = this->r.pget(this->offsets_gc_nte->special_data_table + sizeof(SpecialBE) * special); - this->parsed_specials[special].type = sp_be.type; - this->parsed_specials[special].amount = sp_be.amount; - } - return this->parsed_specials[special]; - } else if (this->offsets_v3_be) { - if ((special >= this->parsed_specials.size()) || (this->parsed_specials[special].type != 0xFFFF)) { - if (special >= this->parsed_specials.size()) { - this->parsed_specials.resize(special + 1); - } - const auto& sp_be = this->r.pget(this->offsets_v3_be->special_data_table + sizeof(SpecialBE) * special); - this->parsed_specials[special].type = sp_be.type; - this->parsed_specials[special].amount = sp_be.amount; - } - return this->parsed_specials[special]; - } else if (this->offsets_v4) { - return this->r.pget(this->offsets_v4->special_data_table + sizeof(Special) * special); - } else { - throw logic_error("table is not v2, v3, or v4"); - } -} - -const ItemParameterTable::StatBoost& ItemParameterTable::get_stat_boost(uint8_t entry_index) const { - if (this->offsets_dc_protos) { - return this->r.pget(this->offsets_dc_protos->stat_boost_table + sizeof(StatBoost) * entry_index); - } else if (this->offsets_v1_v2) { - return this->r.pget(this->offsets_v1_v2->stat_boost_table + sizeof(StatBoost) * entry_index); - } else if (this->offsets_v3_le) { - return this->r.pget(this->offsets_v3_le->stat_boost_table + sizeof(StatBoost) * entry_index); - } else if (this->offsets_gc_nte) { - while (entry_index >= this->parsed_stat_boosts.size()) { - const auto& sb_be = this->r.pget(this->offsets_gc_nte->stat_boost_table + sizeof(StatBoostBE) * this->parsed_stat_boosts.size()); - auto& sb = this->parsed_stat_boosts.emplace_back(); - sb.stats = sb_be.stats; - sb.amounts[0] = sb_be.amounts[0]; - sb.amounts[1] = sb_be.amounts[1]; - } - return this->parsed_stat_boosts[entry_index]; - } else if (this->offsets_v3_be) { - while (entry_index >= this->parsed_stat_boosts.size()) { - const auto& sb_be = this->r.pget(this->offsets_v3_be->stat_boost_table + sizeof(StatBoostBE) * this->parsed_stat_boosts.size()); - auto& sb = this->parsed_stat_boosts.emplace_back(); - sb.stats = sb_be.stats; - sb.amounts[0] = sb_be.amounts[0]; - sb.amounts[1] = sb_be.amounts[1]; - } - return this->parsed_stat_boosts[entry_index]; - } else if (this->offsets_v4) { - return this->r.pget(this->offsets_v4->stat_boost_table + sizeof(StatBoost) * entry_index); - } else { - throw logic_error("table is not v2, v3, or v4"); - } -} - -uint8_t ItemParameterTable::get_max_tech_level(uint8_t char_class, uint8_t tech_num) const { - if (char_class >= 12) { - throw out_of_range("invalid character class"); - } - if (tech_num >= 19) { - throw out_of_range("invalid technique number"); - } - - if (this->offsets_dc_protos || this->offsets_v1_v2) { - if ((tech_num == 14) || (tech_num == 17)) { // Ryuker or Reverser - return 0; - } else { - return ((char_class == 6) || (char_class == 7) || (char_class == 8) || (char_class == 10)) ? 29 : 14; - } - } else if (this->offsets_gc_nte) { - return r.pget_u8(this->offsets_gc_nte->max_tech_level_table + tech_num * 12 + char_class); - } else if (this->offsets_v3_le) { - return r.pget_u8(this->offsets_v3_le->max_tech_level_table + tech_num * 12 + char_class); - } else if (this->offsets_v3_be) { - return r.pget_u8(this->offsets_v3_be->max_tech_level_table + tech_num * 12 + char_class); - } else if (this->offsets_v4) { - return r.pget_u8(this->offsets_v4->max_tech_level_table + tech_num * 12 + char_class); - } else { - throw logic_error("table is not v2, v3, or v4"); - } -} - -uint8_t ItemParameterTable::get_weapon_v1_replacement(uint8_t data1_1) const { - uint32_t offset; - if (this->offsets_dc_protos) { - offset = this->offsets_dc_protos->v1_replacement_table; - } else if (this->offsets_v1_v2) { - offset = this->offsets_v1_v2->v1_replacement_table; - } else if (this->offsets_gc_nte) { - offset = this->offsets_gc_nte->v1_replacement_table; - } else if (this->offsets_v3_le) { - offset = this->offsets_v3_le->v1_replacement_table; - } else if (this->offsets_v3_be) { - offset = this->offsets_v3_be->v1_replacement_table; - } else if (this->offsets_v4) { - offset = this->offsets_v4->v1_replacement_table; - } else { - throw logic_error("table is not v2, v3, or v4"); - } - - return (data1_1 < this->num_weapon_classes) ? this->r.pget_u8(offset + data1_1) : 0x00; -} - uint32_t ItemParameterTable::get_item_id(const ItemData& item) const { switch (item.data1[0]) { case 0: - return this->get_weapon(item.data1[1], item.data1[2]).base.id; + return this->get_weapon(item.data1[1], item.data1[2]).id; case 1: if (item.data1[1] == 3) { - return this->get_unit(item.data1[2]).base.id; + return this->get_unit(item.data1[2]).id; } else if ((item.data1[1] == 1) || (item.data1[1] == 2)) { - return this->get_armor_or_shield(item.data1[1], item.data1[2]).base.id; + return this->get_armor_or_shield(item.data1[1], item.data1[2]).id; } throw runtime_error("invalid item"); case 2: - return this->get_mag(item.data1[1]).base.id; + return this->get_mag(item.data1[1]).id; case 3: if (item.data1[1] == 2) { - return this->get_tool(2, item.data1[4]).base.id; + return this->get_tool(2, item.data1[4]).id; } else { - return this->get_tool(item.data1[1], item.data1[2]).base.id; + return this->get_tool(item.data1[1], item.data1[2]).id; } throw logic_error("this should be impossible"); case 4: @@ -1060,21 +795,21 @@ uint32_t ItemParameterTable::get_item_id(const ItemData& item) const { uint32_t ItemParameterTable::get_item_team_points(const ItemData& item) const { switch (item.data1[0]) { case 0: - return this->get_weapon(item.data1[1], item.data1[2]).base.team_points; + return this->get_weapon(item.data1[1], item.data1[2]).team_points; case 1: if (item.data1[1] == 3) { - return this->get_unit(item.data1[2]).base.team_points; + return this->get_unit(item.data1[2]).team_points; } else if ((item.data1[1] == 1) || (item.data1[1] == 2)) { - return this->get_armor_or_shield(item.data1[1], item.data1[2]).base.team_points; + return this->get_armor_or_shield(item.data1[1], item.data1[2]).team_points; } throw runtime_error("invalid item"); case 2: - return this->get_mag(item.data1[1]).base.team_points; + return this->get_mag(item.data1[1]).team_points; case 3: if (item.data1[1] == 2) { - return this->get_tool(2, item.data1[4]).base.team_points; + return this->get_tool(2, item.data1[4]).team_points; } else { - return this->get_tool(item.data1[1], item.data1[2]).base.team_points; + return this->get_tool(item.data1[1], item.data1[2]).team_points; } throw logic_error("this should be impossible"); case 4: @@ -1085,17 +820,20 @@ uint32_t ItemParameterTable::get_item_team_points(const ItemData& item) const { } uint8_t ItemParameterTable::get_item_base_stars(const ItemData& item) const { - if (item.data1[0] == 2) { - return (item.data1[1] >= this->first_rare_mag_index) ? 12 : 0; - } else if (item.data1[0] < 2) { - return this->get_item_stars(this->get_item_id(item)); - } else if (item.data1[0] == 3) { - const auto& def = (item.data1[1] == 2) - ? this->get_tool(2, item.data1[4]) - : this->get_tool(item.data1[1], item.data1[2]); - return (def.item_flags & 0x80) ? 12 : 0; - } else { - return 0; + switch (item.data1[0]) { + case 0: + case 1: + return this->get_item_stars(this->get_item_id(item)); + case 2: + return (item.data1[1] >= 0x28) ? 12 : 0; + case 3: { + const auto& def = (item.data1[1] == 2) + ? this->get_tool(2, item.data1[4]) + : this->get_tool(item.data1[1], item.data1[2]); + return (def.item_flags & 0x80) ? 12 : 0; + } + default: + return 0; } } @@ -1131,35 +869,6 @@ bool ItemParameterTable::is_item_rare(const ItemData& item) const { } } -bool ItemParameterTable::is_unsealable_item(uint8_t data1_0, uint8_t data1_1, uint8_t data1_2) const { - uint32_t offset, count; - if (this->offsets_dc_protos || this->offsets_v1_v2 || this->offsets_gc_nte) { - return false; - } else if (this->offsets_v3_le) { - const auto& co = this->r.pget(this->offsets_v3_le->unsealable_table); - offset = co.offset; - count = co.count; - } else if (this->offsets_v3_be) { - const auto& co = this->r.pget(this->offsets_v3_be->unsealable_table); - offset = co.offset; - count = co.count; - } else if (this->offsets_v4) { - const auto& co = this->r.pget(this->offsets_v4->unsealable_table); - offset = co.offset; - count = co.count; - } else { - throw logic_error("table is not v2, v3, or v4"); - } - - const auto* defs = &this->r.pget(offset, count * sizeof(UnsealableItem)); - for (size_t z = 0; z < count; z++) { - if ((defs[z].item[0] == data1_0) && (defs[z].item[1] == data1_1) && (defs[z].item[2] == data1_2)) { - return true; - } - } - return false; -} - bool ItemParameterTable::is_unsealable_item(const ItemData& item) const { return this->is_unsealable_item(item.data1[0], item.data1[1], item.data1[2]); } @@ -1187,83 +896,6 @@ const std::vector& ItemParameterTable::get_ } } -const std::map>& ItemParameterTable::get_all_item_combinations() const { - if (this->item_combination_index.empty()) { - uint32_t offset, count; - if (this->offsets_dc_protos || this->offsets_v1_v2 || this->offsets_gc_nte) { - static const std::map> empty_map; - return empty_map; - } else if (this->offsets_v3_le) { - const auto& co = this->r.pget(this->offsets_v3_le->combination_table); - offset = co.offset; - count = co.count; - } else if (this->offsets_v3_be) { - const auto& co = this->r.pget(this->offsets_v3_be->combination_table); - offset = co.offset; - count = co.count; - } else if (this->offsets_v4) { - const auto& co = this->r.pget(this->offsets_v4->combination_table); - offset = co.offset; - count = co.count; - } else { - throw logic_error("table is not v2, v3, or v4"); - } - - const auto* defs = &this->r.pget(offset, count * sizeof(ItemCombination)); - for (size_t z = 0; z < count; z++) { - const auto& def = defs[z]; - uint32_t key = (def.used_item[0] << 16) | (def.used_item[1] << 8) | def.used_item[2]; - this->item_combination_index[key].emplace_back(def); - } - } - return this->item_combination_index; -} - -template -size_t ItemParameterTable::num_events_t(uint32_t base_offset) const { - return this->r.pget>(base_offset).count; -} - -size_t ItemParameterTable::num_events() const { - if (this->offsets_dc_protos || this->offsets_v1_v2 || this->offsets_gc_nte) { - return 0; - } else if (this->offsets_v3_le) { - return this->num_events_t(this->offsets_v3_le->unwrap_table); - } else if (this->offsets_v3_be) { - return this->num_events_t(this->offsets_v3_be->unwrap_table); - } else if (this->offsets_v4) { - return this->num_events_t(this->offsets_v4->unwrap_table); - } else { - throw logic_error("table is not v2, v3, or v4"); - } -} - -template -std::pair ItemParameterTable::get_event_items_t( - uint32_t base_offset, uint8_t event_number) const { - const auto& co = this->r.pget>(base_offset); - if (event_number >= co.count) { - throw out_of_range("invalid event number"); - } - const auto& event_co = this->r.pget>(co.offset + sizeof(ArrayRefT) * event_number); - const auto* defs = &this->r.pget(event_co.offset, event_co.count * sizeof(EventItem)); - return make_pair(defs, event_co.count); -} - -std::pair ItemParameterTable::get_event_items(uint8_t event_number) const { - if (this->offsets_dc_protos || this->offsets_v1_v2 || this->offsets_gc_nte) { - return make_pair(nullptr, 0); - } else if (this->offsets_v3_le) { - return this->get_event_items_t(this->offsets_v3_le->unwrap_table, event_number); - } else if (this->offsets_v3_be) { - return this->get_event_items_t(this->offsets_v3_be->unwrap_table, event_number); - } else if (this->offsets_v4) { - return this->get_event_items_t(this->offsets_v4->unwrap_table, event_number); - } else { - throw logic_error("table is not v2, v3, or v4"); - } -} - size_t ItemParameterTable::price_for_item(const ItemData& item) const { switch (item.data1[0]) { case 0: { @@ -1340,17 +972,447 @@ size_t ItemParameterTable::price_for_item(const ItemData& item) const { throw logic_error("this should be impossible"); } -MagEvolutionTable::MagEvolutionTable(shared_ptr data, size_t num_mags) - : data(data), - num_mags(num_mags), - r(*data) { - size_t offset_table_offset = this->r.pget_u32l(this->data->size() - 0x10); - this->offsets = &r.pget(offset_table_offset); -} +template < + typename RootT, + typename WeaponT, + size_t NumWeaponClasses, + typename ArmorOrShieldT, + typename UnitT, + typename ToolT, + size_t NumToolClasses, + typename MagT, + size_t ItemStarsFirstID, + size_t ItemStarsLastID, + size_t SpecialStarsBeginIndex, + size_t NumSpecials, + bool BE> +class ItemParameterTableT : public ItemParameterTable { +public: + explicit ItemParameterTableT(std::shared_ptr data) + : ItemParameterTable(data), + root(&this->r.pget(BE ? r.pget_u32b(r.size() - 0x10) : r.pget_u32l(r.size() - 0x10))) {} + ~ItemParameterTableT() = default; -uint8_t MagEvolutionTable::get_evolution_number(uint8_t data1_1) const { - if (data1_1 >= this->num_mags) { - throw runtime_error("invalid mag number"); + inline size_t indirect_lookup_2d_count(size_t base_offset, size_t co_index) const { + return this->r.pget>(base_offset + sizeof(ArrayRefT) * co_index).count; + } + + 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) { + throw out_of_range("2-D array index out of range"); + } + return this->r.pget(co.offset + sizeof(T) * item_index); + } + + virtual size_t num_weapon_classes() const { + return NumWeaponClasses; + } + + virtual size_t num_weapons_in_class(uint8_t data1_1) const { + if (data1_1 >= NumWeaponClasses) { + 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) { + throw out_of_range("weapon ID out of range"); + } + uint16_t key = (data1_1 << 8) | data1_2; + auto it = this->weapons.find(key); + if (it == this->weapons.end()) { + const auto& weapon = this->indirect_lookup_2d(this->root->weapon_table, data1_1, data1_2); + it = this->weapons.emplace(key, weapon).first; + } + return it->second; + } + + virtual size_t num_armors_or_shields_in_class(uint8_t data1_1) const { + 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); + } + + virtual const ArmorOrShield& get_armor_or_shield(uint8_t data1_1, uint8_t data1_2) const { + if ((data1_1 < 1) || (data1_1 > 2)) { + throw out_of_range("armor/shield class ID out of range"); + } + auto& vec = (data1_1 == 1) ? this->armors : this->shields; + while (vec.size() <= data1_2) { + vec.emplace_back(this->indirect_lookup_2d(this->root->armor_table, data1_1 - 1, vec.size())); + } + return vec[data1_2]; + } + + virtual size_t num_units() const { + return this->indirect_lookup_2d_count(this->root->unit_table, 0); + } + + virtual const Unit& get_unit(uint8_t data1_2) const { + while (this->units.size() <= data1_2) { + this->units.emplace_back(this->indirect_lookup_2d(this->root->unit_table, 0, this->units.size())); + } + return this->units[data1_2]; + } + + virtual size_t num_mags() const { + return this->indirect_lookup_2d_count(this->root->mag_table, 0); + } + + virtual const Mag& get_mag(uint8_t data1_1) const { + while (this->mags.size() <= data1_1) { + this->mags.emplace_back(this->indirect_lookup_2d(this->root->mag_table, 0, data1_1)); + } + return this->mags[data1_1]; + } + + virtual size_t num_tool_classes() const { + return NumToolClasses; + } + + virtual size_t num_tools_in_class(uint8_t data1_1) const { + if (data1_1 >= NumToolClasses) { + 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) { + throw out_of_range("tool class ID out of range"); + } + uint16_t key = (data1_1 << 8) | data1_2; + auto it = this->tools.find(key); + if (it == this->tools.end()) { + const auto& tool = this->indirect_lookup_2d(this->root->tool_table, data1_1, data1_2); + it = this->tools.emplace(key, tool).first; + } + return it->second; + } + + 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& co = cos[z]; + const auto* defs = &this->r.pget(co.offset, sizeof(ToolT) * co.count); + for (size_t y = 0; y < co.count; y++) { + if (defs[y].base.id == id) { + return make_pair(z, y); + } + } + } + throw out_of_range(std::format("invalid tool class {:08X}", id)); + } + + virtual float get_sale_divisor(uint8_t data1_0, uint8_t data1_1) const { + if (data1_0 == 0) { + if (data1_1 >= NumWeaponClasses) { + return 0.0f; + } + if constexpr (requires { this->root->weapon_sale_divisor_table; }) { + return this->r.pget>(this->root->weapon_sale_divisor_table + data1_1 * sizeof(F32T)); + } else { + return this->r.pget(this->root->weapon_integral_sale_divisor_table + data1_1 * sizeof(uint8_t)); + } + } + + if constexpr (requires { this->root->non_weapon_sale_divisor_table; }) { + const auto& divisors = this->r.pget>(this->root->non_weapon_sale_divisor_table); + if (data1_0 == 1) { + switch (data1_1) { + case 1: + return divisors.armor_divisor; + case 2: + return divisors.shield_divisor; + case 3: + return divisors.unit_divisor; + } + } else if (data1_0 == 2) { + return divisors.mag_divisor; + } + } else { + if (data1_0 == 1) { + const auto& divisors = this->r.pget( + this->root->non_weapon_integral_sale_divisor_table); + switch (data1_1) { + case 1: + return divisors.armor_divisor; + case 2: + return divisors.shield_divisor; + case 3: + return divisors.unit_divisor; + } + } + } + + return 0.0f; + } + + virtual const MagFeedResult& get_mag_feed_result(uint8_t table_index, uint8_t item_index) const { + if (table_index >= 8) { + throw out_of_range("invalid mag feed table index"); + } + if (item_index >= 11) { + throw out_of_range("invalid mag feed item index"); + } + const auto& table_offsets = this->r.pget>(this->root->mag_feed_table); + return this->r.pget(table_offsets[table_index])[item_index]; + } + + 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) + : 0; + } + + virtual uint8_t get_special_stars(uint8_t special) const { + return ((special & 0x3F) && !(special & 0x80)) ? this->get_item_stars(special + SpecialStarsBeginIndex) : 0; + } + + virtual size_t num_specials() const { + return NumSpecials; + } + + virtual const Special& get_special(uint8_t special) const { + special &= 0x3F; + if (special >= NumSpecials) { + throw out_of_range("invalid special index"); + } + while (this->specials.size() <= special) { + this->specials.emplace_back(this->r.pget>( + this->root->special_table + sizeof(SpecialT) * this->specials.size())); + } + return this->specials[special]; + } + + virtual const StatBoost& get_stat_boost(uint8_t entry_index) const { + while (this->stat_boosts.size() <= entry_index) { + this->stat_boosts.emplace_back(this->r.pget>( + this->root->stat_boost_table + sizeof(StatBoostT) * this->stat_boosts.size())); + } + return this->stat_boosts[entry_index]; + } + + virtual uint8_t get_max_tech_level(uint8_t char_class, uint8_t tech_num) const { + if (char_class >= 12) { + throw out_of_range("invalid character class"); + } + if (tech_num >= 19) { + throw out_of_range("invalid technique number"); + } + if constexpr (requires { this->root->max_tech_level_table; }) { + return r.pget_u8(this->root->max_tech_level_table + tech_num * 12 + char_class); + } else { + if ((tech_num == 14) || (tech_num == 17)) { // Ryuker or Reverser + return 0; + } else { + return ((char_class == 6) || (char_class == 7) || (char_class == 8) || (char_class == 10)) ? 29 : 14; + } + } + } + + virtual uint8_t get_weapon_class(uint8_t data1_1) const { + return (data1_1 < NumWeaponClasses) ? this->r.pget_u8(this->root->weapon_class_table + data1_1) : 0x00; + } + + virtual bool is_unsealable_item(uint8_t data1_0, uint8_t data1_1, uint8_t data1_2) const { + if constexpr (requires { this->root->unsealable_table; }) { + const auto& co = this->r.pget>(this->root->unsealable_table); + const auto* defs = &this->r.pget(co.offset, co.count * sizeof(UnsealableItem)); + for (size_t z = 0; z < co.count; z++) { + if ((defs[z].item[0] == data1_0) && (defs[z].item[1] == data1_1) && (defs[z].item[2] == data1_2)) { + return true; + } + } + } + return false; + } + + virtual const std::map>& get_all_item_combinations() const { + if constexpr (requires { this->root->combination_table; }) { + if (this->item_combination_index.empty()) { + 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++) { + const auto& def = defs[z]; + uint32_t key = (def.used_item[0] << 16) | (def.used_item[1] << 8) | def.used_item[2]; + this->item_combination_index[key].emplace_back(def); + } + } + return this->item_combination_index; + } + static const std::map> empty_map; + return empty_map; + } + + virtual size_t num_events() const { + if constexpr (requires { this->root->unwrap_table; }) { + return this->r.pget>(this->root->unwrap_table).count; + } else { + return 0; + } + } + + virtual std::pair get_event_items(uint8_t event_number) const { + if constexpr (requires { this->root->unwrap_table; }) { + const auto& co = this->r.pget>(this->root->unwrap_table); + if (event_number >= co.count) { + throw out_of_range("invalid event number"); + } + const auto& event_co = this->r.pget>(co.offset + sizeof(ArrayRefT) * event_number); + const auto* defs = &this->r.pget(event_co.offset, event_co.count * sizeof(EventItem)); + return make_pair(defs, event_co.count); + } else { + return make_pair(nullptr, 0); + } + } + +protected: + const RootT* root; +}; + +using ItemParameterTableDCNTE = ItemParameterTableT< + 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 + 0x22, // size_t ItemStarsFirstID + 0x168, // size_t ItemStarsLastID + 0xAA, // size_t SpecialStarsBeginIndex + 0x28, // size_t NumSpecials + false>; // bool BE +using ItemParameterTableDC112000 = ItemParameterTableT< + 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 + 0x26, // size_t ItemStarsFirstID + 0x16C, // size_t ItemStarsLastID + 0xAE, // size_t SpecialStarsBeginIndex + 0x28, // size_t NumSpecials + false>; // bool BE +using ItemParameterTableV1 = ItemParameterTableT< + RootV1V2, // 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 + 0x26, // size_t ItemStarsFirstID + 0x16C, // size_t ItemStarsLastID + 0xAE, // size_t SpecialStarsBeginIndex + 0x29, // size_t NumSpecials + false>; // bool BE +using ItemParameterTableV2 = ItemParameterTableT< + RootV1V2, // 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 + 0x4E, // size_t ItemStarsFirstID + 0x215, // size_t ItemStarsLastID + 0x138, // size_t SpecialStarsBeginIndex + 0x29, // size_t NumSpecials + false>; // bool BE +using ItemParameterTableGCNTE = ItemParameterTableT< + 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 + 0x76, // size_t ItemStarsFirstID + 0x298, // size_t ItemStarsLastID + 0x1A3, // size_t SpecialStarsBeginIndex + 0x29, // size_t NumSpecials + true>; // bool BE +using ItemParameterTableGC = ItemParameterTableT< + 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 + 0x94, // size_t ItemStarsFirstID + 0x2F7, // size_t ItemStarsLastID + 0x1CB, // size_t SpecialStarsBeginIndex + 0x29, // size_t NumSpecials + true>; // bool BE +using ItemParameterTableXB = ItemParameterTableT< + 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 + 0x94, // size_t ItemStarsFirstID + 0x2F7, // size_t ItemStarsLastID + 0x1CB, // size_t SpecialStarsBeginIndex + 0x29, // size_t NumSpecials + false>; // bool BE +using ItemParameterTableV4 = ItemParameterTableT< + 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 + 0xB1, // size_t ItemStarsFirstID + 0x437, // size_t ItemStarsLastID + 0x256, // size_t SpecialStarsBeginIndex + 0x29, // size_t NumSpecials + false>; // bool BE + +std::shared_ptr ItemParameterTable::create( + std::shared_ptr data, Version version) { + switch (version) { + case Version::DC_NTE: + return std::make_shared(data); + case Version::DC_11_2000: + return std::make_shared(data); + case Version::DC_V1: + return std::make_shared(data); + case Version::DC_V2: + case Version::PC_NTE: + case Version::PC_V2: + return std::make_shared(data); + case Version::GC_NTE: + return std::make_shared(data); + case Version::GC_V3: + case Version::GC_EP3: + case Version::GC_EP3_NTE: + return std::make_shared(data); + case Version::XB_V3: + return std::make_shared(data); + case Version::BB_V4: + return std::make_shared(data); + default: + throw std::logic_error("Cannot create item parameter table for this version"); } - return this->r.pget_u8(this->offsets->evolution_number + data1_1); } diff --git a/src/ItemParameterTable.hh b/src/ItemParameterTable.hh index b0d814c6..6c9605a8 100644 --- a/src/ItemParameterTable.hh +++ b/src/ItemParameterTable.hh @@ -13,6 +13,7 @@ #include #include +#include "CommonFileFormats.hh" #include "ItemData.hh" #include "Text.hh" #include "Types.hh" @@ -40,310 +41,113 @@ const char* phosg::name_for_enum(ServerDropMode value); class ItemParameterTable { public: - // TODO: This implementation is ugly. We should use real classes and virtual functions instead of manually branching - // on various offset table pointers being null or not in each public function. Rewrite this and make it better. + // These structures are all parsed representations of the file's data. The file's actual structures are defined in + // ItemParamterTable.cc, with analogous names to these structures. - template - struct ItemBaseV2T { + struct ItemBase { // id specifies several things; notably, it doubles as the index of the item's name in the text archive (e.g. // TextEnglish) collection 0. - /* 00 */ U32T id = 0xFFFFFFFF; - /* 04 */ - } __attribute__((packed)); - template - struct ItemBaseV3T : ItemBaseV2T { - /* 04 */ U16T type = 0; // "Model" in Soly's ItemPMT editor - /* 06 */ U16T skin = 0; // "Texture" in Soly's ItemPMT editor - /* 08 */ - } __attribute__((packed)); - template - struct ItemBaseV4T : ItemBaseV3T { - /* 08 */ U32T team_points = 0; - /* 0C */ - } __attribute__((packed)); - - struct WeaponV4; - struct WeaponDCProtos { - /* 00 */ ItemBaseV2T base; - /* 04 */ le_uint16_t class_flags = 0; - /* 06 */ le_uint16_t atp_min = 0; - /* 08 */ le_uint16_t atp_max = 0; - /* 0A */ le_uint16_t atp_required = 0; - /* 0C */ le_uint16_t mst_required = 0; - /* 0E */ le_uint16_t ata_required = 0; - /* 10 */ uint8_t max_grind = 0; - /* 11 */ uint8_t photon = 0; - /* 12 */ uint8_t special = 0; - /* 13 */ uint8_t ata = 0; - /* 14 */ - - WeaponV4 to_v4() const; - } __packed_ws__(WeaponDCProtos, 0x14); - - struct WeaponV1V2 { - /* 00 */ ItemBaseV2T base; - /* 04 */ le_uint16_t class_flags = 0; - /* 06 */ le_uint16_t atp_min = 0; - /* 08 */ le_uint16_t atp_max = 0; - /* 0A */ le_uint16_t atp_required = 0; - /* 0C */ le_uint16_t mst_required = 0; - /* 0E */ le_uint16_t ata_required = 0; - /* 10 */ uint8_t max_grind = 0; - /* 11 */ uint8_t photon = 0; - /* 12 */ uint8_t special = 0; - /* 13 */ uint8_t ata = 0; - /* 14 */ uint8_t stat_boost_entry_index = 0; // TODO: This could be larger (16 or 32 bits) - /* 15 */ parray unknown_a9; - /* 18 */ - - WeaponV4 to_v4() const; - } __packed_ws__(WeaponV1V2, 0x18); - - struct WeaponGCNTE { - /* 00 */ ItemBaseV3T base; - /* 08 */ be_uint16_t class_flags = 0; - /* 0A */ be_uint16_t atp_min = 0; - /* 0C */ be_uint16_t atp_max = 0; - /* 0E */ be_uint16_t atp_required = 0; - /* 10 */ be_uint16_t mst_required = 0; - /* 12 */ be_uint16_t ata_required = 0; - /* 14 */ be_uint16_t mst = 0; - /* 16 */ uint8_t max_grind = 0; - /* 17 */ uint8_t photon = 0; - /* 18 */ uint8_t special = 0; - /* 19 */ uint8_t ata = 0; - /* 1A */ uint8_t stat_boost_entry_index = 0; - /* 1B */ uint8_t projectile = 0; - /* 1C */ int8_t trail1_x = 0; - /* 1D */ int8_t trail1_y = 0; - /* 1E */ int8_t trail2_x = 0; - /* 1F */ int8_t trail2_y = 0; - /* 20 */ int8_t color = 0; - /* 21 */ parray unknown_a1 = 0; - /* 24 */ - - WeaponV4 to_v4() const; - } __packed_ws__(WeaponGCNTE, 0x24); - - template - struct WeaponV3T { - /* 00 */ ItemBaseV3T base; - /* 08 */ U16T class_flags = 0; - /* 0A */ U16T atp_min = 0; - /* 0C */ U16T atp_max = 0; - /* 0E */ U16T atp_required = 0; - /* 10 */ U16T mst_required = 0; - /* 12 */ U16T ata_required = 0; - /* 14 */ U16T mst = 0; - /* 16 */ uint8_t max_grind = 0; - /* 17 */ uint8_t photon = 0; - /* 18 */ uint8_t special = 0; - /* 19 */ uint8_t ata = 0; - /* 1A */ uint8_t stat_boost_entry_index = 0; - /* 1B */ uint8_t projectile = 0; - /* 1C */ int8_t trail1_x = 0; - /* 1D */ int8_t trail1_y = 0; - /* 1E */ int8_t trail2_x = 0; - /* 1F */ int8_t trail2_y = 0; - /* 20 */ int8_t color = 0; - /* 21 */ parray unknown_a1 = 0; - /* 24 */ uint8_t unknown_a4 = 0; - /* 25 */ uint8_t unknown_a5 = 0; - /* 26 */ uint8_t tech_boost = 0; - // Flags in this field: + uint32_t id = 0xFFFFFFFF; + uint16_t type = 0; // "Model" in Soly's ItemPMT editor + uint16_t skin = 0; // "Texture" in Soly's ItemPMT editor + uint32_t team_points = 0; + }; + struct Weapon : ItemBase { + uint16_t class_flags = 0; + uint16_t atp_min = 0; + uint16_t atp_max = 0; + uint16_t atp_required = 0; + uint16_t mst_required = 0; + uint16_t ata_required = 0; + uint16_t mst = 0; + uint8_t max_grind = 0; + uint8_t photon = 0; + uint8_t special = 0; + uint8_t ata = 0; + uint8_t stat_boost_entry_index = 0; // TODO: This could be larger (16 or 32 bits) + uint8_t projectile = 0; + int8_t trail1_x = 0; + int8_t trail1_y = 0; + int8_t trail2_x = 0; + int8_t trail2_y = 0; + int8_t color = 0; + parray unknown_a1 = 0; + uint8_t unknown_a4 = 0; + uint8_t unknown_a5 = 0; + uint8_t tech_boost = 0; + // Bits in behavior_flags: // 01 = disable combos (weapon can only be used once in a row) // 02 = TODO (sets TItemWeapon flag 40000; used in TItemWeapon_v1E) // 04 = TODO (sets TItemWeapon flag 80000; used in TItemWeapon_v1E) // 08 = weapon cannot have attributes (they are ignored if present) - /* 27 */ uint8_t behavior_flags = 0; - /* 28 */ + uint8_t behavior_flags = 0; + }; - WeaponV4 to_v4() const; - } __attribute__((packed)); - - using WeaponV3 = WeaponV3T; - using WeaponV3BE = WeaponV3T; - check_struct_size(WeaponV3, 0x28); - check_struct_size(WeaponV3BE, 0x28); - - struct WeaponV4 { - /* 00 */ ItemBaseV4T base; - /* 0C */ le_uint16_t class_flags = 0x00FF; - /* 0E */ le_uint16_t atp_min = 0; - /* 10 */ le_uint16_t atp_max = 0; - /* 12 */ le_uint16_t atp_required = 0; - /* 14 */ le_uint16_t mst_required = 0; - /* 16 */ le_uint16_t ata_required = 0; - /* 18 */ le_uint16_t mst = 0; - /* 1A */ uint8_t max_grind = 0; - /* 1B */ uint8_t photon = 0; - /* 1C */ uint8_t special = 0; - /* 1D */ uint8_t ata = 0; - /* 1E */ uint8_t stat_boost_entry_index = 0; - /* 1F */ uint8_t projectile = 0; - /* 20 */ int8_t trail1_x = 0; - /* 21 */ int8_t trail1_y = 0; - /* 22 */ int8_t trail2_x = 0; - /* 23 */ int8_t trail2_y = 0; - /* 24 */ int8_t color = 0; - /* 25 */ parray unknown_a1 = 0; - /* 28 */ uint8_t unknown_a4 = 0; - /* 29 */ uint8_t unknown_a5 = 0; - /* 2A */ uint8_t tech_boost = 0; - /* 2B */ uint8_t behavior_flags = 0; - /* 2C */ - } __packed_ws__(WeaponV4, 0x2C); - - template - struct ArmorOrShieldT { - /* V1/V2 offsets */ - /* 00 */ BaseT base; - /* 04 */ U16T dfp = 0; - /* 06 */ U16T evp = 0; - /* 08 */ uint8_t block_particle = 0; - /* 09 */ uint8_t block_effect = 0; - /* 0A */ U16T class_flags = 0x00FF; - /* 0C */ uint8_t required_level = 0; - /* 0D */ uint8_t efr = 0; - /* 0E */ uint8_t eth = 0; - /* 0F */ uint8_t eic = 0; - /* 10 */ uint8_t edk = 0; - /* 11 */ uint8_t elt = 0; - /* 12 */ uint8_t dfp_range = 0; - /* 13 */ uint8_t evp_range = 0; - /* 14 */ - } __attribute__((packed)); - - template - struct ArmorOrShieldFinalT : ArmorOrShieldT { - /* 14 */ uint8_t stat_boost_entry_index = 0; - /* 15 */ uint8_t tech_boost = 0; + struct ArmorOrShield : ItemBase { + uint16_t dfp = 0; + uint16_t evp = 0; + uint8_t block_particle = 0; + uint8_t block_effect = 0; + uint16_t class_flags = 0x00FF; + uint8_t required_level = 0; + uint8_t efr = 0; + uint8_t eth = 0; + uint8_t eic = 0; + uint8_t edk = 0; + uint8_t elt = 0; + uint8_t dfp_range = 0; + uint8_t evp_range = 0; + uint8_t stat_boost_entry_index = 0; + uint8_t tech_boost = 0; // TODO: Figure out what this does. Only a few values appear to do anything: - // Shields: - // 01 sets item->flags |= 4 (used in TItemProShield_v10) - // 03 sets item->flags |= 8 (used in TItemProShield_v1A) // Armors: // 01 sets item->flags |= 1 (used in TItemProArmor_v10) // 02 constructs TItemProArmorParticle instead of TItemProArmor - /* 16 */ uint8_t flags_type = 0; - /* 17 */ uint8_t unknown_a4 = 0; - /* 18 */ - } __attribute__((packed)); - using ArmorOrShieldV4 = ArmorOrShieldFinalT, false>; - check_struct_size(ArmorOrShieldV4, 0x20); - struct ArmorOrShieldDCProtos : ArmorOrShieldT, false> { - ArmorOrShieldV4 to_v4() const; - } __packed_ws__(ArmorOrShieldDCProtos, 0x14); + // Shields: + // 01 sets item->flags |= 4 (used in TItemProShield_v10) + // 03 sets item->flags |= 8 (used in TItemProShield_v1A) + uint8_t flags_type = 0; + uint8_t unknown_a4 = 0; + }; - struct ArmorOrShieldV1V2 : ArmorOrShieldFinalT, false> { - ArmorOrShieldV4 to_v4() const; - } __packed_ws__(ArmorOrShieldV1V2, 0x18); - template - struct ArmorOrShieldV3T : ArmorOrShieldFinalT, BE> { - ArmorOrShieldV4 to_v4() const; - } __attribute__((packed)); - using ArmorOrShieldV3 = ArmorOrShieldV3T; - using ArmorOrShieldV3BE = ArmorOrShieldV3T; - check_struct_size(ArmorOrShieldV3, 0x1C); - check_struct_size(ArmorOrShieldV3BE, 0x1C); + struct Unit : ItemBase { + uint16_t stat = 0; + uint16_t stat_amount = 0; + int16_t modifier_amount = 0; + }; - template - struct UnitT { - /* V1/V2 offsets */ - /* 00 */ BaseT base; - /* 04 */ U16T stat = 0; - /* 06 */ U16T stat_amount = 0; - /* 08 */ - } __attribute__((packed)); - - template - struct UnitFinalT : UnitT { - /* 08 */ S16T modifier_amount = 0; - /* 0A */ parray unused; - /* 0C */ - } __attribute__((packed)); - using UnitV4 = UnitFinalT, false>; - check_struct_size(UnitV4, 0x14); - struct UnitDCProtos : UnitT, false> { - UnitV4 to_v4() const; - } __packed_ws__(UnitDCProtos, 0x08); - struct UnitV1V2 : UnitFinalT, false> { - UnitV4 to_v4() const; - } __packed_ws__(UnitV1V2, 0x0C); - template - struct UnitV3T : UnitFinalT, BE> { - UnitV4 to_v4() const; - } __attribute__((packed)); - using UnitV3 = UnitV3T; - using UnitV3BE = UnitV3T; - check_struct_size(UnitV3, 0x10); - check_struct_size(UnitV3BE, 0x10); - - template - struct MagT { - /* V1/V2 offsets */ - /* 00 */ BaseT base; - /* 04 */ U16T feed_table = 0; - /* 06 */ uint8_t photon_blast = 0; - /* 07 */ uint8_t activation = 0; - /* 08 */ uint8_t on_pb_full = 0; - /* 09 */ uint8_t on_low_hp = 0; - /* 0A */ uint8_t on_death = 0; - /* 0B */ uint8_t on_boss = 0; + struct Mag : ItemBase { + uint16_t feed_table = 0; + uint8_t photon_blast = 0; + uint8_t activation = 0; + uint8_t on_pb_full = 0; + uint8_t on_low_hp = 0; + uint8_t on_death = 0; + uint8_t on_boss = 0; // These flags control how likely each effect is to activate. First, the game computes step_synchro as follows: - // if synchro in [0, 30], step_synchro = 0 - // if synchro in [31, 60], step_synchro = 15 - // if synchro in [61, 80], step_synchro = 25 - // if synchro in [81, 100], step_synchro = 30 - // if synchro in [101, 120], step_synchro = 35 + // If synchro is in [0, 30], set step_synchro = 0 + // If synchro is in [31, 60], set step_synchro = 15 + // If synchro is in [61, 80], set step_synchro = 25 + // If synchro is in [81, 100], set step_synchro = 30 + // If synchro is in [101, 120], set step_synchro = 35 // Then, the percent chance of the effect occurring upon its trigger (e.g. entering a boss arena) is: - // flag == 0 => activation - // flag == 1 => activation + step_synchro - // flag == 2 => step_synchro - // flag == 3 => activation - 10 - // flag == 4 => step_synchro - 10 - // anything else => 0 (effect never occurs) - /* 0C */ uint8_t on_pb_full_flag = 0; - /* 0D */ uint8_t on_low_hp_flag = 0; - /* 0E */ uint8_t on_death_flag = 0; - /* 0F */ uint8_t on_boss_flag = 0; - /* 10 */ - } __attribute__((packed)); + // flag == 0 => chance is (activation) + // flag == 1 => chance is (activation + step_synchro) + // flag == 2 => chance is (step_synchro) + // flag == 3 => chance is (activation - 10) + // flag == 4 => chance is (step_synchro - 10) + // anything else => chance is 0 (effect never occurs) + uint8_t on_pb_full_flag = 0; + uint8_t on_low_hp_flag = 0; + uint8_t on_death_flag = 0; + uint8_t on_boss_flag = 0; + uint16_t class_flags = 0x00FF; + }; - struct MagV4 : MagT, false> { - le_uint16_t class_flags = 0x00FF; - parray unused; - } __packed_ws__(MagV4, 0x1C); - struct MagV1 : MagT, false> { - MagV4 to_v4() const; - } __packed_ws__(MagV1, 0x10); - struct MagV2 : MagT, false> { - /* 10 */ le_uint16_t class_flags = 0x00FF; - /* 12 */ parray unused; - /* 14 */ - - MagV4 to_v4() const; - } __packed_ws__(MagV2, 0x14); - template - struct MagV3T : MagT, BE> { - /* 10 */ U16T class_flags = 0x00FF; - /* 12 */ parray unused; - /* 14 */ - - MagV4 to_v4() const; - } __attribute__((packed)); - using MagV3 = MagV3T; - using MagV3BE = MagV3T; - check_struct_size(MagV3, 0x18); - check_struct_size(MagV3BE, 0x18); - - template - struct ToolT { - /* V1/V2 offsets */ - /* 00 */ BaseT base; - /* 04 */ U16T amount = 0; - /* 06 */ U16T tech = 0; - /* 08 */ S32T cost = 0; + struct Tool : ItemBase { + uint16_t amount = 0; + uint16_t tech = 0; + int32_t cost = 0; // Bits in item_flags: // 00000001 - ever usable by player ("Use" appears in inventory menu) // 00000002 - unknown @@ -352,24 +156,9 @@ public: // 00000010 - usable in Pioneer 2 / Lab // 00000020 - usable in boss arenas // 00000040 - usable in Challenge mode - // 00000080 - is rare (renders as red box; V3+) - /* 0C */ U32T item_flags = 0; - /* 10 */ - } __attribute__((packed)); - - using ToolV4 = ToolT, false>; - check_struct_size(ToolV4, 0x18); - struct ToolV1V2 : ToolT, false> { - ToolV4 to_v4() const; - } __packed_ws__(ToolV1V2, 0x10); - template - struct ToolV3T : ToolT, BE> { - ToolV4 to_v4() const; - } __attribute__((packed)); - using ToolV3 = ToolV3T; - using ToolV3BE = ToolV3T; - check_struct_size(ToolV3, 0x14); - check_struct_size(ToolV3BE, 0x14); + // 00000080 - is rare (renders as red box; V3+ only) + /* 0C */ uint32_t item_flags = 0; + }; struct MagFeedResult { int8_t def = 0; @@ -381,29 +170,12 @@ public: parray unused; } __packed_ws__(MagFeedResult, 8); - using MagFeedResultsList = parray; + struct Special { + uint16_t type = 0xFFFF; + uint16_t amount = 0; + }; - template - struct MagFeedResultsListOffsetsT { - parray, 8> offsets; // Offsets of MagFeedResultsList objects - } __attribute__((packed)); - using MagFeedResultsListOffsets = MagFeedResultsListOffsetsT; - using MagFeedResultsListOffsetsBE = MagFeedResultsListOffsetsT; - check_struct_size(MagFeedResultsListOffsets, 0x20); - check_struct_size(MagFeedResultsListOffsetsBE, 0x20); - - template - struct SpecialT { - U16T type = 0xFFFF; - U16T amount = 0; - } __attribute__((packed)); - using Special = SpecialT; - using SpecialBE = SpecialT; - check_struct_size(Special, 4); - check_struct_size(SpecialBE, 4); - - template - struct StatBoostT { + struct StatBoost { // Only the first of these stat/amount pairs is used in most versions of the game. In DC 11/2000 Sega apparently // changed the loop from `for (z = 0; z != 2; z++)` to `for (z = 0; z != 1; z++)`, so only the first stat/amount // pair is used on all versions after DC NTE. @@ -425,13 +197,11 @@ public: // 0F = LCK penalty // 10 = all of the above penalties except HP // Anything else (including 00) = no bonus or penalty - parray stats = 0; - parray, 2> amounts; + uint8_t stat1 = 0; + uint16_t amount1 = 0; + uint8_t stat2 = 0; + uint16_t amount2 = 0; } __attribute__((packed)); - using StatBoost = StatBoostT; - using StatBoostBE = StatBoostT; - check_struct_size(StatBoost, 6); - check_struct_size(StatBoostBE, 6); // Indexed as [tech_num][char_class] using MaxTechniqueLevels = parray, 19>; @@ -447,19 +217,12 @@ public: parray unused; } __packed_ws__(ItemCombination, 0x10); - template - struct TechniqueBoostEntryT { + struct TechniqueBoost { uint8_t tech_num = 0; - // It appears that only one bit in the flags field is used: - // 01 = enable piercing (for Megid) + // It appears that only one bit in the flags field is used: 01 = enable piercing (for Megid) uint8_t flags = 0; - parray unused; - F32T amount = 0.0f; - } __attribute__((packed)); - using TechniqueBoostEntry = TechniqueBoostEntryT; - using TechniqueBoostEntryBE = TechniqueBoostEntryT; - check_struct_size(TechniqueBoostEntry, 0x08); - check_struct_size(TechniqueBoostEntryBE, 0x08); + float amount = 0.0f; + }; struct EventItem { parray item; @@ -471,283 +234,124 @@ public: uint8_t unused = 0; } __packed_ws__(UnsealableItem, 4); - template - struct NonWeaponSaleDivisorsT { - F32T armor_divisor = 0.0f; - F32T shield_divisor = 0.0f; - F32T unit_divisor = 0.0f; - F32T mag_divisor = 0.0f; - } __attribute__((packed)); - using NonWeaponSaleDivisors = NonWeaponSaleDivisorsT; - using NonWeaponSaleDivisorsBE = NonWeaponSaleDivisorsT; - check_struct_size(NonWeaponSaleDivisors, 0x10); - check_struct_size(NonWeaponSaleDivisorsBE, 0x10); + struct NonWeaponSaleDivisors { + float armor_divisor = 0.0f; + float shield_divisor = 0.0f; + float unit_divisor = 0.0f; + float mag_divisor = 0.0f; + }; - ItemParameterTable(std::shared_ptr data, Version version); - ~ItemParameterTable() = default; + struct ShieldEffect { + uint32_t sound_id; + uint32_t unknown_a1; + }; + + struct PhotonColorEntry { + uint32_t unknown_a1; + VectorXYZTF unknown_a2; + VectorXYZTF unknown_a3; + }; + + struct UnknownA1 { + uint16_t unknown_a1; + uint16_t unknown_a2; + }; + + struct UnknownA5 { + uint32_t target_param; // For players, char_class; for enemies, rt_index; for objects, 0x30 + uint32_t unknown_a2; + uint32_t unknown_a3; + }; + + struct WeaponEffect { + uint32_t sound_id1; + uint32_t eff_value1; + uint32_t sound_id2; + uint32_t eff_value2; + parray unknown_a5; + }; + + struct WeaponRange { + float unknown_a1; + float unknown_a2; + uint32_t unknown_a3; // Angle + uint32_t unknown_a4; // Angle + uint32_t unknown_a5; + }; + + struct RangedSpecial { + uint8_t data1_1; + uint8_t data1_2; + uint8_t weapon_range_index; + uint8_t unknown_a1; + } __packed_ws__(RangedSpecial, 4); + + ItemParameterTable() = delete; + virtual ~ItemParameterTable() = default; + + static std::shared_ptr create(std::shared_ptr data, Version version); std::set compute_all_valid_primary_identifiers() const; - size_t num_weapons_in_class(uint8_t data1_1) const; - const WeaponV4& get_weapon(uint8_t data1_1, uint8_t data1_2) const; - size_t num_armors_or_shields_in_class(uint8_t data1_1) const; - const ArmorOrShieldV4& get_armor_or_shield(uint8_t data1_1, uint8_t data1_2) const; - size_t num_units() const; - const UnitV4& get_unit(uint8_t data1_2) const; - size_t num_mags() const; - const MagV4& get_mag(uint8_t data1_1) const; - size_t num_tools_in_class(uint8_t data1_1) const; - const ToolV4& get_tool(uint8_t data1_1, uint8_t data1_2) const; - std::pair find_tool_by_id(uint32_t id) const; + virtual size_t num_weapon_classes() const = 0; + virtual size_t num_weapons_in_class(uint8_t data1_1) const = 0; + virtual const Weapon& get_weapon(uint8_t data1_1, uint8_t data1_2) const = 0; + virtual size_t num_armors_or_shields_in_class(uint8_t data1_1) const = 0; + virtual const ArmorOrShield& get_armor_or_shield(uint8_t data1_1, uint8_t data1_2) const = 0; + virtual size_t num_units() const = 0; + virtual const Unit& get_unit(uint8_t data1_2) const = 0; + virtual size_t num_mags() const = 0; + virtual const Mag& get_mag(uint8_t data1_1) const = 0; + virtual size_t num_tool_classes() const = 0; + virtual size_t num_tools_in_class(uint8_t data1_1) const = 0; + virtual const Tool& get_tool(uint8_t data1_1, uint8_t data1_2) const = 0; + virtual std::pair find_tool_by_id(uint32_t id) const = 0; - std::variant + std::variant definition_for_primary_identifier(uint32_t primary_identifier) const; - float get_sale_divisor(uint8_t data1_0, uint8_t data1_1) const; - const MagFeedResult& get_mag_feed_result(uint8_t table_index, uint8_t which) const; - uint8_t get_item_stars(uint32_t id) const; - uint8_t get_special_stars(uint8_t special) const; - const Special& get_special(uint8_t special) const; - const StatBoost& get_stat_boost(uint8_t entry_index) const; - uint8_t get_max_tech_level(uint8_t char_class, uint8_t tech_num) const; - uint8_t get_weapon_v1_replacement(uint8_t data1_1) const; + virtual float get_sale_divisor(uint8_t data1_0, uint8_t data1_1) const = 0; + virtual const MagFeedResult& get_mag_feed_result(uint8_t table_index, uint8_t which) const = 0; + virtual uint8_t get_item_stars(uint32_t id) const = 0; + virtual uint8_t get_special_stars(uint8_t special) const = 0; + virtual size_t num_specials() const = 0; + virtual const Special& get_special(uint8_t special) const = 0; + virtual const StatBoost& get_stat_boost(uint8_t entry_index) const = 0; + virtual uint8_t get_max_tech_level(uint8_t char_class, uint8_t tech_num) const = 0; + virtual uint8_t get_weapon_class(uint8_t data1_1) const = 0; uint32_t get_item_id(const ItemData& item) const; uint32_t get_item_team_points(const ItemData& item) const; uint8_t get_item_base_stars(const ItemData& item) const; uint8_t get_item_adjusted_stars(const ItemData& item, bool ignore_unidentified = false) const; bool is_item_rare(const ItemData& item) const; - bool is_unsealable_item(uint8_t data1_0, uint8_t data1_1, uint8_t data1_2) const; - bool is_unsealable_item(const ItemData& param_1) const; + virtual bool is_unsealable_item(uint8_t data1_0, uint8_t data1_1, uint8_t data1_2) const = 0; + bool is_unsealable_item(const ItemData& item) const; const ItemCombination& get_item_combination(const ItemData& used_item, const ItemData& equipped_item) const; const std::vector& get_all_combinations_for_used_item(const ItemData& used_item) const; - const std::map>& get_all_item_combinations() const; - size_t num_events() const; - std::pair get_event_items(uint8_t event_number) const; + virtual const std::map>& get_all_item_combinations() const = 0; + virtual size_t num_events() const = 0; + virtual std::pair get_event_items(uint8_t event_number) const = 0; size_t price_for_item(const ItemData& item) const; - size_t num_weapon_classes; - size_t num_tool_classes; - size_t item_stars_first_id; - size_t item_stars_last_id; - size_t special_stars_begin_index; - size_t num_specials; - size_t first_rare_mag_index; - protected: - struct TableOffsetsDCProtos { - /* ## / NTE / 11/2000 */ - /* 00 / 0013 / 0013 */ le_uint32_t unknown_a0; - /* 04 / 2E1C / 2EB8 */ le_uint32_t weapon_table; - /* 08 / 2D94 / 2E28 */ le_uint32_t armor_table; - /* 0C / 2DA4 / 2E38 */ le_uint32_t unit_table; - /* 10 / 2DB4 / 2E48 */ le_uint32_t tool_table; - /* 14 / 2DAC / 2E40 */ le_uint32_t mag_table; - /* 18 / 1F98 / 202C */ le_uint32_t v1_replacement_table; - /* 1C / 1994 / 1A28 */ le_uint32_t photon_color_table; - /* 20 / 1C64 / 1CF8 */ le_uint32_t weapon_range_table; - /* 24 / 1FBF / 2053 */ le_uint32_t weapon_sale_divisor_table; - /* 28 / 1FE6 / 207A */ le_uint32_t sale_divisor_table; - /* 2C / 2F54 / 2FF0 */ le_uint32_t mag_feed_table; - /* 30 / 22A9 / 233D */ le_uint32_t star_value_table; - /* 34 / 23EE / 2484 */ le_uint32_t unknown_a1; - /* 38 / 275E / 27F4 */ le_uint32_t special_data_table; - /* 3C / 2804 / 2898 */ le_uint32_t stat_boost_table; - /* 40 / 1908 / 199C */ le_uint32_t shield_effect_table; - /* 44 / 0668 / 0668 */ le_uint32_t unknown_a2; - /* 48 / 030C / 030C */ le_uint32_t unknown_a3; - /* 4C / 2CE4 / 2D78 */ le_uint32_t unknown_a4; - } __packed_ws__(TableOffsetsDCProtos, 0x50); - - struct TableOffsetsV1V2 { - // TODO: Is weapon count 0x89 or 0x8A? It could be that the last entry in weapon_table is used for ???? items. - /* ## / V1 / V2*/ - /* 00 / 0013 / 0013 */ le_uint32_t unknown_a0; - /* 04 / 32E8 / 5AFC */ le_uint32_t weapon_table; // -> [{count, offset -> [WeaponV2]}](0x89) - /* 08 / 3258 / 5A5C */ le_uint32_t armor_table; // -> [{count, offset -> [ArmorOrShieldV2]}](2; armors and shields) - /* 0C / 3268 / 5A6C */ le_uint32_t unit_table; // -> {count, offset -> [UnitV2]} (last if out of range) - /* 10 / 3278 / 5A7C */ le_uint32_t tool_table; // -> [{count, offset -> [ToolV2]}](0x10) (last if out of range) - /* 14 / 3270 / 5A74 */ le_uint32_t mag_table; // -> {count, offset -> [MagV2]} - /* 18 / 23C8 / 3DF8 */ le_uint32_t v1_replacement_table; // -> [uint8_t](0x89) - /* 1C / 1DB0 / 2E4C */ le_uint32_t photon_color_table; // -> [0x24-byte structs](0x20) - /* 20 / 2080 / 32CC */ le_uint32_t weapon_range_table; // -> ??? - /* 24 / 23F0 / 3E84 */ le_uint32_t weapon_sale_divisor_table; // -> [float](0x89) - /* 28 / 248C / 40A8 */ le_uint32_t sale_divisor_table; // -> NonWeaponSaleDivisors - /* 2C / 3420 / 5F4C */ le_uint32_t mag_feed_table; // -> MagFeedResultsTable - /* 30 / 275C / 4378 */ le_uint32_t star_value_table; // -> [uint8_t](0x1C7) - /* 34 / 28A2 / 45E4 */ le_uint32_t unknown_a1; - /* 38 / 2C12 / 4540 */ le_uint32_t special_data_table; // -> [Special](0x29) - /* 3C / 2CB8 / 58DC */ le_uint32_t stat_boost_table; // -> [StatBoost] - /* 40 / 3198 / 5704 */ le_uint32_t shield_effect_table; // -> [8-byte structs] - } __packed_ws__(TableOffsetsV1V2, 0x44); - - struct TableOffsetsGCNTE { - /* 00 / 6F0C */ be_uint32_t weapon_table; // -> [{count, offset -> [WeaponV3/WeaponV4]}](0xED) - /* 04 / 6E4C */ be_uint32_t armor_table; // -> [{count, offset -> [ArmorOrShieldV3/ArmorOrShieldV4]}](2; armors and shields) - /* 08 / 6E5C */ be_uint32_t unit_table; // -> {count, offset -> [UnitV3/UnitV4]} (last if out of range) - /* 0C / 6E6C */ be_uint32_t tool_table; // -> [{count, offset -> [ToolV3/ToolV4]}](0x1A) (last if out of range) - /* 10 / 6E64 */ be_uint32_t mag_table; // -> {count, offset -> [MagV3/MagV4]} - /* 14 / 47BC */ be_uint32_t v1_replacement_table; // -> [uint8_t](0xED) - /* 18 / 37A4 */ be_uint32_t photon_color_table; // -> [0x24-byte structs](0x20) - /* 1C / 3A74 */ be_uint32_t weapon_range_table; // -> ??? - /* 20 / 484C */ be_uint32_t weapon_sale_divisor_table; // -> [float](0xED) - /* 24 / 4A80 */ be_uint32_t sale_divisor_table; // -> NonWeaponSaleDivisors - /* 28 / 7384 */ be_uint32_t mag_feed_table; // -> MagFeedResultsTable - /* 2C / 4D50 */ be_uint32_t star_value_table; // -> [uint8_t](0x330) (indexed by .id from weapon, armor, etc.) - /* 30 / 4F72 */ be_uint32_t special_data_table; // -> [Special] - /* 34 / 5018 */ be_uint32_t weapon_effect_table; // -> [16-byte structs] - /* 38 / 68B8 */ be_uint32_t stat_boost_table; // -> [StatBoost] - /* 3C / 61B8 */ be_uint32_t shield_effect_table; // -> [8-byte structs] - /* 40 / 69D8 */ be_uint32_t max_tech_level_table; // -> MaxTechniqueLevels - /* 44 / 737C */ be_uint32_t combination_table; // -> {count, offset -> [ItemCombination]} - /* 48 / 68B0 */ be_uint32_t unknown_a1; - /* 4C / 6B1C */ be_uint32_t tech_boost_table; // -> [TechniqueBoostEntry[3]] - } __packed_ws__(TableOffsetsGCNTE, 0x50); - - template - struct TableOffsetsV3V4T { - /* ## / GC / BB */ - /* 00 / F078 / 14884 */ U32T weapon_table; // -> [{count, offset -> [WeaponV3/WeaponV4]}](0xED) - /* 04 / EF90 / 1478C */ U32T armor_table; // -> [{count, offset -> [ArmorOrShieldV3/ArmorOrShieldV4]}](2; armors and shields) - /* 08 / EFA0 / 1479C */ U32T unit_table; // -> {count, offset -> [UnitV3/UnitV4]} (last if out of range) - /* 0C / EFB0 / 147AC */ U32T tool_table; // -> [{count, offset -> [ToolV3/ToolV4]}](0x1A) (last if out of range) - /* 10 / EFA8 / 147A4 */ U32T mag_table; // -> {count, offset -> [MagV3/MagV4]} - /* 14 / B88C / 0F4B8 */ U32T v1_replacement_table; // -> [uint8_t](0xED) - /* 18 / A7FC / 0DE7C */ U32T photon_color_table; // -> [0x24-byte structs](0x20) - /* 1C / AACC / 0E194 */ U32T weapon_range_table; // -> ??? - /* 20 / B938 / 0F5A8 */ U32T weapon_sale_divisor_table; // -> [float](0xED) - /* 24 / BBCC / 0F83C */ U32T sale_divisor_table; // -> NonWeaponSaleDivisors - /* 28 / F608 / 1502C */ U32T mag_feed_table; // -> MagFeedResultsTable - /* 2C / BE9C / 0FB0C */ U32T star_value_table; // -> [uint8_t](0x330) (indexed by .id from weapon, armor, etc.) - /* 30 / C100 / 0FE3C */ U32T special_data_table; // -> [Special] - /* 34 / C1A4 / 0FEE0 */ U32T weapon_effect_table; // -> [16-byte structs] - /* 38 / DE50 / 1275C */ U32T stat_boost_table; // -> [StatBoost] - /* 3C / D6E4 / 11C80 */ U32T shield_effect_table; // -> [8-byte structs] - /* 40 / DF88 / 12894 */ U32T max_tech_level_table; // -> MaxTechniqueLevels - /* 44 / F5D0 / 14FF4 */ U32T combination_table; // -> {count, offset -> [ItemCombination]} - /* 48 / DE48 / 12754 */ U32T unknown_a1; - /* 4C / EB8C / 14278 */ U32T tech_boost_table; // -> [TechniqueBoost[3]] - /* 50 / F5F0 / 15014 */ U32T unwrap_table; // -> {count, offset -> [{count, offset -> [EventItem]}]} - /* 54 / F5F8 / 1501C */ U32T unsealable_table; // -> {count, offset -> [UnsealableItem]} - /* 58 / F600 / 15024 */ U32T ranged_special_table; // -> {count, offset -> [4-byte structs]} - } __attribute__((packed)); - using TableOffsetsV3V4 = TableOffsetsV3V4T; - using TableOffsetsV3V4BE = TableOffsetsV3V4T; - check_struct_size(TableOffsetsV3V4, 0x5C); - check_struct_size(TableOffsetsV3V4BE, 0x5C); - - Version version; std::shared_ptr data; phosg::StringReader r; - const TableOffsetsDCProtos* offsets_dc_protos; - const TableOffsetsV1V2* offsets_v1_v2; - const TableOffsetsGCNTE* offsets_gc_nte; - const TableOffsetsV3V4* offsets_v3_le; - const TableOffsetsV3V4BE* offsets_v3_be; - const TableOffsetsV3V4* offsets_v4; - // These are unused if offsets_v4 is not null (in that case, we just return references to within the data string) - mutable std::unordered_map parsed_weapons; - mutable std::vector parsed_armors; - mutable std::vector parsed_shields; - mutable std::vector parsed_units; - mutable std::vector parsed_mags; - mutable std::unordered_map parsed_tools; - mutable std::vector parsed_specials; - mutable std::vector parsed_stat_boosts; + mutable std::unordered_map weapons; + mutable std::vector armors; + mutable std::vector shields; + mutable std::vector units; + mutable std::vector mags; + mutable std::unordered_map tools; + + mutable std::vector specials; + mutable std::vector stat_boosts; // Key is used_item. We can't index on (used_item, equipped_item) because equipped_item may contain wildcards, and // the matching order matters. mutable std::map> item_combination_index; - template - std::pair find_tool_by_id_t(uint32_t tool_table_offset, uint32_t id) const; - template - float get_sale_divisor_t(const OffsetsT* offsets, uint8_t data1_0, uint8_t data1_1) const; - template - size_t num_events_t(uint32_t base_offset) const; - template - std::pair get_event_items_t(uint32_t base_offset, uint8_t event_number) const; -}; - -class MagEvolutionTable { -public: - // TODO: V1 format is different! Offsets are 0438 0440 0498 0520 054C - struct MotionReference { - struct Side { - // This specifies which entry in ItemMagMotion.dat is used. The file is just a list of 0x64-byte structures. - // 0xFF = no TItemMagSub is created - uint8_t motion_table_entry = 0xFF; - parray unknown_a1 = 0; - } __packed_ws__(Side, 0x06); - parray sides; // [0] = right side, [1] = left side - } __packed_ws__(MotionReference, 0x0C); - - struct MotionReferenceTables { - // It seems that there are two definition tables, but only the first is used on any version of PSO. On v3 and - // later, the two offsets point to the same table, but on v2 they don't and the second table contains different - // data. TODO: Figure out what the deal is with the different v2 tables. - le_uint32_t ref_table; // -> MotionReference[num_mags] - le_uint32_t unused_ref_table; // -> MotionReference[num_mags] - } __packed_ws__(MotionReferenceTables, 0x08); - - struct ColorEntry { - // Colors are specified as 4 floats, each in the range [0, 1], for each color channel. The default colors are: - // alpha red green blue color (see StaticGameData.cc) - // 1.0 1.0 0.2 0.1 red - // 1.0 0.2 0.2 1.0 blue - // 1.0 1.0 0.9 0.1 yellow - // 1.0 0.1 1.0 0.1 green - // 1.0 0.8 0.1 1.0 purple - // 1.0 0.1 0.1 0.2 black - // 1.0 0.9 1.0 1.0 white - // 1.0 0.1 0.9 1.0 cyan - // 1.0 0.5 0.3 0.2 brown - // 1.0 1.0 0.4 0.0 orange (v3+) - // 1.0 0.502 0.545 0.977 light-blue (v3+) - // 1.0 0.502 0.502 0.0 olive (v3+) - // 1.0 0.0 0.941 0.714 turquoise (v3+) - // 1.0 0.8 0.098 0.392 fuchsia (v3+) - // 1.0 0.498 0.498 0.498 grey (v3+) - // 1.0 0.996 0.996 0.832 cream (v3+) - // 1.0 0.996 0.498 0.784 pink (v3+) - // 1.0 0.0 0.498 0.322 dark-green (v3+) - le_float alpha; - le_float red; - le_float green; - le_float blue; - } __packed_ws__(ColorEntry, 0x10); - - struct UnknownA3Entry { - uint8_t flags; - uint8_t unknown_a2; - le_uint16_t unknown_a3; - le_uint16_t unknown_a4; - le_uint16_t unknown_a5; - } __packed_ws__(UnknownA3Entry, 0x08); - - struct TableOffsets { - // num_mags = 0x3A in v2 and GC NTE, 0x43 in V3, 0x53 in BB - // num_colors = 0x09 in v2 and GC NTE, 0x12 in V3/BB - // TODO: GC NTE uses the v2 format but is big-endian - /* -- / V2 / V3 / BB */ - /* 00 / 05BC / 0340 / 0400 */ le_uint32_t motion_tables; // -> MotionReferenceTables - /* 04 / 0594 / 0348 / 0408 */ le_uint32_t unknown_a2; // -> (uint8_t[2])[num_mags] (references into unknown_a3) - /* 08 / 0608 / 03CE / 04AE */ le_uint32_t unknown_a3; // -> UnknownA3Entry[max(unknown_a2) + 1] - /* 0C / 06B0 / 0476 / 0556 */ le_uint32_t unknown_a4; // -> (uint8_t)[num_mags] - /* 10 / 06EC / 04BC / 05AC */ le_uint32_t color_table; // -> ColorEntry[num_colors] - /* 14 / 077C / 05DC / 06CC */ le_uint32_t evolution_number; // -> uint8_t[num_mags] - } __packed_ws__(TableOffsets, 0x18); - - MagEvolutionTable(std::shared_ptr data, size_t num_mags); - ~MagEvolutionTable() = default; - - uint8_t get_evolution_number(uint8_t data1_1) const; - -protected: - std::shared_ptr data; - size_t num_mags; - phosg::StringReader r; - const TableOffsets* offsets; + explicit ItemParameterTable(std::shared_ptr data); }; diff --git a/src/Items.cc b/src/Items.cc index dc654f8e..5dc4e1a8 100644 --- a/src/Items.cc +++ b/src/Items.cc @@ -133,7 +133,8 @@ void player_use_item(shared_ptr c, size_t item_index, shared_ptrinventory.items[player->inventory.find_equipped_item(EquipSlot::MAG)]; - if (s->mag_evolution_table(c->version())->get_evolution_number(mag.data.data1[1]) < 4) { + uint8_t evolution_number = s->mag_evolution_table(c->version())->get_evolution_number(mag.data.data1[1]); + if (evolution_number < 4) { switch (item.data.data1[2]) { case 0x00: // Cell of MAG 502 mag.data.data1[1] = (player->disp.visual.section_id & 1) ? 0x1D : 0x21; diff --git a/src/LevelTable.hh b/src/LevelTable.hh index 07f4f4de..c43339d8 100644 --- a/src/LevelTable.hh +++ b/src/LevelTable.hh @@ -33,11 +33,9 @@ struct CharacterStatsT { ret.lck = this->lck; return ret; } -} __attribute__((packed)); +} __packed_ws_be__(CharacterStatsT, 0x0E); using CharacterStats = CharacterStatsT; using CharacterStatsBE = CharacterStatsT; -check_struct_size(CharacterStats, 0x0E); -check_struct_size(CharacterStatsBE, 0x0E); template struct PlayerStatsT { @@ -61,11 +59,9 @@ struct PlayerStatsT { ret.meseta = this->meseta; return ret; } -} __attribute__((packed)); +} __packed_ws_be__(PlayerStatsT, 0x24); using PlayerStats = PlayerStatsT; using PlayerStatsBE = PlayerStatsT; -check_struct_size(PlayerStats, 0x24); -check_struct_size(PlayerStatsBE, 0x24); template struct LevelStatsDeltaT { @@ -89,11 +85,9 @@ struct LevelStatsDeltaT { ps.mst += this->mst; ps.lck += this->lck; } -} __attribute__((packed)); +} __packed_ws_be__(LevelStatsDeltaT, 0x0C); using LevelStatsDelta = LevelStatsDeltaT; using LevelStatsDeltaBE = LevelStatsDeltaT; -check_struct_size(LevelStatsDelta, 0x0C); -check_struct_size(LevelStatsDeltaBE, 0x0C); class LevelTable { // This is the base class for all the LevelTable implementations. The public interface here only defines functions diff --git a/src/Lobby.cc b/src/Lobby.cc index d6aa8be4..ee492356 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -840,3 +840,35 @@ bool Lobby::compare_shared(const shared_ptr& a, const shared_ptrname < b->name; } + +template <> +const char* phosg::name_for_enum(Lobby::JoinError value) { + switch (value) { + case Lobby::JoinError::ALLOWED: + return "ALLOWED"; + case Lobby::JoinError::FULL: + return "FULL"; + case Lobby::JoinError::VERSION_CONFLICT: + return "VERSION_CONFLICT"; + case Lobby::JoinError::QUEST_SELECTION_IN_PROGRESS: + return "QUEST_SELECTION_IN_PROGRESS"; + case Lobby::JoinError::QUEST_IN_PROGRESS: + return "QUEST_IN_PROGRESS"; + case Lobby::JoinError::BATTLE_IN_PROGRESS: + return "BATTLE_IN_PROGRESS"; + case Lobby::JoinError::LOADING: + return "LOADING"; + case Lobby::JoinError::SOLO: + return "SOLO"; + case Lobby::JoinError::INCORRECT_PASSWORD: + return "INCORRECT_PASSWORD"; + case Lobby::JoinError::LEVEL_TOO_LOW: + return "LEVEL_TOO_LOW"; + case Lobby::JoinError::LEVEL_TOO_HIGH: + return "LEVEL_TOO_HIGH"; + case Lobby::JoinError::NO_ACCESS_TO_QUEST: + return "NO_ACCESS_TO_QUEST"; + default: + throw runtime_error("invalid drop mode"); + } +} diff --git a/src/Lobby.hh b/src/Lobby.hh index e29253be..77ba1e26 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -281,6 +281,4 @@ struct Lobby : public std::enable_shared_from_this { }; template <> -ServerDropMode phosg::enum_for_name(const char* name); -template <> -const char* phosg::name_for_enum(ServerDropMode value); +const char* phosg::name_for_enum(Lobby::JoinError value); diff --git a/src/MagEvolutionTable.cc b/src/MagEvolutionTable.cc new file mode 100644 index 00000000..3071eae8 --- /dev/null +++ b/src/MagEvolutionTable.cc @@ -0,0 +1,170 @@ +#include "MagEvolutionTable.hh" + +#include "CommonFileFormats.hh" + +using namespace std; + +struct MotionReference { + struct Side { + // This specifies which entry in ItemMagMotion.dat is used. The file is just a list of 0x64-byte structures. + // 0xFF = no TItemMagSub is created + uint8_t motion_table_entry = 0xFF; + parray unknown_a1 = 0; + } __packed_ws__(Side, 0x06); + parray sides; // [0] = right side, [1] = left side +} __packed_ws__(MotionReference, 0x0C); + +template +struct MotionReferenceTables { + // It seems that there are two definition tables, but only the first is used on any version of PSO. On v3 and later, + // the two offsets point to the same table, but on v2 they don't and the second table contains different data. + // TODO: Figure out what the deal is with the different v2 tables. + U32T ref_table; // -> MotionReference[num_mags] + U32T unused_ref_table; // -> MotionReference[num_mags] +} __packed_ws_be__(MotionReferenceTables, 0x08); + +template +struct ColorEntry { + // Colors are specified as 4 floats, each in the range [0, 1], for each color channel. The default colors are: + // alpha red green blue color (see StaticGameData.cc) + // 1.0 1.0 0.2 0.1 red + // 1.0 0.2 0.2 1.0 blue + // 1.0 1.0 0.9 0.1 yellow + // 1.0 0.1 1.0 0.1 green + // 1.0 0.8 0.1 1.0 purple + // 1.0 0.1 0.1 0.2 black + // 1.0 0.9 1.0 1.0 white + // 1.0 0.1 0.9 1.0 cyan + // 1.0 0.5 0.3 0.2 brown + // 1.0 1.0 0.4 0.0 orange (v3+) + // 1.0 0.502 0.545 0.977 light-blue (v3+) + // 1.0 0.502 0.502 0.0 olive (v3+) + // 1.0 0.0 0.941 0.714 turquoise (v3+) + // 1.0 0.8 0.098 0.392 fuchsia (v3+) + // 1.0 0.498 0.498 0.498 grey (v3+) + // 1.0 0.996 0.996 0.832 cream (v3+) + // 1.0 0.996 0.498 0.784 pink (v3+) + // 1.0 0.0 0.498 0.322 dark-green (v3+) + F32T alpha; + F32T red; + F32T green; + F32T blue; +} __packed_ws_be__(ColorEntry, 0x10); + +template +struct UnknownA3Entry { + uint8_t flags; + uint8_t unknown_a2; + U16T unknown_a3; + U16T unknown_a4; + U16T unknown_a5; +} __packed_ws_be__(UnknownA3Entry, 0x08); + +template +struct RootV2V3V4 { + /* -- / 112K / V1 / V2 / V3 / BB */ + /* 00 / 0438 / 0438 / 05BC / 0340 / 0400 */ U32T motion_tables; // -> MotionReferenceTables + /* 04 / 0440 / 0440 / 0594 / 0348 / 0408 */ U32T unknown_a2; // -> (uint8_t[2])[NumMags] (references into unknown_a3) + /* 08 / 0498 / 0498 / 0608 / 03CE / 04AE */ U32T unknown_a3; // -> UnknownA3Entry[max(unknown_a2) + 1] + /* 0C / 0510 / 0520 / 06B0 / 0476 / 0556 */ U32T unknown_a4; // -> uint8_t[NumMags] + /* 10 / 053C / 054C / 06EC / 04BC / 05AC */ U32T color_table; // -> ColorEntry[NumColors] + /* 14 / / / 077C / 05DC / 06CC */ U32T evolution_number; // -> uint8_t[NumMags] +} __packed_ws_be__(RootV2V3V4, 0x18); + +struct RootV1 { + le_uint32_t motion_tables; + le_uint32_t unknown_a2; + le_uint32_t unknown_a3; + le_uint32_t unknown_a4; + le_uint32_t color_table; +} __packed_ws__(RootV1, 0x14); + +static uint8_t get_v1_mag_evolution_number(uint8_t data1_1) { + static const std::array v1_evolution_number_table{ + /* 00 */ 0, 1, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 2, + /* 10 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 2, 3, 4, 3, 3, + /* 20 */ 3, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 4}; + if (data1_1 >= v1_evolution_number_table.size()) { + throw runtime_error("invalid mag number"); + } + return v1_evolution_number_table[data1_1]; +} + +template +class MagEvolutionTableT : public MagEvolutionTable { +public: + explicit MagEvolutionTableT(std::shared_ptr data) + : data(data), r(*data), root(&r.pget(this->r.pget_u32l(this->data->size() - 0x10))) {} + virtual ~MagEvolutionTableT() = default; + + virtual VectorXYZTF get_color_rgba(size_t index) const { + if (index >= NumColors) { + throw runtime_error("invalid mag color index"); + } + const auto& color = this->r.pget>(this->root->color_table + sizeof(ColorEntry) * index); + return {color.red.load(), color.green.load(), color.blue.load(), color.alpha.load()}; + } + + virtual uint8_t get_evolution_number(uint8_t data1_1) const { + if constexpr (requires { this->root->evolution_number_table; }) { + return this->r.pget_u8(this->root->evolution_number_table + data1_1); + } else { + return get_v1_mag_evolution_number(data1_1); + } + } + +protected: + std::shared_ptr data; + phosg::StringReader r; + const RootT* root; +}; + +class MagEvolutionTableDCNTE : public MagEvolutionTable { +public: + MagEvolutionTableDCNTE() = default; + virtual ~MagEvolutionTableDCNTE() = default; + + virtual VectorXYZTF get_color_rgba(size_t) const { + throw runtime_error("mag colors not available on DC NTE"); + } + + virtual uint8_t get_evolution_number(uint8_t data1_1) const { + return get_v1_mag_evolution_number(data1_1); + } +}; + +using MagEvolutionTableDC112000 = MagEvolutionTableT, 0x28, 0x09, false>; +using MagEvolutionTableV1 = MagEvolutionTableT, 0x28, 0x09, false>; +using MagEvolutionTableV2 = MagEvolutionTableT, 0x3A, 0x09, false>; +using MagEvolutionTableGCNTE = MagEvolutionTableT, 0x3A, 0x09, true>; +using MagEvolutionTableGC = MagEvolutionTableT, 0x43, 0x12, true>; +using MagEvolutionTableXB = MagEvolutionTableT, 0x43, 0x12, false>; +using MagEvolutionTableV4 = MagEvolutionTableT, 0x53, 0x12, false>; + +std::shared_ptr MagEvolutionTable::create( + std::shared_ptr data, Version version) { + switch (version) { + case Version::DC_NTE: + return std::make_shared(); + case Version::DC_11_2000: + return std::make_shared(data); + case Version::DC_V1: + return std::make_shared(data); + case Version::DC_V2: + case Version::PC_NTE: + case Version::PC_V2: + return std::make_shared(data); + case Version::GC_NTE: + return std::make_shared(data); + case Version::GC_V3: + case Version::GC_EP3: + case Version::GC_EP3_NTE: + return std::make_shared(data); + case Version::XB_V3: + return std::make_shared(data); + case Version::BB_V4: + return std::make_shared(data); + default: + throw std::logic_error("Cannot create mag evolution table for this version"); + } +} diff --git a/src/MagEvolutionTable.hh b/src/MagEvolutionTable.hh new file mode 100644 index 00000000..e908278b --- /dev/null +++ b/src/MagEvolutionTable.hh @@ -0,0 +1,26 @@ +#pragma once + +#include "WindowsPlatform.hh" + +#include + +#include +#include + +#include "CommonFileFormats.hh" +#include "Text.hh" +#include "Types.hh" +#include "Version.hh" + +class MagEvolutionTable { +public: + virtual ~MagEvolutionTable() = default; + + static std::shared_ptr create(std::shared_ptr data, Version version); + + virtual VectorXYZTF get_color_rgba(size_t index) const = 0; + virtual uint8_t get_evolution_number(uint8_t data1_1) const = 0; + +protected: + MagEvolutionTable() = default; +}; diff --git a/src/PSOEncryption.hh b/src/PSOEncryption.hh index 05b9a939..99825b5d 100644 --- a/src/PSOEncryption.hh +++ b/src/PSOEncryption.hh @@ -343,11 +343,9 @@ public: ret.store_raw(this->value); return ret; } -} __attribute__((packed)); +} __packed_ws_be__(ChallengeTimeT, 4); using ChallengeTime = ChallengeTimeT; using ChallengeTimeBE = ChallengeTimeT; -check_struct_size(ChallengeTime, 4); -check_struct_size(ChallengeTimeBE, 4); std::string decrypt_v2_registry_value(const void* data, size_t size); diff --git a/src/PlayerInventory.hh b/src/PlayerInventory.hh index af34b4d8..295ec033 100644 --- a/src/PlayerInventory.hh +++ b/src/PlayerInventory.hh @@ -76,11 +76,9 @@ struct PlayerInventoryItemT { bool is_equipped() const { return (this->flags & 8); } -} __attribute__((packed)); +} __packed_ws_be__(PlayerInventoryItemT, 0x1C); using PlayerInventoryItem = PlayerInventoryItemT; using PlayerInventoryItemBE = PlayerInventoryItemT; -check_struct_size(PlayerInventoryItem, 0x1C); -check_struct_size(PlayerInventoryItemBE, 0x1C); template struct PlayerBankItemT { @@ -100,11 +98,9 @@ struct PlayerBankItemT { ret.present = this->present; return ret; } -} __attribute__((packed)); +} __packed_ws_be__(PlayerBankItemT, 0x18); using PlayerBankItem = PlayerBankItemT; using PlayerBankItemBE = PlayerBankItemT; -check_struct_size(PlayerBankItem, 0x18); -check_struct_size(PlayerBankItemBE, 0x18); template struct PlayerInventoryT { @@ -291,11 +287,9 @@ struct PlayerInventoryT { ret.items = this->items; return ret; } -} __attribute__((packed)); +} __packed_ws_be__(PlayerInventoryT, 0x34C); using PlayerInventory = PlayerInventoryT; using PlayerInventoryBE = PlayerInventoryT; -check_struct_size(PlayerInventory, 0x34C); -check_struct_size(PlayerInventoryBE, 0x34C); template struct PlayerBankT { diff --git a/src/PlayerSubordinates.hh b/src/PlayerSubordinates.hh index e0db3466..7da0a487 100644 --- a/src/PlayerSubordinates.hh +++ b/src/PlayerSubordinates.hh @@ -312,11 +312,9 @@ struct PlayerVisualConfigT { ret.proportion_y = this->proportion_y; return ret; } -} __attribute__((packed)); +} __packed_ws_be__(PlayerVisualConfigT, 0x50); using PlayerVisualConfig = PlayerVisualConfigT; using PlayerVisualConfigBE = PlayerVisualConfigT; -check_struct_size(PlayerVisualConfig, 0x50); -check_struct_size(PlayerVisualConfigBE, 0x50); template struct PlayerDispDataDCPCV3T { @@ -331,11 +329,9 @@ struct PlayerDispDataDCPCV3T { } PlayerDispDataBB to_bb(Language to_language, Language from_language) const; -} __attribute__((packed)); +} __packed_ws_be__(PlayerDispDataDCPCV3T, 0xD0); using PlayerDispDataDCPCV3 = PlayerDispDataDCPCV3T; using PlayerDispDataDCPCV3BE = PlayerDispDataDCPCV3T; -check_struct_size(PlayerDispDataDCPCV3, 0xD0); -check_struct_size(PlayerDispDataDCPCV3BE, 0xD0); struct PlayerDispDataBBPreview { /* 00 */ le_uint32_t experience = 0; @@ -598,11 +594,9 @@ struct ChallengeAwardStateT { ret.maximum_rank = this->maximum_rank; return ret; } -} __attribute__((packed)); +} __packed_ws_be__(ChallengeAwardStateT, 8); using ChallengeAwardState = ChallengeAwardStateT; using ChallengeAwardStateBE = ChallengeAwardStateT; -check_struct_size(ChallengeAwardState, 8); -check_struct_size(ChallengeAwardStateBE, 8); template struct PlayerRecordsChallengeDCPCT { @@ -673,11 +667,9 @@ struct PlayerRecordsChallengeV3T { /* 00D8:00F4 */ pstring rank_title; /* 00E4:0100 */ parray unknown_l7; /* 0100:011C */ -} __attribute__((packed)); +} __packed_ws_be__(PlayerRecordsChallengeV3T, 0x100); using PlayerRecordsChallengeV3 = PlayerRecordsChallengeV3T; using PlayerRecordsChallengeV3BE = PlayerRecordsChallengeV3T; -check_struct_size(PlayerRecordsChallengeV3, 0x100); -check_struct_size(PlayerRecordsChallengeV3BE, 0x100); struct PlayerRecordsChallengeEp3 { /* 00:1C */ be_uint16_t title_color = 0x7FFF; // XRGB1555 @@ -704,8 +696,7 @@ struct PlayerRecordsChallengeEp3 { /* C8:E4 */ ChallengeAwardStateT ep2_online_award_state; /* D0:EC */ ChallengeAwardStateT ep1_offline_award_state; /* D8:F4 */ -} __attribute__((packed)); -check_struct_size(PlayerRecordsChallengeEp3, 0xD8); +} __packed_ws__(PlayerRecordsChallengeEp3, 0xD8); struct PlayerRecordsChallengeBB { /* 0000 */ le_uint16_t title_color = 0x7FFF; // XRGB1555 @@ -833,11 +824,9 @@ struct PlayerRecordsBattleT { } return ret; } -} __attribute__((packed)); +} __packed_ws_be__(PlayerRecordsBattleT, 0x18); using PlayerRecordsBattle = PlayerRecordsBattleT; using PlayerRecordsBattleBE = PlayerRecordsBattleT; -check_struct_size(PlayerRecordsBattle, 0x18); -check_struct_size(PlayerRecordsBattleBE, 0x18); template DestT convert_player_disp_data(const SrcT&, Language, Language) { @@ -1112,11 +1101,9 @@ struct SymbolChatT { ret.face_parts = this->face_parts; return ret; } -} __attribute__((packed)); +} __packed_ws_be__(SymbolChatT, 0x3C); using SymbolChat = SymbolChatT; using SymbolChatBE = SymbolChatT; -check_struct_size(SymbolChat, 0x3C); -check_struct_size(SymbolChatBE, 0x3C); struct TelepipeState { /* 00 */ le_uint16_t owner_client_id = 0xFFFF; diff --git a/src/Quest.cc b/src/Quest.cc index dac745e0..e5b6fc1a 100644 --- a/src/Quest.cc +++ b/src/Quest.cc @@ -51,11 +51,9 @@ struct PSOMemCardDLQFileEncryptedHeaderT { le_uint32_t decompressed_size; le_uint32_t round3_seed; // Data follows here. -} __attribute__((packed)); +} __packed_ws_be__(PSOMemCardDLQFileEncryptedHeaderT, 0x10); using PSOVMSDLQFileEncryptedHeader = PSOMemCardDLQFileEncryptedHeaderT; using PSOGCIDLQFileEncryptedHeader = PSOMemCardDLQFileEncryptedHeaderT; -check_struct_size(PSOVMSDLQFileEncryptedHeader, 0x10); -check_struct_size(PSOGCIDLQFileEncryptedHeader, 0x10); template string decrypt_download_quest_data_section( diff --git a/src/RareItemSet.hh b/src/RareItemSet.hh index b5d26a8f..a16d716b 100644 --- a/src/RareItemSet.hh +++ b/src/RareItemSet.hh @@ -93,11 +93,9 @@ protected: /* 08 */ U32T box_areas_offset; // -> parray /* 0C */ U32T box_rares_offset; // -> parray /* 10 */ - } __attribute__((packed)); + } __packed_ws_be__(OffsetsT, 0x10); using Offsets = OffsetsT; using OffsetsBE = OffsetsT; - check_struct_size(Offsets, 0x10); - check_struct_size(OffsetsBE, 0x10); struct BoxRare { uint8_t area_norm_plus_1; diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 8164f667..36ee4039 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -524,7 +524,13 @@ asio::awaitable start_login_server_procedure(shared_ptr c) { s->ep3_tournament_index->link_client(c); } - if (s->welcome_message.empty() || + if (c->preferred_lobby_id >= 0) { + s->add_client_to_available_lobby(c, true); + if (c->require_lobby()->is_game()) { + c->set_flag(Client::Flag::LOADING); + c->log.info_f("LOADING flag set"); + } + } else if (s->welcome_message.empty() || c->check_flag(Client::Flag::NO_D6) || !c->check_flag(Client::Flag::AT_WELCOME_MESSAGE)) { c->clear_flag(Client::Flag::AT_WELCOME_MESSAGE); @@ -2754,7 +2760,7 @@ static asio::awaitable on_10_main_menu(shared_ptr c, uint32_t item co_await enable_save_if_needed(c); send_lobby_list(c); if (!c->lobby.lock()) { - s->add_client_to_available_lobby(c); + s->add_client_to_available_lobby(c, false); } co_return; } @@ -2769,7 +2775,7 @@ static asio::awaitable on_10_main_menu(shared_ptr c, uint32_t item co_await send_get_player_info(c); } if (!c->lobby.lock()) { - s->add_client_to_available_lobby(c); + s->add_client_to_available_lobby(c, false); } break; } @@ -2784,7 +2790,7 @@ static asio::awaitable on_10_main_menu(shared_ptr c, uint32_t item co_await enable_save_if_needed(c); send_lobby_list(c); if (!c->lobby.lock()) { - s->add_client_to_available_lobby(c); + s->add_client_to_available_lobby(c, false); } break; } @@ -2810,7 +2816,7 @@ static asio::awaitable on_10_main_menu(shared_ptr c, uint32_t item co_await enable_save_if_needed(c); send_lobby_list(c); if (!c->lobby.lock()) { - s->add_client_to_available_lobby(c); + s->add_client_to_available_lobby(c, false); } break; } @@ -2829,7 +2835,7 @@ static asio::awaitable on_10_main_menu(shared_ptr c, uint32_t item co_await enable_save_if_needed(c); send_lobby_list(c); if (!c->lobby.lock()) { - s->add_client_to_available_lobby(c); + s->add_client_to_available_lobby(c, false); } break; } @@ -2858,7 +2864,7 @@ static asio::awaitable on_10_main_menu(shared_ptr c, uint32_t item co_await enable_save_if_needed(c); send_lobby_list(c); if (!c->lobby.lock()) { - s->add_client_to_available_lobby(c); + s->add_client_to_available_lobby(c, false); } break; } @@ -2886,7 +2892,7 @@ static asio::awaitable on_10_main_menu(shared_ptr c, uint32_t item co_await enable_save_if_needed(c); send_lobby_list(c); if (!c->lobby.lock()) { - s->add_client_to_available_lobby(c); + s->add_client_to_available_lobby(c, false); } break; } @@ -3501,7 +3507,7 @@ static asio::awaitable on_84(shared_ptr c, Channel::Message& msg) // If the client isn't in any lobby, then they just left a game. Add them to the lobby they requested, but fall // back to another lobby if it's full. c->preferred_lobby_id = cmd.item_id; - s->add_client_to_available_lobby(c); + s->add_client_to_available_lobby(c, false); } else { // If the client already is in a lobby, then they're using the lobby teleporter; add them to the lobby they diff --git a/src/SaveFileFormats.hh b/src/SaveFileFormats.hh index 34354f10..ba4e6417 100644 --- a/src/SaveFileFormats.hh +++ b/src/SaveFileFormats.hh @@ -158,11 +158,9 @@ struct WordSelectMessageT { ret.unknown_a4 = this->unknown_a4; return ret; } -} __attribute__((packed)); +} __packed_ws_be__(WordSelectMessageT, 0x1C); using WordSelectMessage = WordSelectMessageT; using WordSelectMessageBE = WordSelectMessageT; -check_struct_size(WordSelectMessage, 0x1C); -check_struct_size(WordSelectMessageBE, 0x1C); template struct SaveFileChatShortcutEntryT { diff --git a/src/ServerState.cc b/src/ServerState.cc index 2a454c28..b024c506 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -83,30 +83,45 @@ ServerState::ServerState(const string& config_filename, bool is_replay) bb_system_cache(new FileContentsCache(3600000000ULL)), gba_files_cache(new FileContentsCache(3600000000ULL)) {} -void ServerState::add_client_to_available_lobby(shared_ptr c) { +void ServerState::add_client_to_available_lobby(shared_ptr c, bool allow_games) { shared_ptr added_to_lobby; - if (c->preferred_lobby_id >= 0) { - try { - auto l = this->find_lobby(c->preferred_lobby_id); - if (l && !l->is_game() && l->check_flag(Lobby::Flag::PUBLIC) && l->version_is_allowed(c->version())) { - l->add_client(c); - added_to_lobby = l; - } - } catch (const out_of_range&) { + auto try_join_lobby = [&](uint32_t lobby_id) -> std::shared_ptr { + auto l = this->find_lobby(lobby_id); + if (!l) { + c->log.info_f("Cannot join lobby {:08X}: lobby does not exist", lobby_id); + return nullptr; } + if (!allow_games && l->is_game()) { + c->log.info_f("Cannot join lobby {:08X}: lobby is a game", lobby_id); + return nullptr; + } + static const std::string password = ""; + auto join_error = l->join_error_for_client(c, &password); + if (join_error == Lobby::JoinError::ALLOWED) { + try { + l->add_client(c); + c->log.info_f("Joined lobby {:08X}", lobby_id); + return l; + } catch (const out_of_range& e) { + c->log.info_f("Cannot join lobby {:08X}: {}", lobby_id, e.what()); + return nullptr; + } + } + c->log.info_f("Cannot join lobby {:08X}: {}", lobby_id, phosg::name_for_enum(join_error)); + return nullptr; + }; + + if (c->preferred_lobby_id >= 0) { + added_to_lobby = try_join_lobby(c->preferred_lobby_id); + c->preferred_lobby_id = -1; } - if (!added_to_lobby.get()) { + if (!added_to_lobby) { for (const auto& lobby_id : this->public_lobby_search_order(c)) { - try { - auto l = this->find_lobby(lobby_id); - if (l && !l->is_game() && l->check_flag(Lobby::Flag::PUBLIC) && l->version_is_allowed(c->version())) { - l->add_client(c); - added_to_lobby = l; - break; - } - } catch (const out_of_range&) { + added_to_lobby = try_join_lobby(lobby_id); + if (added_to_lobby) { + break; } } } @@ -461,8 +476,10 @@ shared_ptr ServerState::item_parameter_table_for_encod } shared_ptr ServerState::mag_evolution_table(Version version) const { - if (is_v1_or_v2(version)) { - return this->mag_evolution_table_v1_v2; + if (is_v1(version)) { + return this->mag_evolution_table_v1; + } else if (is_v2(version)) { + return this->mag_evolution_table_v2; } else if (!is_v4(version)) { return this->mag_evolution_table_v3; } else { @@ -2150,26 +2167,29 @@ void ServerState::load_item_definitions() { string path = std::format("system/item-tables/ItemPMT-{}.prs", 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] = make_shared(data, v); + new_item_parameter_tables[v_s] = ItemParameterTable::create(data, v); } auto json = phosg::JSON::parse(phosg::load_file("system/item-tables/translation-table.json")); auto new_item_translation_table = make_shared(json, new_item_parameter_tables); - // TODO: We should probably load the tables for other versions too. - config_log.info_f("Loading v1/v2 mag evolution table"); - auto mag_data_v1_v2 = make_shared(prs_decompress(phosg::load_file("system/item-tables/ItemMagEdit-dc-v2.prs"))); - auto new_table_v1_v2 = make_shared(mag_data_v1_v2, 0x3A); + config_log.info_f("Loading v1 mag evolution table"); + auto mag_data_v1 = make_shared(prs_decompress(phosg::load_file("system/item-tables/ItemMagEdit-dc-v1.prs"))); + auto new_table_v1 = MagEvolutionTable::create(mag_data_v1, Version::DC_V1); + config_log.info_f("Loading v2 mag evolution table"); + auto mag_data_v2 = make_shared(prs_decompress(phosg::load_file("system/item-tables/ItemMagEdit-dc-v2.prs"))); + auto new_table_v2 = MagEvolutionTable::create(mag_data_v2, Version::DC_V2); config_log.info_f("Loading v3 mag evolution table"); auto mag_data_v3 = make_shared(prs_decompress(phosg::load_file("system/item-tables/ItemMagEdit-xb-v3.prs"))); - auto new_table_v3 = make_shared(mag_data_v3, 0x43); + auto new_table_v3 = MagEvolutionTable::create(mag_data_v3, Version::XB_V3); config_log.info_f("Loading v4 mag evolution table"); auto mag_data_v4 = make_shared(prs_decompress(phosg::load_file("system/item-tables/ItemMagEdit-bb-v4.prs"))); - auto new_table_v4 = make_shared(mag_data_v4, 0x53); + auto new_table_v4 = MagEvolutionTable::create(mag_data_v4, Version::BB_V4); this->item_parameter_tables = std::move(new_item_parameter_tables); this->item_translation_table = std::move(new_item_translation_table); - this->mag_evolution_table_v1_v2 = std::move(new_table_v1_v2); + this->mag_evolution_table_v1 = std::move(new_table_v1); + this->mag_evolution_table_v2 = std::move(new_table_v2); this->mag_evolution_table_v3 = std::move(new_table_v3); this->mag_evolution_table_v4 = std::move(new_table_v4); } diff --git a/src/ServerState.hh b/src/ServerState.hh index 5f0bf1d2..70586054 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -23,6 +23,7 @@ #include "ItemTranslationTable.hh" #include "LevelTable.hh" #include "Lobby.hh" +#include "MagEvolutionTable.hh" #include "Menu.hh" #include "Quest.hh" #include "TeamIndex.hh" @@ -216,7 +217,8 @@ struct ServerState : public std::enable_shared_from_this { std::array, NUM_VERSIONS> item_stack_limits_tables; size_t bb_max_bank_items = 200; size_t bb_max_bank_meseta = 999999; - std::shared_ptr mag_evolution_table_v1_v2; + std::shared_ptr mag_evolution_table_v1; + std::shared_ptr mag_evolution_table_v2; std::shared_ptr mag_evolution_table_v3; std::shared_ptr mag_evolution_table_v4; std::shared_ptr text_index; @@ -324,7 +326,7 @@ struct ServerState : public std::enable_shared_from_this { ServerState& operator=(const ServerState&) = delete; ServerState& operator=(ServerState&&) = delete; - void add_client_to_available_lobby(std::shared_ptr c); + void add_client_to_available_lobby(std::shared_ptr c, bool allow_games); void remove_client_from_lobby(std::shared_ptr c); bool change_client_lobby( std::shared_ptr c, diff --git a/src/Text.hh b/src/Text.hh index 4830abd4..d97d9f9d 100644 --- a/src/Text.hh +++ b/src/Text.hh @@ -23,6 +23,11 @@ __attribute__((packed)); \ check_struct_size(StructT, Size) +#define __packed_ws_be__(StructT, Size) \ + __attribute__((packed)); \ + check_struct_size(StructT, Size); \ + check_struct_size(StructT, Size) + // Conversion functions std::string encode_utf8_char(uint32_t ch); diff --git a/src/WordSelectTable.cc b/src/WordSelectTable.cc index 21d7db3d..aa50748a 100644 --- a/src/WordSelectTable.cc +++ b/src/WordSelectTable.cc @@ -40,12 +40,9 @@ struct NonWindowsRootT { U32T table4; U32T article_types_table; U32T table6; -} __attribute__((packed)); - +} __packed_ws_be__(NonWindowsRootT, 0x1C); using NonWindowsRoot = NonWindowsRootT; using NonWindowsRootBE = NonWindowsRootT; -check_struct_size(NonWindowsRoot, 0x1C); -check_struct_size(NonWindowsRootBE, 0x1C); struct PCV2Root { le_uint32_t unknown_a1;