#include "ItemParameterTable.hh" using namespace std; 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_V1_11_2000_PROTOTYPE: { 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"); } this->first_rare_mag_index = 0x28; } set ItemParameterTable::compute_all_valid_primary_identifiers() const { set ret; auto find_items_1d = [&](uint64_t data1, size_t position) -> size_t { ItemData item(data1, 0); for (size_t x = 0; x < 0x100; x++) { item.data1[position] = x; try { this->get_item_id(item); } catch (const out_of_range&) { return x; } ret.emplace(item.primary_identifier()); } return 0x100; }; auto find_items_2d = [&](uint64_t data1) { for (size_t x = 0; x < 0x100; x++) { size_t effective_data1 = data1 | (static_cast(x) << 48); size_t data2_position = (effective_data1 == 0x0302000000000000) ? 4 : 2; if (find_items_1d(effective_data1, data2_position) == 0) { break; } } }; find_items_2d(0x0000000000000000); find_items_1d(0x0101000000000000, 2); find_items_1d(0x0102000000000000, 2); find_items_1d(0x0103000000000000, 2); find_items_1d(0x0200000000000000, 1); find_items_2d(0x0300000000000000); 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 = this->stat_boost; return ret; } ItemParameterTable::WeaponV4 ItemParameterTable::WeaponGCNTE::to_v4() const { WeaponV4 ret; ret.base.id = this->base.id.load(); ret.base.type = this->base.type.load(); ret.base.skin = this->base.skin.load(); ret.class_flags = this->class_flags.load(); ret.atp_min = this->atp_min.load(); ret.atp_max = this->atp_max.load(); ret.atp_required = this->atp_required.load(); ret.mst_required = this->mst_required.load(); ret.ata_required = this->ata_required.load(); ret.mst = this->mst.load(); ret.max_grind = this->max_grind; ret.photon = this->photon; ret.special = this->special; ret.ata = this->ata; ret.stat_boost = this->stat_boost; 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_a2 = this->unknown_a2; ret.unknown_a3 = this->unknown_a3; return ret; } template ItemParameterTable::WeaponV4 ItemParameterTable::WeaponV3T::to_v4() const { WeaponV4 ret; ret.base.id = this->base.id.load(); ret.base.type = this->base.type.load(); ret.base.skin = this->base.skin.load(); ret.class_flags = this->class_flags.load(); ret.atp_min = this->atp_min.load(); ret.atp_max = this->atp_max.load(); ret.atp_required = this->atp_required.load(); ret.mst_required = this->mst_required.load(); ret.ata_required = this->ata_required.load(); ret.mst = this->mst.load(); ret.max_grind = this->max_grind; ret.photon = this->photon; ret.special = this->special; ret.ata = this->ata; ret.stat_boost = this->stat_boost; 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_a2 = this->unknown_a2; ret.unknown_a3 = this->unknown_a3; ret.unknown_a4 = this->unknown_a4; ret.unknown_a5 = this->unknown_a5; ret.tech_boost = this->tech_boost; ret.combo_type = this->combo_type; 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 = this->stat_boost; ret.tech_boost = this->tech_boost; ret.unknown_a2 = this->unknown_a2; return ret; } template ItemParameterTable::ArmorOrShieldV4 ItemParameterTable::ArmorOrShieldV3T::to_v4() const { ArmorOrShieldV4 ret; ret.base.id = this->base.id.load(); ret.base.type = this->base.type.load(); ret.base.skin = this->base.skin.load(); ret.dfp = this->dfp.load(); ret.evp = this->evp.load(); ret.block_particle = this->block_particle; ret.block_effect = this->block_effect; ret.class_flags = this->class_flags.load(); 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 = this->stat_boost; ret.tech_boost = this->tech_boost; ret.unknown_a2 = this->unknown_a2.load(); 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.load(); ret.base.type = this->base.type.load(); ret.base.skin = this->base.skin.load(); ret.stat = this->stat.load(); ret.stat_amount = this->stat_amount.load(); ret.modifier_amount = this->modifier_amount.load(); 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.load(); ret.base.type = this->base.type.load(); ret.base.skin = this->base.skin.load(); ret.feed_table = this->feed_table.load(); 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.load(); 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_flag = this->item_flag; return ret; } template ItemParameterTable::ToolV4 ItemParameterTable::ToolV3T::to_v4() const { ToolV4 ret; ret.base.id = this->base.id.load(); ret.base.type = this->base.type.load(); ret.base.skin = this->base.skin.load(); ret.amount = this->amount.load(); ret.tech = this->tech.load(); ret.cost = this->cost.load(); ret.item_flag = this->item_flag.load(); return ret; } template size_t indirect_lookup_2d_count(const StringReader& r, size_t root_offset, size_t co_index) { using ArrayRefT = typename std::conditional_t; return r.pget(root_offset + sizeof(ArrayRefT) * co_index).count; } template const T& indirect_lookup_2d(const StringReader& r, size_t root_offset, size_t co_index, size_t item_index) { using ArrayRefT = typename std::conditional_t; 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&) { ArmorOrShieldV4 def_v4; if (this->offsets_dc_protos) { def_v4 = indirect_lookup_2d(this->r, this->offsets_dc_protos->armor_table, data1_1 - 1, data1_2).to_v4(); } else if (this->offsets_v1_v2) { def_v4 = indirect_lookup_2d(this->r, this->offsets_v1_v2->armor_table, data1_1 - 1, data1_2).to_v4(); } else if (this->offsets_gc_nte) { def_v4 = indirect_lookup_2d(this->r, this->offsets_gc_nte->armor_table, data1_1 - 1, data1_2).to_v4(); } else if (this->offsets_v3_le) { def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_le->armor_table, data1_1 - 1, data1_2).to_v4(); } else if (this->offsets_v3_be) { def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_be->armor_table, data1_1 - 1, data1_2).to_v4(); } else { throw logic_error("table is not v2, v3, or v4"); } if (data1_2 >= parsed_vec.size()) { parsed_vec.resize(data1_2 + 1); } parsed_vec[data1_2] = def_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&) { UnitV4 def_v4; if (this->offsets_dc_protos) { def_v4 = indirect_lookup_2d(this->r, this->offsets_dc_protos->unit_table, 0, data1_2).to_v4(); } else if (this->offsets_v1_v2) { def_v4 = indirect_lookup_2d(this->r, this->offsets_v1_v2->unit_table, 0, data1_2).to_v4(); } else if (this->offsets_gc_nte) { def_v4 = indirect_lookup_2d(this->r, this->offsets_gc_nte->unit_table, 0, data1_2).to_v4(); } else if (this->offsets_v3_le) { def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_le->unit_table, 0, data1_2).to_v4(); } else if (this->offsets_v3_be) { def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_be->unit_table, 0, data1_2).to_v4(); } else { throw logic_error("table is not v2, v3, or v4"); } if (data1_2 >= this->parsed_units.size()) { this->parsed_units.resize(data1_2 + 1); } this->parsed_units[data1_2] = def_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&) { MagV4 def_v4; if (this->offsets_dc_protos) { def_v4 = indirect_lookup_2d(this->r, this->offsets_dc_protos->mag_table, 0, data1_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, data1_1).to_v4(); } else { def_v4 = indirect_lookup_2d(this->r, this->offsets_v1_v2->mag_table, 0, data1_1).to_v4(); } } else if (this->offsets_gc_nte) { def_v4 = indirect_lookup_2d(this->r, this->offsets_gc_nte->mag_table, 0, data1_1).to_v4(); } else if (this->offsets_v3_le) { def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_le->mag_table, 0, data1_1).to_v4(); } else if (this->offsets_v3_be) { def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_be->mag_table, 0, data1_1).to_v4(); } else { throw logic_error("table is not v2, v3, or v4"); } if (data1_1 >= this->parsed_mags.size()) { this->parsed_mags.resize(data1_1 + 1); } this->parsed_mags[data1_1] = def_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(string_printf("invalid tool class %08" PRIX32, 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"); } } template float ItemParameterTable::get_sale_divisor_t(const OffsetsT* offsets, uint8_t data1_0, uint8_t data1_1) const { using FloatT = typename std::conditional::type; 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(FloatT)); 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.load(); this->parsed_specials[special].amount = sp_be.amount.load(); } 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.load(); this->parsed_specials[special].amount = sp_be.amount.load(); } 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"); } } 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; case 1: if (item.data1[1] == 3) { return this->get_unit(item.data1[2]).base.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; } throw runtime_error("invalid item"); case 2: return this->get_mag(item.data1[1]).base.id; case 3: if (item.data1[1] == 2) { return this->get_tool(2, item.data1[4]).base.id; } else { return this->get_tool(item.data1[1], item.data1[2]).base.id; } throw logic_error("this should be impossible"); case 4: throw runtime_error("item is meseta and therefore has no definition"); default: throw runtime_error("invalid item"); } } 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; case 1: if (item.data1[1] == 3) { return this->get_unit(item.data1[2]).base.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; } throw runtime_error("invalid item"); case 2: return this->get_mag(item.data1[1]).base.team_points; case 3: if (item.data1[1] == 2) { return this->get_tool(2, item.data1[4]).base.team_points; } else { return this->get_tool(item.data1[1], item.data1[2]).base.team_points; } throw logic_error("this should be impossible"); case 4: throw runtime_error("item is meseta and therefore has no definition"); default: throw runtime_error("invalid item"); } } 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_flag & 0x80) ? 12 : 0; } else { return 0; } } uint8_t ItemParameterTable::get_item_adjusted_stars(const ItemData& item, bool ignore_unidentified) const { uint8_t ret = this->get_item_base_stars(item); if (item.data1[0] == 0) { bool is_unidentified = (!ignore_unidentified) && (item.data1[4] & 0x80); if (ret < 9) { if (!is_unidentified) { ret += this->get_special_stars(item.data1[4]); } } else if (is_unidentified) { ret = 0; } } else if (item.data1[0] == 1) { if (item.data1[1] == 3) { int16_t unit_bonus = item.get_unit_bonus(); if (unit_bonus < 0) { ret--; } else if (unit_bonus > 0) { ret++; } } } return min(ret, 12); } bool ItemParameterTable::is_item_rare(const ItemData& item) const { try { return (this->get_item_base_stars(item) >= 9); } catch (const out_of_range&) { return false; } } 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]); } const ItemParameterTable::ItemCombination& ItemParameterTable::get_item_combination( const ItemData& used_item, const ItemData& equipped_item) const { for (const auto& def : this->get_all_combinations_for_used_item(used_item)) { if ((def.equipped_item[0] == 0xFF || def.equipped_item[0] == equipped_item.data1[0]) && (def.equipped_item[1] == 0xFF || def.equipped_item[1] == equipped_item.data1[1]) && (def.equipped_item[2] == 0xFF || def.equipped_item[2] == equipped_item.data1[2])) { return def; } } throw out_of_range("no item combination applies"); } const std::vector& ItemParameterTable::get_all_combinations_for_used_item( const ItemData& used_item) const { try { uint32_t key = (used_item.data1[0] << 16) | (used_item.data1[1] << 8) | used_item.data1[2]; return this->get_all_item_combinations().at(key); } catch (const out_of_range&) { static const vector ret; return ret; } } 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: { if (item.data1[4] & 0x80) { return 8; } if (this->is_item_rare(item)) { return 80; } float sale_divisor = this->get_sale_divisor(item.data1[0], item.data1[1]); if (sale_divisor == 0.0) { throw runtime_error("item sale divisor is zero"); } const auto& def = this->get_weapon(item.data1[1], item.data1[2]); double atp_max = def.atp_max + item.data1[3]; double atp_factor = ((atp_max * atp_max) / sale_divisor); double bonus_factor = 0.0; for (size_t bonus_index = 0; bonus_index < 3; bonus_index++) { uint8_t bonus_type = item.data1[(2 * bonus_index) + 6]; if ((bonus_type > 0) && (bonus_type < 6)) { bonus_factor += item.data1[(2 * bonus_index) + 7]; } bonus_factor += 100.0; } size_t special_stars = this->get_special_stars(item.data1[4]); double special_stars_factor = 1000.0 * special_stars * special_stars; return special_stars_factor + ((atp_factor * bonus_factor) / 100.0); } case 1: { if (this->is_item_rare(item)) { return 80; } if (item.data1[1] == 3) { // Unit return this->get_item_adjusted_stars(item) * this->get_sale_divisor(item.data1[0], 3); } double sale_divisor = (double)this->get_sale_divisor(item.data1[0], item.data1[1]); if (sale_divisor == 0.0) { throw runtime_error("item sale divisor is zero"); } int16_t def_bonus = item.get_armor_or_shield_defense_bonus(); int16_t evp_bonus = item.get_common_armor_evasion_bonus(); const auto& def = this->get_armor_or_shield(item.data1[1], item.data1[2]); double power_factor = def.dfp + def.evp + def_bonus + evp_bonus; double power_factor_floor = static_cast((power_factor * power_factor) / sale_divisor); return power_factor_floor + (70.0 * static_cast(item.data1[5] + 1) * static_cast(def.required_level + 1)); } case 2: return (item.data1[2] + 1) * this->get_sale_divisor(2, item.data1[1]); 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.cost * ((item.data1[1] == 2) ? (item.data1[2] + 1) : 1); } case 4: return item.data2d; default: throw runtime_error("invalid item"); } throw logic_error("this should be impossible"); } MagEvolutionTable::MagEvolutionTable(shared_ptr data) : data(data), r(*data) { size_t offset_table_offset = this->r.pget_u32l(this->data->size() - 0x10); this->offsets = &r.pget(offset_table_offset); } uint8_t MagEvolutionTable::get_evolution_number(uint8_t data1_1) const { const auto& table = this->r.pget(this->offsets->evolution_number); return table.values[data1_1]; }