#include "CommonItemSet.hh" #include "AFSArchive.hh" #include "EnemyType.hh" #include "GSLArchive.hh" #include "StaticGameData.hh" using namespace std; template JSON to_json(const parray& v) { auto ret = JSON::list(); for (size_t z = 0; z < Count; z++) { ret.emplace_back(v[z]); } return ret; } template void from_json_into(const JSON& json, parray& ret) { if (json.size() != Count) { throw runtime_error("incorrect array length"); } for (size_t z = 0; z < Count; z++) { ret[z] = json.at(z).as_int(); } } template JSON to_json(const parray, Count>& v) { auto ret = JSON::list(); for (size_t z = 0; z < Count; z++) { ret.emplace_back(to_json(v[z])); } return ret; } template void from_json_into(const JSON& json, parray, Count>& ret) { if (json.size() != Count) { throw runtime_error("incorrect array length"); } for (size_t z = 0; z < Count; z++) { from_json_into(json.at(z), ret[z]); } } template JSON to_json(const CommonItemSet::Table::Range& v) { if (v.min == v.max) { return JSON(v.min); } else { return JSON::list({v.min, v.max}); } } template void from_json_into(const JSON& json, CommonItemSet::Table::Range& ret) { if (json.is_int()) { IntT v = json.as_int(); ret.min = v; ret.max = v; } else { const auto& l = json.as_list(); if (l.size() != 2) { throw runtime_error("incorrect range list length"); } ret.min = l.at(0)->as_int(); ret.max = l.at(1)->as_int(); } } template JSON to_json(const parray, Count1>& v) { auto ret = JSON::list(); for (size_t z = 0; z < Count1; z++) { ret.emplace_back(to_json(v[z])); } return ret; } template void from_json_into(const JSON& json, parray, Count1>& ret) { if (json.size() != Count1) { throw runtime_error("incorrect array length"); } for (size_t z = 0; z < Count1; z++) { from_json_into(json.at(z), ret[z]); } } template void from_json_into(const JSON& json, parray, Count2>, Count1>& ret) { if (json.size() != Count1) { throw runtime_error("incorrect array length"); } for (size_t z = 0; z < Count1; z++) { from_json_into(json.at(z), ret[z]); } } CommonItemSet::Table::Table(const JSON& json, Episode episode) : episode(episode) { from_json_into(json.at("BaseWeaponTypeProbTable"), this->base_weapon_type_prob_table); from_json_into(json.at("SubtypeBaseTable"), this->subtype_base_table); from_json_into(json.at("SubtypeAreaLengthTable"), this->subtype_area_length_table); from_json_into(json.at("GrindProbTable"), this->grind_prob_table); from_json_into(json.at("ArmorShieldTypeIndexProbTable"), this->armor_shield_type_index_prob_table); from_json_into(json.at("ArmorSlotCountProbTable"), this->armor_slot_count_prob_table); from_json_into(json.at("BoxMesetaRanges"), this->box_meseta_ranges); this->has_rare_bonus_value_prob_table = json.at("HasRareBonusValueProbTable").as_bool(); from_json_into(json.at("BonusValueProbTable"), this->bonus_value_prob_table); from_json_into(json.at("NonRareBonusProbSpec"), this->nonrare_bonus_prob_spec); from_json_into(json.at("BonusTypeProbTable"), this->bonus_type_prob_table); from_json_into(json.at("SpecialMult"), this->special_mult); from_json_into(json.at("SpecialPercent"), this->special_percent); from_json_into(json.at("ToolClassProbTable"), this->tool_class_prob_table); from_json_into(json.at("TechniqueIndexProbTable"), this->technique_index_prob_table); from_json_into(json.at("TechniqueLevelRanges"), this->technique_level_ranges); this->armor_or_shield_type_bias = json.at("ArmorOrShieldTypeBias").as_int(); from_json_into(json.at("UnitMaxStarsTable"), this->unit_max_stars_table); from_json_into(json.at("BoxItemClassProbTable"), this->box_item_class_prob_table); const auto& enemy_meseta_ranges_json = json.at("EnemyMesetaRanges").as_dict(); const auto& enemy_type_drop_probs_json = json.at("EnemyTypeDropProbs").as_dict(); const auto& enemy_item_classes_json = json.at("EnemyItemClasses").as_dict(); for (size_t z = 0; z < 0x64; z++) { static const array episodes = {Episode::EP1, Episode::EP2, Episode::EP4}; for (Episode episode : episodes) { for (auto type : enemy_types_for_rare_table_index(episode, z)) { string name = string_printf("%s:%s", abbreviation_for_episode(episode), name_for_enum(type)); from_json_into(*enemy_meseta_ranges_json.at(name), this->enemy_meseta_ranges[z]); this->enemy_type_drop_probs[z] = enemy_type_drop_probs_json.at(name)->as_int(); this->enemy_item_classes[z] = enemy_item_classes_json.at(name)->as_int(); } } } } static const char* name_for_common_item_class(uint8_t item_class) { switch (item_class) { case 0x00: return "WEAPON "; case 0x01: return "ARMOR "; case 0x02: return "SHIELD "; case 0x03: return "UNIT "; case 0x04: return "TOOL "; case 0x05: return "MESETA "; case 0x06: return "NOTHING"; default: return "UNKNOWN"; } } void CommonItemSet::Table::print(FILE* stream) const { const auto& meseta_ranges = this->enemy_meseta_ranges; const auto& drop_probs = this->enemy_type_drop_probs; const auto& item_classes = this->enemy_item_classes; fprintf(stream, "Enemy tables:\n"); fprintf(stream, " ## $LOW $HIGH DAR%% ITEM ENEMIES\n"); for (size_t z = 0; z < 0x64; z++) { string enemies_str; for (EnemyType enemy_type : enemy_types_for_rare_table_index(this->episode, z)) { if (!enemies_str.empty()) { enemies_str += ", "; } enemies_str += name_for_enum(enemy_type); } if (drop_probs[z]) { fprintf(stream, " %02zX %5hu %5hu %3hhu%% %02hX:%s %s\n", z, meseta_ranges[z].min, meseta_ranges[z].max, drop_probs[z], item_classes[z], name_for_common_item_class(item_classes[z]), enemies_str.c_str()); } else { fprintf(stream, " %02zX ----- ----- 0%% -- %s\n", z, enemies_str.c_str()); } } static const array base_weapon_type_names = { "SABER ", "SWORD ", "DAGGER ", "PARTISAN", "SLICER ", "HANDGUN ", "RIFLE ", "MECHGUN ", "SHOT ", "CANE ", "ROD ", "WAND ", }; fprintf(stream, "Base weapon config:\n"); fprintf(stream, " TYPE PROB [SB AL] FLOORS\n"); for (size_t z = 0; z < 12; z++) { uint8_t floor_to_class[10]; if (this->subtype_base_table[z] < 0) { size_t start_floor = std::min(-this->subtype_area_length_table[z], 10); for (size_t x = 0; x < start_floor; x++) { floor_to_class[x] = 0xFF; } for (size_t x = start_floor; x < 10; x++) { floor_to_class[x] = (x - start_floor) / this->subtype_area_length_table[z]; } } else { for (size_t x = 0; x < 10; x++) { floor_to_class[x] = this->subtype_base_table[z] + (x / this->subtype_area_length_table[z]); } } fprintf(stream, " %02zX:%s %3hhu%% [%02hhX %02hhX] %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX\n", z, base_weapon_type_names[z], this->base_weapon_type_prob_table[z], this->subtype_base_table[z], this->subtype_area_length_table[z], floor_to_class[0], floor_to_class[1], floor_to_class[2], floor_to_class[3], floor_to_class[4], floor_to_class[5], floor_to_class[6], floor_to_class[7], floor_to_class[8], floor_to_class[9]); } fprintf(stream, "Box configuration:\n"); fprintf(stream, " AR $LOW $HIGH WEP%% ARM%% SHD%% UNI%% TL%% MST%% NO%%\n"); for (size_t z = 0; z < 10; z++) { fprintf(stream, " %02zX %5hu %5hu %3hhu%% %3hhu%% %3hhu%% %3hhu%% %3hhu%% %3hhu%% %3hhu%%\n", z, this->box_meseta_ranges[z].min, this->box_meseta_ranges[z].max, this->box_item_class_prob_table[0][z], this->box_item_class_prob_table[1][z], this->box_item_class_prob_table[2][z], this->box_item_class_prob_table[3][z], this->box_item_class_prob_table[4][z], this->box_item_class_prob_table[5][z], this->box_item_class_prob_table[6][z]); } fprintf(stream, "Weapon drops:\n"); fprintf(stream, " Grinds:\n"); fprintf(stream, " GD AR0%% AR1%% AR2%% AR3%%\n"); for (size_t z = 0; z < 9; z++) { fprintf(stream, " +%zu %3hhd%% %3hhd%% %3hhd%% %3hhd%%\n", z, this->grind_prob_table[z][0], this->grind_prob_table[z][1], this->grind_prob_table[z][2], this->grind_prob_table[z][3]); } fprintf(stream, " Bonus value table:\n"); fprintf(stream, " ID"); for (int8_t v = -10; v <= 100; v += 5) { fprintf(stream, " %5hhd%%", v); } fputc('\n', stream); for (size_t z = 0; z < (this->has_rare_bonus_value_prob_table ? 6 : 5); z++) { fprintf(stream, " %02zX", z); for (size_t x = 0; x < 0x17; x++) { fprintf(stream, " %5hu#", this->bonus_value_prob_table[x][z]); } fputc('\n', stream); } fprintf(stream, " Area config tables:\n"); fprintf(stream, " AR BONUS SP NO%% NTV%% AB%% MAC%% DRK%% HIT%% SM SPC%%\n"); for (size_t z = 0; z < 10; z++) { fprintf(stream, " %02zX %02hhX %02hhX %02hhX %3hhu%% %3hhu%% %3hhu%% %3hhu%% %3hhu%% %3hhu%% %02hhX %3hhu%%\n", z, this->nonrare_bonus_prob_spec[0][z], this->nonrare_bonus_prob_spec[1][z], this->nonrare_bonus_prob_spec[2][z], this->bonus_type_prob_table[0][z], this->bonus_type_prob_table[1][z], this->bonus_type_prob_table[2][z], this->bonus_type_prob_table[3][z], this->bonus_type_prob_table[4][z], this->bonus_type_prob_table[5][z], this->special_mult[z], this->special_percent[z]); } fprintf(stream, " Tool class table:\n"); fprintf(stream, " CS A1 A2 A3 A4 A5 A6 A7 A8 A9 A10\n"); for (size_t tool_class = 0; tool_class < this->tool_class_prob_table.size(); tool_class++) { fprintf(stream, " %02zX", tool_class); for (size_t area_norm = 0; area_norm < 10; area_norm++) { fprintf(stream, " %5hu", this->tool_class_prob_table[tool_class][area_norm]); } fputc('\n', stream); } static const array technique_names = { "FOIE ", "GIFOIE ", "RAFOIE ", "BARTA ", "GIBARTA ", "RABARTA ", "ZONDE ", "GIZONDE ", "RAZONDE ", "GRANTS ", "DEBAND ", "JELLEN ", "ZALURE ", "SHIFTA ", "RYUKER ", "RESTA ", "ANTI ", "REVERSER", "MEGID ", }; fprintf(stream, " Technique table:\n"); fprintf(stream, " TECH A1 A2 A3 A4 A5 A6 A7 A8 A9 A10\n"); for (size_t tech_num = 0; tech_num < this->technique_index_prob_table.size(); tech_num++) { fprintf(stream, " %02zX:%s", tech_num, technique_names[tech_num]); for (size_t area_norm = 0; area_norm < 10; area_norm++) { uint16_t prob = this->technique_index_prob_table[tech_num][area_norm]; if (prob) { const auto& level_range = this->technique_level_ranges[tech_num][area_norm]; size_t min_level = level_range.min + 1; size_t max_level = level_range.max + 1; fprintf(stream, " %5hu[%2zu-%2zu]", prob, min_level, max_level); } else { fprintf(stream, " 0[-----]"); } } fputc('\n', stream); } fprintf(stream, " Armor/shield type bias: %hhu\n", this->armor_or_shield_type_bias); fprintf(stream, " Armor/shield type index table:\n"); fprintf(stream, " TY PROB\n"); for (size_t z = 0; z < 5; z++) { fprintf(stream, " %02zX %3hhu%%\n", z, this->armor_shield_type_index_prob_table[z]); } fprintf(stream, " Armor/shield slot count table:\n"); fprintf(stream, " #S PROB\n"); for (size_t z = 0; z < 5; z++) { fprintf(stream, " %02zX %3hhu%%\n", z, this->armor_slot_count_prob_table[z]); } fprintf(stream, " Unit maximum stars table:\n"); fprintf(stream, " AR #*\n"); for (size_t z = 0; z < 10; z++) { fprintf(stream, " %02zX %3hhu\n", z, this->unit_max_stars_table[z]); } } JSON CommonItemSet::Table::json() const { JSON enemy_meseta_ranges_json = JSON::dict(); JSON enemy_type_drop_probs_json = JSON::dict(); JSON enemy_item_classes_json = JSON::dict(); for (size_t z = 0; z < 0x64; z++) { static const array episodes = {Episode::EP1, Episode::EP2, Episode::EP4}; for (Episode episode : episodes) { for (auto type : enemy_types_for_rare_table_index(episode, z)) { string name = string_printf("%s:%s", abbreviation_for_episode(episode), name_for_enum(type)); enemy_meseta_ranges_json.emplace(name, to_json(this->enemy_meseta_ranges[z])); enemy_type_drop_probs_json.emplace(name, this->enemy_type_drop_probs[z]); enemy_item_classes_json.emplace(name, this->enemy_item_classes[z]); } } } return JSON::dict({ {"BaseWeaponTypeProbTable", to_json(this->base_weapon_type_prob_table)}, {"SubtypeBaseTable", to_json(this->subtype_base_table)}, {"SubtypeAreaLengthTable", to_json(this->subtype_area_length_table)}, {"GrindProbTable", to_json(this->grind_prob_table)}, {"ArmorShieldTypeIndexProbTable", to_json(this->armor_shield_type_index_prob_table)}, {"ArmorSlotCountProbTable", to_json(this->armor_slot_count_prob_table)}, {"EnemyMesetaRanges", std::move(enemy_meseta_ranges_json)}, {"EnemyTypeDropProbs", std::move(enemy_type_drop_probs_json)}, {"EnemyItemClasses", std::move(enemy_item_classes_json)}, {"BoxMesetaRanges", to_json(this->box_meseta_ranges)}, {"HasRareBonusValueProbTable", this->has_rare_bonus_value_prob_table}, {"BonusValueProbTable", to_json(this->bonus_value_prob_table)}, {"NonRareBonusProbSpec", to_json(this->nonrare_bonus_prob_spec)}, {"BonusTypeProbTable", to_json(this->bonus_type_prob_table)}, {"SpecialMult", to_json(this->special_mult)}, {"SpecialPercent", to_json(this->special_percent)}, {"ToolClassProbTable", to_json(this->tool_class_prob_table)}, {"TechniqueIndexProbTable", to_json(this->technique_index_prob_table)}, {"TechniqueLevelRanges", to_json(this->technique_level_ranges)}, {"ArmorOrShieldTypeBias", this->armor_or_shield_type_bias}, {"UnitMaxStarsTable", to_json(this->unit_max_stars_table)}, {"BoxItemClassProbTable", to_json(this->box_item_class_prob_table)}, }); } JSON CommonItemSet::json() const { auto modes_dict = JSON::dict(); static const array modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO}; for (const auto& mode : modes) { auto episodes_dict = JSON::dict(); static const array episodes = {Episode::EP1, Episode::EP2, Episode::EP4}; for (const auto& episode : episodes) { auto difficulty_dict = JSON::dict(); for (uint8_t difficulty = 0; difficulty < 4; difficulty++) { auto section_id_dict = JSON::dict(); for (uint8_t section_id = 0; section_id < 10; section_id++) { try { auto table = this->get_table(episode, mode, difficulty, section_id); section_id_dict.emplace(name_for_section_id(section_id), table->json()); } catch (const runtime_error&) { } } difficulty_dict.emplace(token_name_for_difficulty(difficulty), std::move(section_id_dict)); } episodes_dict.emplace(token_name_for_episode(episode), std::move(difficulty_dict)); } modes_dict.emplace(name_for_mode(mode), std::move(episodes_dict)); } return modes_dict; } void CommonItemSet::print(FILE* stream) const { static const array modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO}; for (const auto& mode : modes) { static const array episodes = {Episode::EP1, Episode::EP2, Episode::EP4}; for (const auto& episode : episodes) { for (uint8_t difficulty = 0; difficulty < 4; difficulty++) { for (uint8_t section_id = 0; section_id < 10; section_id++) { try { auto table = this->get_table(episode, mode, difficulty, section_id); fprintf(stream, "============ %s %s %s %s\n", name_for_mode(mode), name_for_episode(episode), name_for_difficulty(difficulty), name_for_section_id(section_id)); table->print(stream); } catch (const runtime_error&) { } } } } } } CommonItemSet::Table::Table(const StringReader& r, bool is_big_endian, bool is_v3, Episode episode) : episode(episode) { if (is_big_endian) { this->parse_itempt_t(r, is_v3); } else { this->parse_itempt_t(r, is_v3); } } template void CommonItemSet::Table::parse_itempt_t(const StringReader& r, bool is_v3) { using U16T = typename std::conditional::type; using U32T = typename std::conditional::type; const auto& offsets = r.pget>(r.pget(r.size() - 0x10)); this->base_weapon_type_prob_table = r.pget>(offsets.base_weapon_type_prob_table_offset); this->subtype_base_table = r.pget>(offsets.subtype_base_table_offset); this->subtype_area_length_table = r.pget>(offsets.subtype_area_length_table_offset); this->grind_prob_table = r.pget, 9>>(offsets.grind_prob_table_offset); this->armor_shield_type_index_prob_table = r.pget>(offsets.armor_shield_type_index_prob_table_offset); this->armor_slot_count_prob_table = r.pget>(offsets.armor_slot_count_prob_table_offset); const auto& data = r.pget, 0x64>>(offsets.enemy_meseta_ranges_offset); for (size_t z = 0; z < data.size(); z++) { this->enemy_meseta_ranges[z] = Range{data[z].min, data[z].max}; } this->enemy_type_drop_probs = r.pget>(offsets.enemy_type_drop_probs_offset); this->enemy_item_classes = r.pget>(offsets.enemy_item_classes_offset); { const auto& data = r.pget, 0x0A>>(offsets.box_meseta_ranges_offset); for (size_t z = 0; z < data.size(); z++) { this->box_meseta_ranges[z] = Range{data[z].min, data[z].max}; } } this->has_rare_bonus_value_prob_table = is_v3; if (!this->has_rare_bonus_value_prob_table) { // V2 const auto& data = r.pget, 0x17>>(offsets.bonus_value_prob_table_offset); for (size_t z = 0; z < data.size(); z++) { for (size_t x = 0; x < data[z].size(); x++) { this->bonus_value_prob_table[z][x] = data[z][x]; } } } else { // V3 const auto& data = r.pget, 0x17>>(offsets.bonus_value_prob_table_offset); for (size_t z = 0; z < data.size(); z++) { for (size_t x = 0; x < data[z].size(); x++) { this->bonus_value_prob_table[z][x] = data[z][x]; } } } this->nonrare_bonus_prob_spec = r.pget, 3>>(offsets.nonrare_bonus_prob_spec_offset); this->bonus_type_prob_table = r.pget, 6>>(offsets.bonus_type_prob_table_offset); this->special_mult = r.pget>(offsets.special_mult_offset); this->special_percent = r.pget>(offsets.special_percent_offset); { const auto& data = r.pget, 0x1C>>(offsets.tool_class_prob_table_offset); for (size_t z = 0; z < data.size(); z++) { for (size_t x = 0; x < data[z].size(); x++) { this->tool_class_prob_table[z][x] = data[z][x]; } } } this->technique_index_prob_table = r.pget, 0x13>>(offsets.technique_index_prob_table_offset); this->technique_level_ranges = r.pget, 0x0A>, 0x13>>(offsets.technique_level_ranges_offset); this->armor_or_shield_type_bias = offsets.armor_or_shield_type_bias; this->unit_max_stars_table = r.pget>(offsets.unit_max_stars_offset); this->box_item_class_prob_table = r.pget, 7>>(offsets.box_item_class_prob_table_offset); } uint16_t CommonItemSet::key_for_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) { // Bits: -----EEEMMDDSSSS return (((static_cast(episode) << 8) & 0x0700) | ((static_cast(mode) << 6) & 0x00C0) | ((static_cast(difficulty) << 4) & 0x0030) | (static_cast(secid) & 0x000F)); } shared_ptr CommonItemSet::get_table( Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const { try { return this->tables.at(this->key_for_table(episode, mode, difficulty, secid)); } catch (const out_of_range&) { throw runtime_error(string_printf("common item table not available for episode=%s, mode=%s, difficulty=%hu, secid=%hu", name_for_episode(episode), name_for_mode(mode), difficulty, secid)); } } AFSV2CommonItemSet::AFSV2CommonItemSet( std::shared_ptr pt_afs_data, std::shared_ptr ct_afs_data) { // ItemPT.afs has 40 entries; the first 10 are for Normal, then Hard, etc. AFSArchive pt_afs(pt_afs_data); for (size_t difficulty = 0; difficulty < 4; difficulty++) { for (size_t section_id = 0; section_id < 10; section_id++) { auto entry = pt_afs.get(difficulty * 10 + section_id); StringReader r(entry.first, entry.second); auto table = make_shared(r, false, false, Episode::EP1); this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::NORMAL, difficulty, section_id), table); this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::BATTLE, difficulty, section_id), table); this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::SOLO, difficulty, section_id), table); } } // ItemCT.afs also has 40 entries, but only the 0th, 10th, 20th, and 30th are // used (section_id is ignored) AFSArchive ct_afs(ct_afs_data); for (size_t difficulty = 0; difficulty < 4; difficulty++) { auto r = ct_afs.get_reader(difficulty * 10); auto table = make_shared
(r, false, false, Episode::EP1); for (size_t section_id = 0; section_id < 10; section_id++) { this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::CHALLENGE, difficulty, section_id), table); } } } GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr gsl_data, bool is_big_endian) { GSLArchive gsl(gsl_data, is_big_endian); auto filename_for_table = +[](Episode episode, uint8_t difficulty, uint8_t section_id, bool is_challenge) -> string { const char* episode_token = ""; switch (episode) { case Episode::EP1: episode_token = ""; break; case Episode::EP2: episode_token = "l"; break; case Episode::EP4: episode_token = "s"; break; default: throw runtime_error("invalid episode"); } return string_printf( "ItemPT%s%s%c%1hhu.rel", is_challenge ? "c" : "", episode_token, tolower(abbreviation_for_difficulty(difficulty)), section_id); }; vector episodes = {Episode::EP1, Episode::EP2, Episode::EP4}; for (Episode episode : episodes) { for (size_t difficulty = 0; difficulty < 4; difficulty++) { for (size_t section_id = 0; section_id < 10; section_id++) { StringReader r; try { r = gsl.get_reader(filename_for_table(episode, difficulty, section_id, false)); } catch (const exception&) { // Fall back to Episode 1 if Episode 4 data is missing if (episode == Episode::EP4) { auto ep1_table = this->tables.at(this->key_for_table(Episode::EP1, GameMode::NORMAL, difficulty, section_id)); this->tables.emplace(this->key_for_table(episode, GameMode::NORMAL, difficulty, section_id), ep1_table); this->tables.emplace(this->key_for_table(episode, GameMode::BATTLE, difficulty, section_id), ep1_table); this->tables.emplace(this->key_for_table(episode, GameMode::SOLO, difficulty, section_id), ep1_table); continue; } else { throw; } } auto table = make_shared
(r, is_big_endian, true, episode); this->tables.emplace(this->key_for_table(episode, GameMode::NORMAL, difficulty, section_id), table); this->tables.emplace(this->key_for_table(episode, GameMode::BATTLE, difficulty, section_id), table); this->tables.emplace(this->key_for_table(episode, GameMode::SOLO, difficulty, section_id), table); } } if (episode != Episode::EP4) { for (size_t difficulty = 0; difficulty < 4; difficulty++) { auto r = gsl.get_reader(filename_for_table(episode, difficulty, 0, true)); auto table = make_shared
(r, is_big_endian, true, episode); for (size_t section_id = 0; section_id < 10; section_id++) { this->tables.emplace(this->key_for_table(episode, GameMode::CHALLENGE, difficulty, section_id), table); } } } } } JSONCommonItemSet::JSONCommonItemSet(const JSON& json) { for (const auto& mode_it : json.as_dict()) { static const unordered_map mode_keys( {{"Normal", GameMode::NORMAL}, {"Battle", GameMode::BATTLE}, {"Challenge", GameMode::CHALLENGE}, {"Solo", GameMode::SOLO}}); GameMode mode = mode_keys.at(mode_it.first); for (const auto& episode_it : mode_it.second->as_dict()) { static const unordered_map episode_keys( {{"Episode1", Episode::EP1}, {"Episode2", Episode::EP2}, {"Episode4", Episode::EP4}}); Episode episode = episode_keys.at(episode_it.first); for (const auto& difficulty_it : episode_it.second->as_dict()) { static const unordered_map difficulty_keys( {{"Normal", 0}, {"Hard", 1}, {"VeryHard", 2}, {"Ultimate", 3}}); uint8_t difficulty = difficulty_keys.at(difficulty_it.first); for (const auto& section_id_it : difficulty_it.second->as_dict()) { uint8_t section_id = section_id_for_name(section_id_it.first); this->tables.emplace( this->key_for_table(episode, mode, difficulty, section_id), make_shared
(*section_id_it.second, episode)); } } } } } RELFileSet::RELFileSet(std::shared_ptr data) : data(data), r(*this->data) {} ArmorRandomSet::ArmorRandomSet(std::shared_ptr data) : RELFileSet(data) { // For some reason the footer tables are doubly indirect in this file uint32_t specs_offset_offset = this->r.pget_u32b(data->size() - 0x10); uint32_t specs_offset = this->r.pget_u32b(specs_offset_offset); this->tables = &this->r.pget>(specs_offset); } std::pair ArmorRandomSet::get_armor_table(size_t index) const { return this->get_table(this->tables->at(0), index); } std::pair ArmorRandomSet::get_shield_table(size_t index) const { return this->get_table(this->tables->at(1), index); } std::pair ArmorRandomSet::get_unit_table(size_t index) const { return this->get_table(this->tables->at(2), index); } ToolRandomSet::ToolRandomSet(std::shared_ptr data) : RELFileSet(data) { uint32_t specs_offset = r.pget_u32b(data->size() - 0x10); this->common_recovery_table_spec = &r.pget(r.pget_u32b( specs_offset)); this->rare_recovery_table_spec = &r.pget( r.pget_u32b(specs_offset + sizeof(uint32_t)), 2 * sizeof(TableSpec)); this->tech_disk_table_spec = this->rare_recovery_table_spec + 1; this->tech_disk_level_table_spec = &r.pget(r.pget_u32b( specs_offset + 2 * sizeof(uint32_t))); } pair ToolRandomSet::get_common_recovery_table( size_t index) const { return this->get_table(*this->common_recovery_table_spec, index); } pair ToolRandomSet::get_rare_recovery_table(size_t index) const { return this->get_table(*this->rare_recovery_table_spec, index); } pair ToolRandomSet::get_tech_disk_table(size_t index) const { return this->get_table(*this->tech_disk_table_spec, index); } pair ToolRandomSet::get_tech_disk_level_table(size_t index) const { return this->get_table(*this->tech_disk_level_table_spec, index); } WeaponRandomSet::WeaponRandomSet(std::shared_ptr data) : RELFileSet(data) { uint32_t offsets_offset = this->r.pget_u32b(data->size() - 0x10); this->offsets = &this->r.pget(offsets_offset); } std::pair WeaponRandomSet::get_weapon_type_table(size_t index) const { const auto& spec = this->r.pget( this->offsets->weapon_type_table + index * sizeof(TableSpec)); const auto* data = &this->r.pget( spec.offset, spec.entries_per_table * sizeof(WeightTableEntry8)); return make_pair(data, spec.entries_per_table); } const parray* WeaponRandomSet::get_bonus_type_table(size_t which, size_t index) const { uint32_t base_offset = which ? this->offsets->bonus_type_table2 : this->offsets->bonus_type_table1; return &this->r.pget>( base_offset + sizeof(parray) * index); } const WeaponRandomSet::RangeTableEntry* WeaponRandomSet::get_bonus_range(size_t which, size_t index) const { uint32_t base_offset = which ? this->offsets->bonus_range_table2 : this->offsets->bonus_range_table1; return &this->r.pget( base_offset + sizeof(RangeTableEntry) * index); } const parray* WeaponRandomSet::get_special_mode_table(size_t index) const { return &this->r.pget>( this->offsets->special_mode_table + sizeof(parray) * index); } const WeaponRandomSet::RangeTableEntry* WeaponRandomSet::get_standard_grind_range(size_t index) const { return &this->r.pget( this->offsets->standard_grind_range_table + sizeof(RangeTableEntry) * index); } const WeaponRandomSet::RangeTableEntry* WeaponRandomSet::get_favored_grind_range(size_t index) const { return &this->r.pget( this->offsets->favored_grind_range_table + sizeof(RangeTableEntry) * index); } TekkerAdjustmentSet::TekkerAdjustmentSet(std::shared_ptr data) : data(data), r(*data) { this->offsets = &this->r.pget(this->r.pget_u32b(this->r.size() - 0x10)); } const ProbabilityTable& TekkerAdjustmentSet::get_table( std::array, 10>& tables_default, std::array, 10>& tables_favored, uint32_t offset_and_count_offset, bool favored, uint8_t section_id) const { if (section_id >= 10) { throw runtime_error("invalid section ID"); } ProbabilityTable& table = favored ? tables_favored[section_id] : tables_default[section_id]; if (table.count == 0) { uint32_t offset = r.pget_u32b(offset_and_count_offset); uint32_t count_per_section_id = r.pget_u32b(offset_and_count_offset + 4); auto* entries = &r.pget(offset, sizeof(DeltaProbabilityEntry) * count_per_section_id * 10); for (size_t z = count_per_section_id * section_id; z < count_per_section_id * (section_id + 1); z++) { size_t count = favored ? entries[z].count_favored : entries[z].count_default; for (size_t w = 0; w < count; w++) { table.push(entries[z].delta_index); } } } return table; } const ProbabilityTable& TekkerAdjustmentSet::get_special_upgrade_prob_table(uint8_t section_id, bool favored) const { return this->get_table( this->special_upgrade_prob_tables_default, this->special_upgrade_prob_tables_favored, this->offsets->special_upgrade_prob_table_offset, favored, section_id); } const ProbabilityTable& TekkerAdjustmentSet::get_grind_delta_prob_table(uint8_t section_id, bool favored) const { return this->get_table( this->grind_delta_prob_tables_default, this->grind_delta_prob_tables_favored, this->offsets->grind_delta_prob_table_offset, favored, section_id); } const ProbabilityTable& TekkerAdjustmentSet::get_bonus_delta_prob_table(uint8_t section_id, bool favored) const { return this->get_table( this->bonus_delta_prob_tables_default, this->bonus_delta_prob_tables_favored, this->offsets->bonus_delta_prob_table_offset, favored, section_id); } int8_t TekkerAdjustmentSet::get_luck(uint32_t start_offset, uint8_t delta_index) const { StringReader sub_r = r.sub(start_offset); while (!sub_r.eof()) { const auto& entry = sub_r.get(); if (entry.delta_index == 0xFF) { return 0; } else if (entry.delta_index == delta_index) { return entry.luck; } } return 0; } int8_t TekkerAdjustmentSet::get_luck_for_special_upgrade(uint8_t delta_index) const { return this->get_luck(this->offsets->special_upgrade_luck_table_offset, delta_index); } int8_t TekkerAdjustmentSet::get_luck_for_grind_delta(uint8_t delta_index) const { return this->get_luck(this->offsets->grind_delta_luck_table_offset, delta_index); } int8_t TekkerAdjustmentSet::get_luck_for_bonus_delta(uint8_t delta_index) const { return this->get_luck(this->offsets->bonus_delta_luck_offset, delta_index); }