#include "LevelTable.hh" #include #include #include "CommonFileFormats.hh" #include "Compression.hh" #include "PSOEncryption.hh" using namespace std; void LevelTable::reset_to_base(PlayerStats& stats, uint8_t char_class) const { stats.level = 0; stats.experience = 0; stats.char_stats = this->base_stats_for_class(char_class); } void LevelTable::advance_to_level(PlayerStats& stats, uint32_t level, uint8_t char_class) const { for (; stats.level < level; stats.level++) { const auto& level_stats = this->stats_delta_for_level(char_class, stats.level + 1); // The original code clamps the resulting stat values to [0, max_stat]; we // don't have max_stat handy so we just allow them to be unbounded stats.char_stats.atp += level_stats.atp; stats.char_stats.mst += level_stats.mst; stats.char_stats.evp += level_stats.evp; stats.char_stats.hp += level_stats.hp; stats.char_stats.dfp += level_stats.dfp; stats.char_stats.ata += level_stats.ata; // Note: It is not a bug that lck is ignored here; the original code // ignores it too. stats.experience = level_stats.experience; } } LevelTableV2::LevelTableV2(const string& data, bool compressed) { struct Offsets { // TODO: The overall format of this file on V2 has much more data than we // actually use. What's known of the structure so far: le_uint32_t level_deltas; // (5468) -> u32[9] -> LevelStatsDelta[200] le_uint32_t unknown_a1; // (548C) -> float[6] le_uint32_t max_stats; // (54A4) -> PlayerStats[9] le_uint32_t level_100_stats; // (55E8) -> PlayerStats[9] le_uint32_t base_stats; // (57AC) -> u32[9] -> CharacterStats le_uint32_t unknown_a2; // (57D0) -> (0x120 zero bytes) le_uint32_t attack_data; // (58F0) -> AttackData[9] le_uint32_t unknown_a4; // (5AA0) -> parray, 9> le_uint32_t unknown_a5; // (5B54) -> float[9] le_uint32_t unknown_a6; // (5B78) -> (0x30 bytes) le_uint32_t unknown_a7; // (5BA8) -> (0x2D bytes) le_uint32_t unknown_a8; // (5E00) -> u32[3] -> float[0x2D] le_uint32_t unknown_a9; // (5DF4) -> (0x90 bytes) le_uint32_t unknown_a10; // (60D0) -> u32[3] -> (0x10-byte struct)[0x0C] le_uint32_t unknown_a11; // (616C) -> u32[3] -> (0x30-bytes) le_uint32_t unknown_a12; // (64FC) -> u32[3] -> (0x14-byte struct)[0x0F] } __packed_ws__(Offsets, 0x40); phosg::StringReader r; string decompressed_data; if (compressed) { decompressed_data = prs_decompress(data); r = phosg::StringReader(decompressed_data); } else { r = phosg::StringReader(data); } const auto& footer = r.pget(r.size() - sizeof(RELFileFooter)); const auto& offsets = r.pget(footer.root_offset); const auto& level_deltas_offsets = r.pget>(offsets.level_deltas); const auto& base_stats_offsets = r.pget>(offsets.base_stats); for (size_t char_class = 0; char_class < 9; char_class++) { const auto& src_level_deltas = r.pget>(level_deltas_offsets[char_class]); for (size_t level = 0; level < 200; level++) { this->level_deltas[char_class][level] = src_level_deltas[level]; } this->max_stats[char_class] = r.pget(offsets.max_stats + char_class * sizeof(PlayerStats)); this->level_100_stats[char_class] = r.pget(offsets.level_100_stats + char_class * sizeof(PlayerStats)); this->base_stats[char_class] = r.pget(base_stats_offsets[char_class]); } } const CharacterStats& LevelTableV2::base_stats_for_class(uint8_t char_class) const { return this->base_stats.at(char_class); } const PlayerStats& LevelTableV2::level_100_stats_for_class(uint8_t char_class) const { return this->level_100_stats.at(char_class); } const PlayerStats& LevelTableV2::max_stats_for_class(uint8_t char_class) const { return this->max_stats.at(char_class); } const LevelStatsDelta& LevelTableV2::stats_delta_for_level(uint8_t char_class, uint8_t level) const { return this->level_deltas.at(char_class).at(level); } LevelTableV3BE::LevelTableV3BE(const string& data, bool encrypted) { phosg::StringReader r; string decompressed_data; if (encrypted) { auto decrypted = decrypt_pr2_data(data); decompressed_data = prs_decompress(decrypted.compressed_data); if (decompressed_data.size() != decrypted.decompressed_size) { throw runtime_error("decompressed data size does not match expected size"); } r = phosg::StringReader(decompressed_data); } else { r = phosg::StringReader(data); } // The GC format is very simple (but everything is big-endian): // root: // u32 offset: // u32[12] offsets: // LevelStatsDeltaBE[200] level_deltas const auto& footer = r.pget(r.size() - sizeof(RELFileFooterBE)); const auto& offsets = r.pget>(r.pget_u32b(footer.root_offset)); for (size_t char_class = 0; char_class < 12; char_class++) { const auto& src_deltas = r.pget>(offsets[char_class]); for (size_t level = 0; level < 200; level++) { const auto& src_delta = src_deltas[level]; auto& dest_delta = this->level_deltas[char_class][level]; dest_delta.atp = src_delta.atp; dest_delta.mst = src_delta.mst; dest_delta.evp = src_delta.evp; dest_delta.hp = src_delta.hp; dest_delta.dfp = src_delta.dfp; dest_delta.ata = src_delta.ata; dest_delta.lck = src_delta.lck; dest_delta.tp = src_delta.tp; dest_delta.experience = src_delta.experience; } } } const CharacterStats& LevelTableV3BE::base_stats_for_class(uint8_t char_class) const { static const array data = { // ATP MST EVP HP DFP ATA LCK CharacterStats{0x0023, 0x001D, 0x002D, 0x0014, 0x0011, 0x001E, 0x000A}, CharacterStats{0x001E, 0x0028, 0x003C, 0x0013, 0x0016, 0x0019, 0x000A}, CharacterStats{0x0023, 0x0000, 0x0023, 0x0016, 0x0012, 0x0023, 0x000A}, CharacterStats{0x0012, 0x0014, 0x0024, 0x0010, 0x000D, 0x0028, 0x000A}, CharacterStats{0x0019, 0x0000, 0x001F, 0x0012, 0x0012, 0x002D, 0x000A}, CharacterStats{0x0014, 0x0000, 0x001F, 0x0011, 0x0017, 0x002D, 0x000A}, CharacterStats{0x000D, 0x0035, 0x0023, 0x0014, 0x000A, 0x000F, 0x000A}, CharacterStats{0x000D, 0x003C, 0x0032, 0x0013, 0x0007, 0x000C, 0x000A}, CharacterStats{0x000A, 0x003A, 0x0035, 0x0013, 0x000D, 0x000A, 0x000A}, CharacterStats{0x0023, 0x0000, 0x0023, 0x0016, 0x0012, 0x0023, 0x000A}, CharacterStats{0x000D, 0x0035, 0x0023, 0x0014, 0x000A, 0x000F, 0x000A}, CharacterStats{0x0012, 0x0014, 0x0024, 0x0010, 0x000D, 0x0028, 0x000A}, }; return data.at(char_class); } static const array max_stats_v3_v4 = { // ATP MST EVP HP DFP ATA LCK ESP PRX PRY L E M PlayerStats{{0x056B, 0x02DC, 0x02F4, 0x0265, 0x0243, 0x054B, 0x0064}, 0x0064, 0.0f, 0.0f, 0, 0, 0}, PlayerStats{{0x04CB, 0x0499, 0x032B, 0x0254, 0x024D, 0x056C, 0x0064}, 0x0064, 0.0f, 0.0f, 0, 0, 0}, PlayerStats{{0x065D, 0x0000, 0x0294, 0x0379, 0x0259, 0x0514, 0x0064}, 0x0064, 0.0f, 0.0f, 0, 0, 0}, PlayerStats{{0x04E7, 0x0299, 0x02CB, 0x02D5, 0x0203, 0x06CB, 0x0064}, 0x0064, 0.0f, 0.0f, 0, 0, 0}, PlayerStats{{0x0541, 0x0000, 0x02BB, 0x0430, 0x025E, 0x05FA, 0x0064}, 0x0064, 0.0f, 0.0f, 0, 0, 0}, PlayerStats{{0x0492, 0x0000, 0x0313, 0x0439, 0x02B0, 0x062C, 0x0064}, 0x0064, 0.0f, 0.0f, 0, 0, 0}, PlayerStats{{0x0365, 0x0504, 0x024C, 0x0302, 0x01F2, 0x0440, 0x0064}, 0x0064, 0.0f, 0.0f, 0, 0, 0}, PlayerStats{{0x032B, 0x05DC, 0x02A7, 0x02E3, 0x01CF, 0x04B0, 0x0064}, 0x0064, 0.0f, 0.0f, 0, 0, 0}, PlayerStats{{0x0244, 0x06D6, 0x0373, 0x02BE, 0x0186, 0x04EC, 0x0064}, 0x0064, 0.0f, 0.0f, 0, 0, 0}, PlayerStats{{0x050B, 0x0000, 0x036D, 0x02EE, 0x020D, 0x05DC, 0x0064}, 0x0064, 0.0f, 0.0f, 0, 0, 0}, PlayerStats{{0x03E7, 0x053C, 0x028B, 0x02CC, 0x01D6, 0x03F2, 0x0064}, 0x0064, 0.0f, 0.0f, 0, 0, 0}, PlayerStats{{0x0474, 0x0407, 0x0384, 0x02CF, 0x0241, 0x06C2, 0x0064}, 0x0064, 0.0f, 0.0f, 0, 0, 0}, }; const PlayerStats& LevelTableV3BE::max_stats_for_class(uint8_t char_class) const { return max_stats_v3_v4.at(char_class); } const LevelStatsDelta& LevelTableV3BE::stats_delta_for_level(uint8_t char_class, uint8_t level) const { return this->level_deltas.at(char_class).at(level); } LevelTableV4::LevelTableV4(const string& data, bool compressed) { struct Offsets { le_uint32_t base_stats; // -> u32[12] -> CharacterStats le_uint32_t level_deltas; // -> u32[12] -> LevelStatsDelta[200] } __packed_ws__(Offsets, 8); phosg::StringReader r; string decompressed_data; if (compressed) { decompressed_data = prs_decompress(data); r = phosg::StringReader(decompressed_data); } else { r = phosg::StringReader(data); } const auto& footer = r.pget(r.size() - sizeof(RELFileFooter)); const auto& offsets = r.pget(footer.root_offset); const auto& level_deltas_offsets = r.pget>(offsets.level_deltas); const auto& base_stats_offsets = r.pget>(offsets.base_stats); for (size_t char_class = 0; char_class < 12; char_class++) { const auto& src_level_deltas = r.pget>(level_deltas_offsets[char_class]); for (size_t level = 0; level < 200; level++) { this->level_deltas[char_class][level] = src_level_deltas[level]; } this->base_stats[char_class] = r.pget(base_stats_offsets[char_class]); } } const CharacterStats& LevelTableV4::base_stats_for_class(uint8_t char_class) const { return this->base_stats.at(char_class); } const PlayerStats& LevelTableV4::max_stats_for_class(uint8_t char_class) const { return max_stats_v3_v4.at(char_class); } const LevelStatsDelta& LevelTableV4::stats_delta_for_level(uint8_t char_class, uint8_t level) const { return this->level_deltas.at(char_class).at(level); }