add level table JSON format

This commit is contained in:
Martin Michelsen
2026-05-09 13:36:33 -07:00
parent 9915422ae6
commit 7ce3ce5b65
46 changed files with 7462 additions and 398 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ void BattleParamsIndex::Table::print(FILE* stream, Episode episode) const {
phosg::fwrite_fmt(stream,
"{:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5} {}",
e.char_stats.atp, e.char_stats.mst, e.char_stats.evp, e.char_stats.hp, e.char_stats.dfp, e.char_stats.ata,
e.char_stats.lck, e.esp, e.experience, e.meseta, names_str);
e.char_stats.lck, e.esp, e.exp, e.meseta, names_str);
fputc('\n', stream);
}
}
+2 -2
View File
@@ -540,7 +540,7 @@ static asio::awaitable<void> server_command_bbchar_savechar(const Args& a, bool
bb_player->disp.stats.char_stats.dfp += bb_player->get_material_usage(PSOBBCharacterFile::MaterialType::DEF) * 2;
bb_player->disp.stats.char_stats.lck += bb_player->get_material_usage(PSOBBCharacterFile::MaterialType::LUCK) * 2;
bb_player->disp.stats.char_stats.hp += bb_player->get_material_usage(PSOBBCharacterFile::MaterialType::HP) * 2;
bb_player->disp.stats.experience = ch.character->disp.stats.experience;
bb_player->disp.stats.exp = ch.character->disp.stats.exp;
bb_player->disp.stats.meseta = ch.character->disp.stats.meseta;
}
bb_player->disp.technique_levels_v1 = ch.character->disp.technique_levels_v1;
@@ -915,7 +915,7 @@ ChatCommandDefinition cc_edit(
} else if (tokens.at(0) == "meseta" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
p->disp.stats.meseta = stoul(tokens.at(1));
} else if (tokens.at(0) == "exp" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
p->disp.stats.experience = stoul(tokens.at(1));
p->disp.stats.exp = stoul(tokens.at(1));
} else if (tokens.at(0) == "level" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
p->disp.stats.level = stoul(tokens.at(1)) - 1;
p->recompute_stats(s->level_table(a.c->version()), true);
+1 -1
View File
@@ -703,7 +703,7 @@ void Client::create_challenge_overlay(
overlay->disp.visual.char_class, overlay->disp.stats.level);
overlay->disp.stats.esp = 40;
overlay->disp.stats.attack_range = 10.0;
overlay->disp.stats.experience = stats_delta.experience;
overlay->disp.stats.exp = stats_delta.exp;
overlay->disp.stats.meseta = 0;
overlay->clear_all_material_usage();
for (size_t z = 0; z < 0x13; z++) {
+3 -1
View File
@@ -4269,7 +4269,9 @@ struct G_Attack_6x43_6x44_6x45 {
le_uint16_t unknown_a2 = 0;
} __packed_ws__(G_Attack_6x43_6x44_6x45, 8);
// 6x46: Attack finished (sent after each of 43, 44, and 45) (protected on GC NTE/V3/V4)
// 6x46: Set attack strike targets (sent after each of 43, 44, and 45) (protected on GC NTE/V3/V4)
// This command sets the targets of each strike of an attack (e.g. each pair of mechgun bullets, or each swing of a
// pair of daggers). For multi-strike attacks, this is sent multiple times.
// The number of targets is not bounds-checked during byteswapping on GC clients. The client only expects up to 10
// entries here, so if the number of targets is too large, the client will byteswap the function's return address on
// the stack, and it will crash.
+76
View File
@@ -197,3 +197,79 @@ size_t get_rel_array_count(const std::set<uint32_t>& offsets, size_t start_offse
}
return (*it - start_offset) / sizeof(T);
}
template <bool BE>
class RELFileWriter {
public:
RELFileWriter() = default;
~RELFileWriter() = default;
template <typename T>
uint32_t put(const T& obj) {
uint32_t ret = this->w.size();
this->w.put<T>(obj);
return ret;
}
uint32_t write(const void* data, size_t size) {
uint32_t ret = this->w.size();
this->w.write(data, size);
return ret;
}
uint32_t write(const std::string& data) {
uint32_t ret = this->w.size();
this->w.write(data);
return ret;
}
uint32_t write_offset(uint32_t value) {
uint32_t ret = this->w.size();
this->relocations.emplace(ret);
this->w.put<U32T<BE>>(value);
return ret;
}
uint32_t write_ref(const ArrayRefT<BE>& ref) {
uint32_t ret = this->w.size();
this->w.put<ArrayRefT<BE>>(ref);
this->relocations.emplace(ret + offsetof(ArrayRefT<BE>, offset));
return ret;
}
void align(size_t alignment) {
while (this->w.size() & (alignment - 1)) {
this->w.put_u8(0);
}
}
std::string finalize(uint32_t root_offset) {
RELFileFooterT<BE> footer;
footer.root_offset = root_offset;
this->align(0x20);
footer.relocations_offset = this->w.size();
footer.num_relocations = this->relocations.size();
footer.unused1[0] = 1;
uint32_t last_offset = 0;
for (uint32_t reloc_offset : this->relocations) {
if (reloc_offset & 3) {
throw std::logic_error("Relocation is not 4-byte aligned");
}
size_t reloc_value = (reloc_offset - last_offset) >> 2;
if (reloc_value > 0xFFFF) {
throw std::runtime_error("Relocation offset is too far away from previous");
}
this->w.put<U16T<BE>>(reloc_value);
last_offset = reloc_offset;
}
align(0x20);
this->w.put<RELFileFooterT<BE>>(footer);
return std::move(this->w.str());
}
phosg::StringWriter w;
std::set<uint32_t> relocations;
};
+1 -1
View File
@@ -152,7 +152,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
client_json.emplace("DFP", p->disp.stats.char_stats.dfp.load());
client_json.emplace("ATA", p->disp.stats.char_stats.ata.load());
client_json.emplace("LCK", p->disp.stats.char_stats.lck.load());
client_json.emplace("EXP", p->disp.stats.experience.load());
client_json.emplace("EXP", p->disp.stats.exp.load());
client_json.emplace("Meseta", p->disp.stats.meseta.load());
auto tech_levels_json = phosg::JSON::dict();
for (size_t z = 0; z < 0x13; z++) {
+98 -148
View File
@@ -2913,224 +2913,204 @@ public:
}
static std::string serialize(const ItemParameterTable& pmt) {
set<uint32_t> relocations;
RELFileWriter<BE> rel;
RootT root;
phosg::StringWriter w;
if constexpr (!std::is_same_v<HeaderT, EmptyHeader>) {
w.put<HeaderT>(HeaderT());
rel.template put<HeaderT>(HeaderT());
}
auto align = [&w](size_t alignment) -> void {
while (w.size() & (alignment - 1)) {
w.put_u8(0);
}
};
auto write_ref = [&w, &relocations](const ArrayRefT<BE>& ref) -> void {
w.put<ArrayRefT<BE>>(ref);
relocations.emplace(w.size() - 4);
};
if constexpr (requires { root.entry_count; }) {
root.entry_count = 0x13;
}
align(4);
ArrayRefT<BE> shields_ref{pmt.num_armors_or_shields_in_class(2) - HasImplicitPlaceholders, w.size()};
rel.align(4);
ArrayRefT<BE> shields_ref{pmt.num_armors_or_shields_in_class(2) - HasImplicitPlaceholders, rel.w.size()};
for (size_t data1_2 = 0; data1_2 < (shields_ref.count + HasImplicitPlaceholders); data1_2++) {
w.put<ArmorOrShieldT>(pmt.get_armor_or_shield(2, data1_2));
rel.template put<ArmorOrShieldT>(pmt.get_armor_or_shield(2, data1_2));
}
if constexpr (requires { root.shield_stat_boost_index_table; }) {
root.shield_stat_boost_index_table = w.size();
w.write(pmt.get_shield_stat_boost_index_table());
root.shield_stat_boost_index_table = rel.write(pmt.get_shield_stat_boost_index_table());
}
align(4);
ArrayRefT<BE> armors_ref{pmt.num_armors_or_shields_in_class(1) - HasImplicitPlaceholders, w.size()};
rel.align(4);
ArrayRefT<BE> armors_ref{pmt.num_armors_or_shields_in_class(1) - HasImplicitPlaceholders, rel.w.size()};
for (size_t data1_2 = 0; data1_2 < (armors_ref.count + HasImplicitPlaceholders); data1_2++) {
w.put<ArmorOrShieldT>(pmt.get_armor_or_shield(1, data1_2));
rel.template put<ArmorOrShieldT>(pmt.get_armor_or_shield(1, data1_2));
}
if constexpr (requires { root.armor_stat_boost_index_table; }) {
root.armor_stat_boost_index_table = w.size();
w.write(pmt.get_armor_stat_boost_index_table());
root.armor_stat_boost_index_table = rel.write(pmt.get_armor_stat_boost_index_table());
}
align(4);
ArrayRefT<BE> units_ref{pmt.num_units() - HasImplicitPlaceholders, w.size()};
rel.align(4);
ArrayRefT<BE> units_ref{pmt.num_units() - HasImplicitPlaceholders, rel.w.size()};
for (size_t data1_2 = 0; data1_2 < (units_ref.count + HasImplicitPlaceholders); data1_2++) {
w.put<UnitT>(pmt.get_unit(data1_2));
rel.template put<UnitT>(pmt.get_unit(data1_2));
}
align(4);
ArrayRefT<BE> mags_ref{pmt.num_mags() - HasImplicitPlaceholders, w.size()};
rel.align(4);
ArrayRefT<BE> mags_ref{pmt.num_mags() - HasImplicitPlaceholders, rel.w.size()};
for (size_t data1_2 = 0; data1_2 < (mags_ref.count + HasImplicitPlaceholders); data1_2++) {
w.put<MagT>(pmt.get_mag(data1_2));
rel.template put<MagT>(pmt.get_mag(data1_2));
}
align(4);
rel.align(4);
std::vector<ArrayRefT<BE>> tool_refs;
for (size_t data1_1 = 0; data1_1 < pmt.num_tool_classes(); data1_1++) {
auto& ref = tool_refs.emplace_back(ArrayRefT<BE>{pmt.num_tools_in_class(data1_1), w.size()});
auto& ref = tool_refs.emplace_back(ArrayRefT<BE>{pmt.num_tools_in_class(data1_1), rel.w.size()});
for (size_t data1_2 = 0; data1_2 < ref.count; data1_2++) {
w.put<ToolT>(pmt.get_tool(data1_1, data1_2));
rel.template put<ToolT>(pmt.get_tool(data1_1, data1_2));
}
}
align(4);
rel.align(4);
std::vector<ArrayRefT<BE>> weapon_refs;
for (size_t data1_1 = 0; data1_1 < pmt.num_weapon_classes(); data1_1++) {
auto& ref = weapon_refs.emplace_back(ArrayRefT<BE>{pmt.num_weapons_in_class(data1_1), w.size()});
auto& ref = weapon_refs.emplace_back(ArrayRefT<BE>{pmt.num_weapons_in_class(data1_1), rel.w.size()});
for (size_t data1_2 = 0; data1_2 < ref.count; data1_2++) {
w.put<WeaponT>(pmt.get_weapon(data1_1, data1_2));
rel.template put<WeaponT>(pmt.get_weapon(data1_1, data1_2));
}
}
if constexpr (requires { root.weapon_stat_boost_index_table; }) {
root.weapon_stat_boost_index_table = w.size();
w.write(pmt.get_weapon_stat_boost_index_table());
root.weapon_stat_boost_index_table = rel.write(pmt.get_weapon_stat_boost_index_table());
}
align(4);
root.photon_color_table = w.size();
rel.align(4);
root.photon_color_table = rel.w.size();
for (size_t z = 0; z < pmt.num_photon_colors(); z++) {
w.put<PhotonColorEntryT<BE>>(pmt.get_photon_color(z));
rel.template put<PhotonColorEntryT<BE>>(pmt.get_photon_color(z));
}
align(4);
root.weapon_range_table = w.size();
rel.align(4);
root.weapon_range_table = rel.w.size();
for (size_t z = 0; z < pmt.num_weapon_ranges(); z++) {
w.put<WeaponRangeT<BE>>(pmt.get_weapon_range(z));
rel.template put<WeaponRangeT<BE>>(pmt.get_weapon_range(z));
}
root.weapon_kind_table = w.size();
root.weapon_kind_table = rel.w.size();
for (size_t z = 0; z < pmt.num_weapon_classes(); z++) {
w.put_u8(pmt.get_weapon_kind(z));
rel.w.put_u8(pmt.get_weapon_kind(z));
}
if constexpr (requires { root.weapon_integral_sale_divisor_table; }) {
root.weapon_integral_sale_divisor_table = w.size();
root.weapon_integral_sale_divisor_table = rel.w.size();
for (size_t z = 0; z < pmt.num_weapon_classes(); z++) {
w.put_u8(pmt.get_sale_divisor(0, z));
rel.w.put_u8(pmt.get_sale_divisor(0, z));
}
} else {
align(4);
root.weapon_sale_divisor_table = w.size();
rel.align(4);
root.weapon_sale_divisor_table = rel.w.size();
for (size_t z = 0; z < pmt.num_weapon_sale_divisors(); z++) {
w.put<F32T<BE>>(pmt.get_sale_divisor(0, z));
rel.template put<F32T<BE>>(pmt.get_sale_divisor(0, z));
}
}
if constexpr (requires { root.non_weapon_integral_sale_divisor_table; }) {
root.non_weapon_integral_sale_divisor_table = w.size();
NonWeaponSaleDivisorsDCProtos sds;
sds.armor_divisor = pmt.get_sale_divisor(1, 1);
sds.shield_divisor = pmt.get_sale_divisor(1, 2);
sds.unit_divisor = pmt.get_sale_divisor(1, 3);
w.put<NonWeaponSaleDivisorsDCProtos>(sds);
root.non_weapon_integral_sale_divisor_table = rel.template put<NonWeaponSaleDivisorsDCProtos>(sds);
} else {
align(4);
root.non_weapon_sale_divisor_table = w.size();
rel.align(4);
NonWeaponSaleDivisorsT<BE> sds;
sds.armor_divisor = pmt.get_sale_divisor(1, 1);
sds.shield_divisor = pmt.get_sale_divisor(1, 2);
sds.unit_divisor = pmt.get_sale_divisor(1, 3);
sds.mag_divisor = pmt.get_sale_divisor(2, 0);
w.put<NonWeaponSaleDivisorsT<BE>>(sds);
root.non_weapon_sale_divisor_table = rel.template put<NonWeaponSaleDivisorsT<BE>>(sds);
}
MagFeedResultsListOffsetsT<BE> mag_feed_result_offsets;
for (size_t table_index = 0; table_index < 8; table_index++) {
mag_feed_result_offsets[table_index] = w.size();
mag_feed_result_offsets[table_index] = rel.w.size();
for (size_t item_id = 0; item_id < 11; item_id++) {
w.put<MagFeedResult>(pmt.get_mag_feed_result(table_index, item_id));
rel.template put<MagFeedResult>(pmt.get_mag_feed_result(table_index, item_id));
}
}
root.star_value_table = w.size();
w.write(pmt.get_star_value_table());
root.star_value_table = rel.write(pmt.get_star_value_table());
if constexpr (requires { root.unknown_a1; }) {
align(2);
root.unknown_a1 = w.size();
w.write(pmt.get_unknown_a1());
rel.align(2);
root.unknown_a1 = rel.write(pmt.get_unknown_a1());
}
align(2);
root.special_table = w.size();
rel.align(2);
root.special_table = rel.w.size();
for (size_t z = 0; z < pmt.num_specials(); z++) {
w.put<SpecialT<BE>>(pmt.get_special(z));
rel.template put<SpecialT<BE>>(pmt.get_special(z));
}
align(4);
root.weapon_effect_table = w.size();
rel.align(4);
root.weapon_effect_table = rel.w.size();
for (size_t z = 0; z < pmt.num_weapon_effects(); z++) {
w.put<WeaponEffectT<BE>>(pmt.get_weapon_effect(z));
rel.template put<WeaponEffectT<BE>>(pmt.get_weapon_effect(z));
}
align(4);
rel.align(4);
if constexpr (requires { root.shield_effect_table; }) {
root.shield_effect_table = w.size();
root.shield_effect_table = rel.w.size();
for (size_t z = 0; z < pmt.num_shield_effects(); z++) {
w.put<ShieldEffectT<BE>>(pmt.get_shield_effect(z));
rel.template put<ShieldEffectT<BE>>(pmt.get_shield_effect(z));
}
}
align(4);
rel.align(4);
if constexpr (requires { root.sound_remap_table; }) {
std::vector<SoundRemapTableOffsetsT<BE>> remap_refs;
const auto& remaps = pmt.get_all_sound_remaps();
for (const auto& remap : remaps) {
auto& remap_ref = remap_refs.emplace_back();
remap_ref.sound_id = remap.sound_id;
remap_ref.remaps_for_rt_index_table = w.size();
remap_ref.remaps_for_rt_index_table = rel.w.size();
for (uint32_t remap_sound_id : remap.by_rt_index) {
w.put<U32T<BE>>(remap_sound_id);
rel.template put<U32T<BE>>(remap_sound_id);
}
remap_ref.remaps_for_char_class_table = w.size();
remap_ref.remaps_for_char_class_table = rel.w.size();
for (uint32_t remap_sound_id : remap.by_char_class) {
w.put<U32T<BE>>(remap_sound_id);
rel.template put<U32T<BE>>(remap_sound_id);
}
}
ArrayRefT<BE> remap_vec{remaps.size(), w.size()};
ArrayRefT<BE> remap_vec{remaps.size(), rel.w.size()};
for (const auto& remap_ref : remap_refs) {
w.put<SoundRemapTableOffsetsT<BE>>(remap_ref);
relocations.emplace(w.size() - 8);
relocations.emplace(w.size() - 4);
uint32_t offset = rel.template put<SoundRemapTableOffsetsT<BE>>(remap_ref);
rel.relocations.emplace(offset + 4);
rel.relocations.emplace(offset + 8);
}
root.sound_remap_table = w.size();
write_ref(remap_vec);
root.sound_remap_table = rel.write_ref(remap_vec);
}
align(4);
root.stat_boost_table = w.size();
rel.align(4);
root.stat_boost_table = rel.w.size();
for (size_t z = 0; z < pmt.num_stat_boosts(); z++) {
w.put<StatBoostT<BE>>(pmt.get_stat_boost(z));
rel.template put<StatBoostT<BE>>(pmt.get_stat_boost(z));
}
if constexpr (requires { root.max_tech_level_table; }) {
root.max_tech_level_table = w.size();
MaxTechniqueLevels max_tech_levels;
for (size_t tech_num = 0; tech_num < 0x13; tech_num++) {
for (size_t char_class = 0; char_class < 0x0C; char_class++) {
max_tech_levels[tech_num][char_class] = pmt.get_max_tech_level(char_class, tech_num);
}
}
w.put<MaxTechniqueLevels>(max_tech_levels);
root.max_tech_level_table = rel.template put<MaxTechniqueLevels>(max_tech_levels);
}
ArrayRefT<BE> combination_table_ref;
if constexpr (requires { root.combination_table; }) {
combination_table_ref.offset = w.size();
combination_table_ref.offset = rel.w.size();
combination_table_ref.count = pmt.num_item_combinations();
for (size_t z = 0; z < combination_table_ref.count; z++) {
w.put<ItemCombination>(pmt.get_item_combination(z));
rel.template put<ItemCombination>(pmt.get_item_combination(z));
}
}
if constexpr (requires { root.tech_boost_table; }) {
align(4);
root.tech_boost_table = w.size();
rel.align(4);
root.tech_boost_table = rel.w.size();
for (size_t z = 0; z < pmt.num_tech_boosts(); z++) {
w.put<TechBoostT<BE>>(pmt.get_tech_boost(z));
rel.template put<TechBoostT<BE>>(pmt.get_tech_boost(z));
}
}
@@ -3138,8 +3118,8 @@ public:
if constexpr (requires { root.unwrap_table; }) {
for (size_t event = 0; event < pmt.num_events(); event++) {
auto [event_items, num_items] = pmt.get_event_items(event);
unwrap_table_refs.emplace_back(ArrayRefT<BE>{num_items, w.size()});
w.write(event_items, sizeof(EventItem) * num_items);
unwrap_table_refs.emplace_back(ArrayRefT<BE>{num_items, rel.w.size()});
rel.write(event_items, sizeof(EventItem) * num_items);
}
}
@@ -3147,95 +3127,65 @@ public:
if constexpr (requires { root.unsealable_table; }) {
const auto& items = pmt.all_unsealable_items();
unsealable_table_ref.count = items.size();
unsealable_table_ref.offset = w.size();
unsealable_table_ref.offset = rel.w.size();
for (const auto& item : items) {
UnsealableItem encoded;
u32_to_item_code(encoded.item, item);
w.put<UnsealableItem>(encoded);
rel.template put<UnsealableItem>(encoded);
}
}
ArrayRefT<BE> ranged_specials_ref;
if constexpr (requires { root.ranged_special_table; }) {
ranged_specials_ref.count = pmt.num_ranged_specials();
ranged_specials_ref.offset = w.size();
ranged_specials_ref.offset = rel.w.size();
for (size_t z = 0; z < ranged_specials_ref.count; z++) {
w.put<RangedSpecial>(pmt.get_ranged_special(z));
rel.template put<RangedSpecial>(pmt.get_ranged_special(z));
}
}
align(4);
root.armor_table = w.size();
write_ref(armors_ref);
write_ref(shields_ref);
root.unit_table = w.size();
write_ref(units_ref);
root.mag_table = w.size();
write_ref(mags_ref);
root.tool_table = w.size();
rel.align(4);
root.armor_table = rel.write_ref(armors_ref);
rel.write_ref(shields_ref);
root.unit_table = rel.write_ref(units_ref);
root.mag_table = rel.write_ref(mags_ref);
root.tool_table = rel.w.size();
for (const auto& ref : tool_refs) {
write_ref(ref);
rel.write_ref(ref);
}
root.weapon_table = w.size();
root.weapon_table = rel.w.size();
for (const auto& ref : weapon_refs) {
write_ref(ref);
rel.write_ref(ref);
}
if constexpr (requires { root.combination_table; }) {
root.combination_table = w.size();
write_ref(combination_table_ref);
root.combination_table = rel.write_ref(combination_table_ref);
}
if constexpr (requires { root.unwrap_table; }) {
ArrayRefT<BE> event_ref{unwrap_table_refs.size(), w.size()};
ArrayRefT<BE> event_ref{unwrap_table_refs.size(), rel.w.size()};
for (const auto& ref : unwrap_table_refs) {
write_ref(ref);
rel.write_ref(ref);
}
root.unwrap_table = w.size();
write_ref(event_ref);
root.unwrap_table = rel.write_ref(event_ref);
}
if constexpr (requires { root.unsealable_table; }) {
root.unsealable_table = w.size();
write_ref(unsealable_table_ref);
root.unsealable_table = rel.write_ref(unsealable_table_ref);
}
if constexpr (requires { root.ranged_special_table; }) {
root.ranged_special_table = w.size();
write_ref(ranged_specials_ref);
root.ranged_special_table = rel.write_ref(ranged_specials_ref);
}
root.mag_feed_table = w.size();
w.put<MagFeedResultsListOffsetsT<BE>>(mag_feed_result_offsets);
root.mag_feed_table = rel.template put<MagFeedResultsListOffsetsT<BE>>(mag_feed_result_offsets);
for (size_t z = 1; z <= 8; z++) {
relocations.emplace(w.size() - (z * 4));
rel.relocations.emplace(rel.w.size() - (z * 4));
}
RELFileFooterT<BE> footer;
footer.root_offset = w.size();
w.put<RootT>(root);
uint32_t root_offset = rel.template put<RootT>(root);
constexpr size_t root_field_count = (sizeof(RootT) / 4) - ((requires { root.entry_count; }) ? 1 : 0);
for (size_t z = 1; z <= root_field_count; z++) {
relocations.emplace(w.size() - (z * 4));
rel.relocations.emplace(rel.w.size() - (z * 4));
}
align(0x20);
footer.relocations_offset = w.size();
footer.num_relocations = relocations.size();
footer.unused1[0] = 1;
uint32_t last_offset = 0;
for (uint32_t reloc_offset : relocations) {
if (reloc_offset & 3) {
throw logic_error("Relocation is not 4-byte aligned");
}
size_t reloc_value = (reloc_offset - last_offset) >> 2;
if (reloc_value > 0xFFFF) {
throw runtime_error("Relocation offset is too far away from previous");
}
w.put<U16T<BE>>(reloc_value);
last_offset = reloc_offset;
}
align(0x20);
w.put<RELFileFooterT<BE>>(footer);
return std::move(w.str());
return rel.finalize(root_offset);
}
protected:
+182 -92
View File
@@ -5,14 +5,13 @@
#include <phosg/Filesystem.hh>
#include "CommonFileFormats.hh"
#include "Compression.hh"
#include "PSOEncryption.hh"
#include "StaticGameData.hh"
using namespace std;
void LevelTable::reset_to_base(PlayerStats& stats, uint8_t char_class) const {
stats.level = 0;
stats.experience = 0;
stats.exp = 0;
stats.char_stats = this->base_stats_for_class(char_class);
}
@@ -28,43 +27,111 @@ void LevelTable::advance_to_level(PlayerStats& stats, uint32_t level, uint8_t ch
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;
stats.exp = level_stats.exp;
}
}
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<parray<float, 5>, 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);
phosg::JSON LevelTable::json() const {
auto base_stats_json = phosg::JSON::list();
auto max_stats_json = phosg::JSON::list();
auto level_deltas_json = phosg::JSON::list();
for (size_t char_class = 0; char_class < this->num_char_classes(); char_class++) {
base_stats_json.emplace_back(this->base_stats_for_class(char_class).json());
max_stats_json.emplace_back(this->max_stats_for_class(char_class).json());
auto this_class_level_deltas_json = phosg::JSON::list();
for (size_t level = 0; level < 200; level++) {
this_class_level_deltas_json.emplace_back(this->stats_delta_for_level(char_class, level).json());
}
level_deltas_json.emplace_back(std::move(this_class_level_deltas_json));
}
return phosg::JSON::dict({
{"BaseStats", std::move(base_stats_json)},
{"MaxStats", std::move(max_stats_json)},
{"LevelDeltas", std::move(level_deltas_json)},
});
}
JSONLevelTable::JSONLevelTable(const phosg::JSON& json) {
const auto& base_stats_json = json.at("BaseStats").as_list();
const auto& max_stats_json = json.at("MaxStats").as_list();
const auto& level_deltas_json = json.at("LevelDeltas").as_list();
for (size_t char_class = 0; char_class < base_stats_json.size(); char_class++) {
this->base_stats.emplace_back(CharacterStats::from_json(*base_stats_json.at(char_class)));
this->max_stats.emplace_back(PlayerStats::from_json(*max_stats_json.at(char_class)));
const auto& this_class_level_deltas_json = level_deltas_json.at(char_class)->as_list();
auto& parsed_deltas = this->level_deltas.emplace_back();
for (size_t level = 0; level < 200; level++) {
parsed_deltas[level] = LevelStatsDelta::from_json(*this_class_level_deltas_json.at(level));
}
}
}
size_t JSONLevelTable::num_char_classes() const {
return this->base_stats.size();
}
const CharacterStats& JSONLevelTable::base_stats_for_class(uint8_t char_class) const {
return this->base_stats.at(char_class);
}
const PlayerStats& JSONLevelTable::max_stats_for_class(uint8_t char_class) const {
return this->max_stats.at(char_class);
}
const LevelStatsDelta& JSONLevelTable::stats_delta_for_level(uint8_t char_class, uint8_t level) const {
return this->level_deltas.at(char_class).at(level);
}
LevelTableV2::LevelTableV2(const string& data) {
struct Root {
// The overall format of this file on V2 has much more data than we actually use. This table is sorted by the
// offset in the PlayerTable.prs file; note that the offset fields in this structure do not match that order.
// ## OFFS WHAT -> TARGET
// 0008 level_deltas[0] -> LevelStatsDelta[200] (by level) (the rest follow immediately)
// 00 5468 level_deltas -> u32[9] (by char_class)
// 04 548C hp_tp_factors -> HPTPFactors[3] (by char_class_class; [0] = hunter, [1] = ranger, [2] = force)
// 08 54A4 max_stats -> PlayerStats[9] (by char_class)
// 0C 55E8 level_100_stats -> PlayerStats[9] (by char_class)
// 572C base_stats[0] -> CharacterStats
// 10 57AC base_stats -> u32[9] (by char_class)
// 14 57D0 resist_data -> ResistData[9] (by char_class)
// 18 58F0 attack_data -> AttackData[9] (by char_class)
// 1C 5AA0 unknown_a4 -> float[15][3] (by [???][attack_number])
// 20 5B54 unknown_a5 -> float[3][3] (by [strike_number][attack_number])
// 24 5B78 unknown_a6 -> float[3][3] (by [strike_number][attack_number]) (may be [4][3] in original code; there are 0xC zero bytes after)
// 28 5BA8 unknown_a7 -> uint8_t[15][3] (same indexes as unknown_a4)
// 5BD8 unknown_a9[0] -> UnknownA9[15] (index unknown; appears animation-related)
// 30 5DF4 unknown_a9 -> u32[3] (by char_class_class)
// 2C 5E00 area_sound_configs -> AreaSoundConfig[0x12] (by area)
// 5E90 unknown_a10[0] -> (0x10-byte struct)[0x0C] (the rest follow immediately)
// 34 60D0 unknown_a10 -> u32[3] (by char_class_class)
// 60DC unknown_a11[0] -> WeaponReference[12] (the rest follow immediately)
// 38 616C unknown_a11 -> u32[3] (by char_class_class)
// 6178 unknown_a12[0] -> UnknownA12[15] (the rest follow immediately)
// 3C 64FC unknown_a12 -> u32[3] (by char_class_class)
/* 00 / 5468 * */ le_uint32_t level_deltas;
/* 04 / 548C * */ le_uint32_t hp_tp_factors;
/* 08 / 54A4 * */ le_uint32_t max_stats;
/* 0C / 55E8 * */ le_uint32_t level_100_stats;
/* 10 / 57AC * */ le_uint32_t base_stats;
/* 14 / 57D0 * */ le_uint32_t resist_data;
/* 18 / 58F0 * */ le_uint32_t attack_data;
/* 1C / 5AA0 * */ le_uint32_t unknown_a4;
/* 20 / 5B54 * */ le_uint32_t unknown_a5;
/* 24 / 5B78 * */ le_uint32_t unknown_a6;
/* 28 / 5BA8 * */ le_uint32_t unknown_a7;
/* 2C / 5E00 * */ le_uint32_t area_sound_configs;
/* 30 / 5DF4 * */ le_uint32_t unknown_a9;
/* 34 / 60D0 * */ le_uint32_t unknown_a10;
/* 38 / 616C * */ le_uint32_t unknown_a11;
/* 3C / 64FC * */ le_uint32_t unknown_a12;
} __packed_ws__(Root, 0x40);
phosg::StringReader r(data);
const auto& footer = r.pget<RELFileFooter>(r.size() - sizeof(RELFileFooter));
const auto& offsets = r.pget<Offsets>(footer.root_offset);
const auto& offsets = r.pget<Root>(footer.root_offset);
const auto& level_deltas_offsets = r.pget<parray<le_uint32_t, 9>>(offsets.level_deltas);
const auto& base_stats_offsets = r.pget<parray<le_uint32_t, 9>>(offsets.base_stats);
for (size_t char_class = 0; char_class < 9; char_class++) {
@@ -73,17 +140,16 @@ LevelTableV2::LevelTableV2(const string& data, bool compressed) {
this->level_deltas[char_class][level] = src_level_deltas[level];
}
this->max_stats[char_class] = r.pget<PlayerStats>(offsets.max_stats + char_class * sizeof(PlayerStats));
this->level_100_stats[char_class] = r.pget<PlayerStats>(offsets.level_100_stats + char_class * sizeof(PlayerStats));
this->base_stats[char_class] = r.pget<CharacterStats>(base_stats_offsets[char_class]);
}
}
const CharacterStats& LevelTableV2::base_stats_for_class(uint8_t char_class) const {
return this->base_stats.at(char_class);
size_t LevelTableV2::num_char_classes() const {
return 9;
}
const PlayerStats& LevelTableV2::level_100_stats_for_class(uint8_t char_class) const {
return this->level_100_stats.at(char_class);
const CharacterStats& LevelTableV2::base_stats_for_class(uint8_t char_class) const {
return this->base_stats.at(char_class);
}
const PlayerStats& LevelTableV2::max_stats_for_class(uint8_t char_class) const {
@@ -94,46 +160,11 @@ const LevelStatsDelta& LevelTableV2::stats_delta_for_level(uint8_t char_class, u
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<true>(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<RELFileFooterBE>(r.size() - sizeof(RELFileFooterBE));
const auto& offsets = r.pget<parray<be_uint32_t, 12>>(r.pget_u32b(footer.root_offset));
for (size_t char_class = 0; char_class < 12; char_class++) {
const auto& src_deltas = r.pget<parray<LevelStatsDeltaBE, 200>>(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;
}
}
size_t LevelTableV3::num_char_classes() const {
return 12;
}
const CharacterStats& LevelTableV3BE::base_stats_for_class(uint8_t char_class) const {
const CharacterStats& LevelTableV3::base_stats_for_class(uint8_t char_class) const {
static const array<CharacterStats, 12> data = {
// ATP MST EVP HP DFP ATA LCK
CharacterStats{0x0023, 0x001D, 0x002D, 0x0014, 0x0011, 0x001E, 0x000A},
@@ -168,31 +199,86 @@ static const array<PlayerStats, 12> max_stats_v3_v4 = {
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 {
const PlayerStats& LevelTableV3::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 {
const LevelStatsDelta& LevelTableV3::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);
template <bool BE>
void parse_level_deltas_t(std::array<std::array<LevelStatsDelta, 200>, 12>& deltas, const string& data) {
// The V3 format is very simple:
// root:
// u32 offset:
// u32[12] offsets:
// LevelStatsDeltaBE[200] level_deltas
phosg::StringReader r(data);
const auto& footer = r.pget<RELFileFooterT<BE>>(r.size() - sizeof(RELFileFooterT<BE>));
const auto& offsets = r.pget<parray<U32T<BE>, 12>>(r.pget<U32T<BE>>(footer.root_offset));
for (size_t char_class = 0; char_class < 12; char_class++) {
const auto& src_deltas = r.pget<parray<LevelStatsDeltaT<BE>, 200>>(offsets[char_class]);
for (size_t level = 0; level < 200; level++) {
deltas[char_class][level] = src_deltas[level];
}
}
}
phosg::StringReader r;
string decompressed_data;
if (compressed) {
decompressed_data = prs_decompress(data);
r = phosg::StringReader(decompressed_data);
} else {
r = phosg::StringReader(data);
LevelTableGC::LevelTableGC(const string& data) {
parse_level_deltas_t<true>(this->level_deltas, data);
}
LevelTableXB::LevelTableXB(const string& data) {
parse_level_deltas_t<false>(this->level_deltas, data);
}
struct RootV4 {
le_uint32_t base_stats; // -> u32[12] -> CharacterStats
le_uint32_t level_deltas; // -> u32[12] -> LevelStatsDelta[200]
} __packed_ws__(RootV4, 8);
std::string LevelTable::serialize_binary_v4() const {
RELFileWriter<false> rel;
RootV4 root;
{
std::vector<uint32_t> offsets;
for (size_t char_class = 0; char_class < this->num_char_classes(); char_class++) {
offsets.emplace_back(rel.put<CharacterStats>(this->base_stats_for_class(char_class)));
}
root.base_stats = rel.w.size();
for (uint32_t offset : offsets) {
rel.write_offset(offset);
}
}
{
std::vector<uint32_t> offsets;
for (size_t char_class = 0; char_class < this->num_char_classes(); char_class++) {
offsets.emplace_back(rel.w.size());
for (size_t level = 0; level < 200; level++) {
rel.put<LevelStatsDelta>(this->stats_delta_for_level(char_class, level));
}
}
root.level_deltas = rel.w.size();
for (uint32_t offset : offsets) {
rel.write_offset(offset);
}
}
size_t root_offset = rel.put<RootV4>(root);
rel.relocations.emplace(root_offset);
rel.relocations.emplace(root_offset + 4);
return rel.finalize(root_offset);
}
LevelTableV4::LevelTableV4(const string& data) {
phosg::StringReader r(data);
const auto& footer = r.pget<RELFileFooter>(r.size() - sizeof(RELFileFooter));
const auto& offsets = r.pget<Offsets>(footer.root_offset);
const auto& offsets = r.pget<RootV4>(footer.root_offset);
const auto& level_deltas_offsets = r.pget<parray<le_uint32_t, 12>>(offsets.level_deltas);
const auto& base_stats_offsets = r.pget<parray<le_uint32_t, 12>>(offsets.base_stats);
for (size_t char_class = 0; char_class < 12; char_class++) {
@@ -204,6 +290,10 @@ LevelTableV4::LevelTableV4(const string& data, bool compressed) {
}
}
size_t LevelTableV4::num_char_classes() const {
return 12;
}
const CharacterStats& LevelTableV4::base_stats_for_class(uint8_t char_class) const {
return this->base_stats.at(char_class);
}
+168 -41
View File
@@ -21,17 +21,34 @@ struct CharacterStatsT {
/* 0A */ U16T<BE> ata = 0;
/* 0C */ U16T<BE> lck = 0;
/* 0E */
static CharacterStatsT<BE> from_json(const phosg::JSON& json) {
return CharacterStatsT<BE>{
json.at("ATP").as_int(),
json.at("MST").as_int(),
json.at("EVP").as_int(),
json.at("HP").as_int(),
json.at("DFP").as_int(),
json.at("ATA").as_int(),
json.at("LCK").as_int()};
}
phosg::JSON json() const {
return phosg::JSON::dict({{"ATP", this->atp.load()},
{"MST", this->mst.load()},
{"EVP", this->evp.load()},
{"HP", this->hp.load()},
{"DFP", this->dfp.load()},
{"ATA", this->ata.load()},
{"LCK", this->lck.load()}});
}
operator CharacterStatsT<!BE>() const {
CharacterStatsT<!BE> ret;
ret.atp = this->atp;
ret.mst = this->mst;
ret.evp = this->evp;
ret.hp = this->hp;
ret.dfp = this->dfp;
ret.ata = this->ata;
ret.lck = this->lck;
return ret;
return CharacterStatsT<!BE>{
this->atp.load(),
this->mst.load(),
this->evp.load(),
this->hp.load(),
this->dfp.load(),
this->ata.load(),
this->lck.load()};
}
} __packed_ws_be__(CharacterStatsT, 0x0E);
using CharacterStats = CharacterStatsT<false>;
@@ -44,37 +61,82 @@ struct PlayerStatsT {
/* 10 */ F32T<BE> attack_range = 0.0;
/* 14 */ F32T<BE> knockback_range = 0.0;
/* 18 */ U32T<BE> level = 0; // Qedit specifies this as tech level when used for enemies
/* 1C */ U32T<BE> experience = 0;
/* 1C */ U32T<BE> exp = 0;
/* 20 */ U32T<BE> meseta = 0; // Qedit specifies this as TP when used for enemies
/* 24 */
operator PlayerStatsT<!BE>() const {
PlayerStatsT<!BE> ret;
ret.char_stats = this->char_stats;
ret.esp = this->esp;
ret.attack_range = this->attack_range;
ret.knockback_range = this->knockback_range;
ret.level = this->level;
ret.experience = this->experience;
ret.meseta = this->meseta;
static PlayerStatsT<BE> from_json(const phosg::JSON& json) {
return PlayerStatsT<BE>{
CharacterStatsT<BE>::from_json(json),
json.at("ESP").as_int(),
json.at("AttackRange").as_float(),
json.at("KnockbackRange").as_float(),
json.at("Level").as_int(),
json.at("EXP").as_int(),
json.at("Meseta").as_int()};
}
phosg::JSON json() const {
auto ret = this->char_stats.json();
ret.emplace("ESP", this->esp.load());
ret.emplace("AttackRange", this->attack_range.load());
ret.emplace("KnockbackRange", this->knockback_range.load());
ret.emplace("Level", this->level.load());
ret.emplace("EXP", this->exp.load());
ret.emplace("Meseta", this->meseta.load());
return ret;
}
operator PlayerStatsT<!BE>() const {
return PlayerStatsT<!BE>{
this->char_stats,
this->esp.load(),
this->attack_range.load(),
this->knockback_range.load(),
this->level.load(),
this->exp.load(),
this->meseta.load()};
}
} __packed_ws_be__(PlayerStatsT, 0x24);
using PlayerStats = PlayerStatsT<false>;
using PlayerStatsBE = PlayerStatsT<true>;
template <bool BE>
struct LevelStatsDeltaT {
/* 00 */ uint8_t atp;
/* 01 */ uint8_t mst;
/* 02 */ uint8_t evp;
/* 03 */ uint8_t hp;
/* 04 */ uint8_t dfp;
/* 05 */ uint8_t ata;
/* 06 */ uint8_t lck;
/* 07 */ uint8_t tp;
/* 08 */ U32T<BE> experience;
/* 00 */ uint8_t atp = 0;
/* 01 */ uint8_t mst = 0;
/* 02 */ uint8_t evp = 0;
/* 03 */ uint8_t hp = 0;
/* 04 */ uint8_t dfp = 0;
/* 05 */ uint8_t ata = 0;
/* 06 */ uint8_t lck = 0;
/* 07 */ uint8_t tp = 0;
/* 08 */ U32T<BE> exp = 0;
/* 0C */
static LevelStatsDeltaT<BE> from_json(const phosg::JSON& json) {
return LevelStatsDeltaT<BE>{
static_cast<uint8_t>(json.at("ATP").as_int()),
static_cast<uint8_t>(json.at("MST").as_int()),
static_cast<uint8_t>(json.at("EVP").as_int()),
static_cast<uint8_t>(json.at("HP").as_int()),
static_cast<uint8_t>(json.at("DFP").as_int()),
static_cast<uint8_t>(json.at("ATA").as_int()),
static_cast<uint8_t>(json.at("LCK").as_int()),
static_cast<uint8_t>(json.at("TP").as_int()),
static_cast<uint32_t>(json.at("EXP").as_int())};
}
phosg::JSON json() const {
return phosg::JSON::dict({{"ATP", this->atp},
{"MST", this->mst},
{"EVP", this->evp},
{"HP", this->hp},
{"DFP", this->dfp},
{"ATA", this->ata},
{"LCK", this->lck},
{"TP", this->tp},
{"EXP", this->exp.load()}});
}
operator LevelStatsDeltaT<!BE>() const {
return LevelStatsDeltaT<!BE>{
this->atp, this->mst, this->evp, this->hp, this->dfp, this->ata, this->lck, this->tp, this->exp.load()};
}
void apply(CharacterStats& ps) const {
ps.ata += this->ata;
@@ -95,6 +157,8 @@ class LevelTable {
// Offsets structures inside the subclasses' constructor implementations for more details on the file formats.
public:
virtual ~LevelTable() = default;
virtual size_t num_char_classes() const = 0;
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const = 0;
virtual const PlayerStats& max_stats_for_class(uint8_t char_class) const = 0;
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const = 0;
@@ -102,50 +166,113 @@ public:
void reset_to_base(PlayerStats& stats, uint8_t char_class) const;
void advance_to_level(PlayerStats& stats, uint32_t level, uint8_t char_class) const;
std::string serialize_binary_v4() const;
phosg::JSON json() const;
protected:
LevelTable() = default;
};
class LevelTableV2 : public LevelTable { // from PlayerTable.prs (PC)
class JSONLevelTable : public LevelTable {
public:
LevelTableV2(const std::string& data, bool compressed);
virtual ~LevelTableV2() = default;
JSONLevelTable(const phosg::JSON& json);
virtual ~JSONLevelTable() = default;
virtual size_t num_char_classes() const;
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
const PlayerStats& level_100_stats_for_class(uint8_t char_class) const;
virtual const PlayerStats& max_stats_for_class(uint8_t char_class) const;
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
private:
std::vector<CharacterStats> base_stats;
std::vector<PlayerStats> max_stats;
std::vector<std::array<LevelStatsDelta, 200>> level_deltas;
};
class LevelTableV2 : public LevelTable { // from PlayerTable.prs (PC)
public:
struct HPTPFactors {
le_float hp_factor;
le_float tp_factor;
} __packed_ws__(HPTPFactors, 8);
struct UnknownA9 {
le_float unknown_a1;
le_float unknown_a2;
le_float unknown_a3;
} __packed_ws__(UnknownA9, 0x0C);
struct AreaSoundConfig {
le_uint16_t step_sound;
le_uint16_t grass_step_sound;
le_uint16_t water_step_sound;
parray<uint8_t, 2> unused;
} __packed_ws__(AreaSoundConfig, 8);
struct WeaponReference {
le_uint16_t data1_1;
le_uint16_t data1_2;
} __packed_ws__(WeaponReference, 4);
struct UnknownA12 {
le_float unknown_a1;
le_float unknown_a2;
le_float unknown_a3;
le_float unknown_a4;
le_uint32_t unknown_a5;
} __packed_ws__(UnknownA12, 0x14);
explicit LevelTableV2(const std::string& data);
virtual ~LevelTableV2() = default;
virtual size_t num_char_classes() const;
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
virtual const PlayerStats& max_stats_for_class(uint8_t char_class) const;
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
protected:
std::array<CharacterStats, 9> base_stats;
std::array<PlayerStats, 9> level_100_stats;
std::array<PlayerStats, 9> max_stats;
std::array<std::array<LevelStatsDelta, 200>, 9> level_deltas;
};
class LevelTableV3BE : public LevelTable { // from PlyLevelTbl.cpt (GC)
class LevelTableV3 : public LevelTable { // from PlyLevelTbl.cpt (GC/XB)
public:
LevelTableV3BE(const std::string& data, bool encrypted);
virtual ~LevelTableV3BE() = default;
virtual ~LevelTableV3() = default;
virtual size_t num_char_classes() const;
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
virtual const PlayerStats& max_stats_for_class(uint8_t char_class) const;
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
private:
protected:
LevelTableV3() = default;
std::array<std::array<LevelStatsDelta, 200>, 12> level_deltas;
};
class LevelTableGC : public LevelTableV3 {
public:
explicit LevelTableGC(const std::string& data);
virtual ~LevelTableGC() = default;
};
class LevelTableXB : public LevelTableV3 {
public:
explicit LevelTableXB(const std::string& data);
virtual ~LevelTableXB() = default;
};
class LevelTableV4 : public LevelTable { // from PlyLevelTbl.prs (BB)
public:
LevelTableV4(const std::string& data, bool compressed);
explicit LevelTableV4(const std::string& data);
virtual ~LevelTableV4() = default;
virtual size_t num_char_classes() const;
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
virtual const PlayerStats& max_stats_for_class(uint8_t char_class) const;
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
private:
protected:
std::array<CharacterStats, 12> base_stats;
std::array<std::array<LevelStatsDelta, 200>, 12> level_deltas;
};
+44 -3
View File
@@ -2429,6 +2429,50 @@ Action a_encode_item_parameter_table(
write_output_data(args, data.data(), data.size(), nullptr);
});
Action a_decode_level_table(
"decode-level-table", nullptr,
+[](phosg::Arguments& args) {
auto input_data = read_input_data(args);
std::shared_ptr<LevelTable> table;
bool decompressed = args.get<bool>("decompressed");
switch (get_cli_version(args)) {
case Version::PC_V2:
table = std::make_shared<LevelTableV2>(decompressed ? input_data : prs_decompress(input_data));
break;
case Version::GC_V3:
table = std::make_shared<LevelTableGC>(
decompressed ? input_data : decrypt_and_decompress_pr2_data<true>(input_data));
break;
case Version::XB_V3:
table = std::make_shared<LevelTableXB>(
decompressed ? input_data : decrypt_and_decompress_pr2_data<false>(input_data));
break;
case Version::BB_V4:
table = std::make_shared<LevelTableV4>(decompressed ? input_data : prs_decompress(input_data));
break;
default:
throw std::runtime_error("This version does not have a level table");
}
auto json = table->json();
uint32_t serialize_options = phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::SORT_DICT_KEYS;
if (args.get<bool>("hex")) {
serialize_options |= phosg::JSON::SerializeOption::HEX_INTEGERS;
}
string json_data = json.serialize(serialize_options);
write_output_data(args, json_data.data(), json_data.size(), nullptr);
});
Action a_encode_level_table(
"encode-level-table-v4", nullptr,
+[](phosg::Arguments& args) {
JSONLevelTable table(phosg::JSON::parse(read_input_data(args)));
string data = table.serialize_binary_v4();
if (!args.get<bool>("decompressed")) {
data = prs_compress_optimal(data);
}
write_output_data(args, data.data(), data.size(), nullptr);
});
Action a_find_rel_sectionr(
"find-rel-sections", nullptr,
+[](phosg::Arguments& args) {
@@ -2616,7 +2660,6 @@ Action a_print_level_stats(
vector<PlayerStats> level_1_v1_v2;
vector<PlayerStats> level_100_v1_v2;
vector<PlayerStats> level_100_limit_v1_v2;
vector<PlayerStats> level_200_v1_v2;
vector<PlayerStats> level_200_limit_v1_v2;
vector<PlayerStats> level_1_v3;
@@ -2628,7 +2671,6 @@ Action a_print_level_stats(
for (size_t z = 0; z < 12; z++) {
if (z < 9) {
level_1_v1_v2.emplace_back().char_stats = s->level_table_v1_v2->base_stats_for_class(z);
level_100_limit_v1_v2.emplace_back(s->level_table_v1_v2->level_100_stats_for_class(z));
level_200_limit_v1_v2.emplace_back(s->level_table_v1_v2->max_stats_for_class(z));
s->level_table_v1_v2->advance_to_level(level_100_v1_v2.emplace_back(level_1_v1_v2.back()), 99, z);
s->level_table_v1_v2->advance_to_level(level_200_v1_v2.emplace_back(level_1_v1_v2.back()), 199, z);
@@ -2682,7 +2724,6 @@ Action a_print_level_stats(
print_stats_set(level_1_v1_v2, "v1/v2 Lv.1 ");
print_stats_set(level_100_v1_v2, "v1/v2 Lv.100");
print_stats_set(level_100_limit_v1_v2, "v1 limit ");
print_stats_set(level_200_v1_v2, "v2 Lv.200 ");
print_stats_set(level_200_limit_v1_v2, "v2 limit ");
print_stats_set(level_1_v3, "v3 Lv.1 ");
+1 -1
View File
@@ -334,7 +334,7 @@ using PlayerDispDataDCPCV3 = PlayerDispDataDCPCV3T<false>;
using PlayerDispDataDCPCV3BE = PlayerDispDataDCPCV3T<true>;
struct PlayerDispDataBBPreview {
/* 00 */ le_uint32_t experience = 0;
/* 00 */ le_uint32_t exp = 0;
/* 04 */ le_uint32_t level = 0;
// The name field in this structure is used for the player's Guild Card number, apparently (possibly because it's a
// char array and this is BB)
+1 -1
View File
@@ -3070,7 +3070,7 @@ std::string disassemble_quest_script(
l->lines.emplace_back(std::format(" {:04X} attack_range {:08X} /* {:g} */", l->offset + offsetof(PlayerStats, attack_range), stats.attack_range.load_raw(), stats.attack_range));
l->lines.emplace_back(std::format(" {:04X} knockback_range {:08X} /* {:g} */", l->offset + offsetof(PlayerStats, knockback_range), stats.knockback_range.load_raw(), stats.knockback_range));
l->lines.emplace_back(std::format(" {:04X} level {:08X} /* level {} */", l->offset + offsetof(PlayerStats, level), stats.level, stats.level + 1));
l->lines.emplace_back(std::format(" {:04X} experience {:08X} /* {} */", l->offset + offsetof(PlayerStats, experience), stats.experience, stats.experience));
l->lines.emplace_back(std::format(" {:04X} exp {:08X} /* {} */", l->offset + offsetof(PlayerStats, exp), stats.exp, stats.exp));
l->lines.emplace_back(std::format(" {:04X} meseta {:08X} /* {} */", l->offset + offsetof(PlayerStats, meseta), stats.meseta, stats.meseta));
});
};
+5 -5
View File
@@ -3947,7 +3947,7 @@ static void add_player_exp(shared_ptr<Client> c, uint32_t exp, uint16_t from_ene
auto s = c->require_server_state();
auto p = c->character_file();
p->disp.stats.experience += exp;
p->disp.stats.exp += exp;
if (c->version() == Version::BB_V4) {
send_give_experience(c, exp, from_enemy_id);
}
@@ -3955,7 +3955,7 @@ static void add_player_exp(shared_ptr<Client> c, uint32_t exp, uint16_t from_ene
bool leveled_up = false;
do {
const auto& level = s->level_table(c->version())->stats_delta_for_level(p->disp.visual.char_class, p->disp.stats.level + 1);
if (p->disp.stats.experience >= level.experience) {
if (p->disp.stats.exp >= level.exp) {
leveled_up = true;
level.apply(p->disp.stats.char_stats);
p->disp.stats.level++;
@@ -4017,7 +4017,7 @@ static uint32_t base_exp_for_enemy_type(
const auto& bp_table = bp_index->get_table(is_solo, episode);
const auto& bp_stats_indexes = type_definition_for_enemy(enemy_type).bp_stats_indexes;
if (!bp_stats_indexes.empty()) {
return bp_table.stats_for_index(difficulty, bp_stats_indexes.back()).experience;
return bp_table.stats_for_index(difficulty, bp_stats_indexes.back()).exp;
}
} catch (const out_of_range&) {
}
@@ -4700,8 +4700,8 @@ static asio::awaitable<void> on_battle_level_up_bb(shared_ptr<Client> c, Subcomm
auto s = c->require_server_state();
auto lp = lc->character_file();
uint32_t target_level = min<uint32_t>(lp->disp.stats.level + cmd.num_levels, 199);
uint32_t before_exp = lp->disp.stats.experience;
int32_t exp_delta = lp->disp.stats.experience - before_exp;
uint32_t before_exp = lp->disp.stats.exp;
int32_t exp_delta = lp->disp.stats.exp - before_exp;
if (exp_delta > 0) {
s->level_table(lc->version())->advance_to_level(lp->disp.stats, target_level, lp->disp.visual.char_class);
if (lc->version() == Version::BB_V4) {
+3 -3
View File
@@ -370,7 +370,7 @@ PSOBBBaseSystemFile::PSOBBBaseSystemFile() {
PlayerDispDataBBPreview PSOBBCharacterFile::to_preview() const {
PlayerDispDataBBPreview pre;
pre.level = this->disp.stats.level;
pre.experience = this->disp.stats.experience;
pre.exp = this->disp.stats.exp;
pre.visual = this->disp.visual;
pre.name = this->disp.name;
pre.play_time_seconds = this->play_time_seconds;
@@ -1429,11 +1429,11 @@ void PSOBBCharacterFile::import_tethealla_material_usage(std::shared_ptr<const L
void PSOBBCharacterFile::recompute_stats(std::shared_ptr<const LevelTable> level_table, bool reset_exp) {
uint32_t level = this->disp.stats.level;
uint32_t exp = this->disp.stats.experience;
uint32_t exp = this->disp.stats.exp;
level_table->reset_to_base(this->disp.stats, this->disp.visual.char_class);
level_table->advance_to_level(this->disp.stats, level, this->disp.visual.char_class);
if (!reset_exp) {
this->disp.stats.experience = exp;
this->disp.stats.exp = exp;
}
this->disp.stats.char_stats.atp += (this->get_material_usage(MaterialType::POWER) * 2);
+4 -4
View File
@@ -1888,9 +1888,9 @@ void ServerState::load_battle_params() {
void ServerState::load_level_tables() {
config_log.info_f("Loading level tables");
this->level_table_v1_v2 = make_shared<LevelTableV2>(phosg::load_file("system/level-tables/PlayerTable-pc-v2.prs"), true);
this->level_table_v3 = make_shared<LevelTableV3BE>(phosg::load_file("system/level-tables/PlyLevelTbl-gc-v3.cpt"), true);
this->level_table_v4 = make_shared<LevelTableV4>(*this->load_bb_file("PlyLevelTbl.prs"), true);
this->level_table_v1_v2 = make_shared<JSONLevelTable>(phosg::JSON::parse(phosg::load_file("system/level-tables/level-table-v1-v2.json")));
this->level_table_v3 = make_shared<JSONLevelTable>(phosg::JSON::parse(phosg::load_file("system/level-tables/level-table-v3.json")));
this->level_table_v4 = make_shared<JSONLevelTable>(phosg::JSON::parse(phosg::load_file("system/level-tables/level-table-v4.json")));
}
void ServerState::load_text_index() {
@@ -2248,7 +2248,7 @@ void ServerState::generate_bb_stream_file() {
add_file("BattleParamEntry_lab_on.dat");
add_file("BattleParamEntry_ep4.dat");
add_file("BattleParamEntry_ep4_on.dat");
add_file("PlyLevelTbl.prs");
add_file("PlyLevelTbl.prs", prs_compress_optimal(this->level_table_v4->serialize_binary_v4()));
add_file("ItemMagEdit.prs");
auto pmt = this->item_parameter_table(Version::BB_V4);
add_file("ItemPMT.prs", prs_compress_optimal(pmt->serialize_binary(Version::BB_V4)));
+1 -1
View File
@@ -204,7 +204,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_tournament_final_round_ex_values;
std::shared_ptr<const QuestCategoryIndex> quest_category_index;
std::shared_ptr<const QuestIndex> quest_index;
std::shared_ptr<const LevelTableV2> level_table_v1_v2;
std::shared_ptr<const LevelTable> level_table_v1_v2;
std::shared_ptr<const LevelTable> level_table_v3;
std::shared_ptr<const LevelTable> level_table_v4;
std::shared_ptr<const BattleParamsIndex> battle_params;
+12 -12
View File
@@ -293,18 +293,18 @@ uint8_t npc_for_name(const string& name, Version version) {
const char* name_for_char_class(uint8_t cls) {
static const array<const char*, 12> names = {
/* 00 */ "HUmar", // 0
/* 01 */ "HUnewearl", // 0
/* 02 */ "HUcast", // 1
/* 03 */ "RAmar", // 0
/* 04 */ "RAcast", // 2
/* 05 */ "RAcaseal", // 1
/* 06 */ "FOmarl", // 0
/* 07 */ "FOnewm", // 0
/* 08 */ "FOnewearl", // 0
/* 09 */ "HUcaseal", // 1
/* 0A */ "FOmar", // 0
/* 0B */ "RAmarl"}; // 0
/* 00 */ "HUmar",
/* 01 */ "HUnewearl",
/* 02 */ "HUcast",
/* 03 */ "RAmar",
/* 04 */ "RAcast",
/* 05 */ "RAcaseal",
/* 06 */ "FOmarl",
/* 07 */ "FOnewm",
/* 08 */ "FOnewearl",
/* 09 */ "HUcaseal",
/* 0A */ "FOmar",
/* 0B */ "RAmarl"};
try {
return names.at(cls);
} catch (const out_of_range&) {
Binary file not shown.
@@ -1,4 +1,6 @@
{
// Unlike all other PSO versions, this file is serialized and sent to the client. Making changes in here WILL affect
// BB clients.
"ArmorSaleDivisor": 0.79999995231628418,
"ArmorStatBoostIndexes": [],
"ItemCombinations": [
@@ -1,4 +1,6 @@
{
// This file matches what's in the client's ItemPMT file. Modifying this file will not affect the client; it only
// exists so the server can match the client's behavior.
"ArmorSaleDivisor": 10.0,
"ArmorStatBoostIndexes": [0x0, 0x0, 0x5, 0x5, 0x5, 0x5, 0x0, 0x5, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xF, 0xF, 0x32, 0x14, 0x14, 0x14, 0x19, 0x19, 0x1E, 0x1E, 0x0, 0x0, 0x0, 0x0, 0x14, 0x46, 0x0, 0x1E, 0x0, 0x0, 0x0, 0x1E, 0x0, 0x1E, 0x1E, 0x0, 0x0, 0x0, 0x0, 0x0],
"ItemCombinations": [],
@@ -1,4 +1,6 @@
{
// This file matches what's in the client's ItemPMT file. Modifying this file will not affect the client; it only
// exists so the server can match the client's behavior.
"ArmorSaleDivisor": 10.0,
"ArmorStatBoostIndexes": [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1E, 0x0, 0x0, 0x0, 0x0, 0x0],
"ItemCombinations": [],
@@ -1,4 +1,6 @@
{
// This file matches what's in the client's ItemPMT file. Modifying this file will not affect the client; it only
// exists so the server can match the client's behavior.
"ArmorSaleDivisor": 0.39999997615814209,
"ArmorStatBoostIndexes": [],
"ItemCombinations": [],
@@ -1,4 +1,6 @@
{
// This file matches what's in the client's ItemPMT file. Modifying this file will not affect the client; it only
// exists so the server can match the client's behavior.
"ArmorSaleDivisor": 0.79999995231628418,
"ArmorStatBoostIndexes": [],
"ItemCombinations": [
@@ -1,4 +1,6 @@
{
// This file matches what's in the client's ItemPMT file. Modifying this file will not affect the client; it only
// exists so the server can match the client's behavior.
"ArmorSaleDivisor": 0.79999995231628418,
"ArmorStatBoostIndexes": [],
"ItemCombinations": [
@@ -1,4 +1,6 @@
{
// This file matches what's in the client's ItemPMT file. Modifying this file will not affect the client; it only
// exists so the server can match the client's behavior.
"ArmorSaleDivisor": 0.39999997615814209,
"ArmorStatBoostIndexes": [],
"ItemCombinations": [],
@@ -1,4 +1,6 @@
{
// This file matches what's in the client's ItemPMT file. Modifying this file will not affect the client; it only
// exists so the server can match the client's behavior.
"ArmorSaleDivisor": 0.79999995231628418,
"ArmorStatBoostIndexes": [],
"ItemCombinations": [
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+79
View File
@@ -0,0 +1,79 @@
#!/bin/sh
set -e
EXECUTABLE="$1"
if [ -z "$EXECUTABLE" ]; then
EXECUTABLE="./newserv"
fi
DIR=tests/game-tables
PMT_PREFIX=$DIR/item-parameter-table
echo "... (level-table) BB"
$EXECUTABLE decode-level-table --bb-v4 $DIR/level-table-bb-v4.expected.bin --decompressed $DIR/level-table-bb-v4.json --hex
$EXECUTABLE encode-level-table-v4 $DIR/level-table-bb-v4.json $DIR/level-table-bb-v4.encoded.bin --decompressed
bindiff $DIR/level-table-bb-v4.expected.bin $DIR/level-table-bb-v4.encoded.bin
echo "... (item-parameter-table) DC NTE"
$EXECUTABLE decode-item-parameter-table --dc-nte $PMT_PREFIX-dc-nte.expected.bin --decompressed $PMT_PREFIX-dc-nte.json --hex
$EXECUTABLE encode-item-parameter-table --dc-nte $PMT_PREFIX-dc-nte.json $PMT_PREFIX-dc-nte.encoded.bin --decompressed
bindiff $PMT_PREFIX-dc-nte.expected.bin $PMT_PREFIX-dc-nte.encoded.bin
echo "... (item-parameter-table) DC 11/2000"
$EXECUTABLE decode-item-parameter-table --dc-11-2000 $PMT_PREFIX-dc-11-2000.expected.bin --decompressed $PMT_PREFIX-dc-11-2000.json --hex
$EXECUTABLE encode-item-parameter-table --dc-11-2000 $PMT_PREFIX-dc-11-2000.json $PMT_PREFIX-dc-11-2000.encoded.bin --decompressed
bindiff $PMT_PREFIX-dc-11-2000.expected.bin $PMT_PREFIX-dc-11-2000.encoded.bin
echo "... (item-parameter-table) DC V1"
$EXECUTABLE decode-item-parameter-table --dc-v1 $PMT_PREFIX-dc-v1.expected.bin --decompressed $PMT_PREFIX-dc-v1.json --hex
$EXECUTABLE encode-item-parameter-table --dc-v1 $PMT_PREFIX-dc-v1.json $PMT_PREFIX-dc-v1.encoded.bin --decompressed
bindiff $PMT_PREFIX-dc-v1.expected.bin $PMT_PREFIX-dc-v1.encoded.bin
echo "... (item-parameter-table) DC V2"
$EXECUTABLE decode-item-parameter-table --dc-v2 $PMT_PREFIX-dc-v2.expected.bin --decompressed $PMT_PREFIX-dc-v2.json --hex
$EXECUTABLE encode-item-parameter-table --dc-v2 $PMT_PREFIX-dc-v2.json $PMT_PREFIX-dc-v2.encoded.bin --decompressed
bindiff $PMT_PREFIX-dc-v2.expected.bin $PMT_PREFIX-dc-v2.encoded.bin
echo "... (item-parameter-table) PC NTE"
$EXECUTABLE decode-item-parameter-table --pc-nte $PMT_PREFIX-pc-nte.expected.bin --decompressed $PMT_PREFIX-pc-nte.json --hex
$EXECUTABLE encode-item-parameter-table --pc-nte $PMT_PREFIX-pc-nte.json $PMT_PREFIX-pc-nte.encoded.bin --decompressed
bindiff $PMT_PREFIX-pc-nte.expected.bin $PMT_PREFIX-pc-nte.encoded.bin
echo "... (item-parameter-table) PC V2"
$EXECUTABLE decode-item-parameter-table --pc-v2 $PMT_PREFIX-pc-v2.expected.bin --decompressed $PMT_PREFIX-pc-v2.json --hex
$EXECUTABLE encode-item-parameter-table --pc-v2 $PMT_PREFIX-pc-v2.json $PMT_PREFIX-pc-v2.encoded.bin --decompressed
bindiff $PMT_PREFIX-pc-v2.expected.bin $PMT_PREFIX-pc-v2.encoded.bin
echo "... (item-parameter-table) GC NTE"
$EXECUTABLE decode-item-parameter-table --gc-nte $PMT_PREFIX-gc-nte.expected.bin --decompressed $PMT_PREFIX-gc-nte.json --hex
$EXECUTABLE encode-item-parameter-table --gc-nte $PMT_PREFIX-gc-nte.json $PMT_PREFIX-gc-nte.encoded.bin --decompressed
bindiff $PMT_PREFIX-gc-nte.expected.bin $PMT_PREFIX-gc-nte.encoded.bin
echo "... (item-parameter-table) GC V3"
$EXECUTABLE decode-item-parameter-table --gc-v3 $PMT_PREFIX-gc-v3.expected.bin --decompressed $PMT_PREFIX-gc-v3.json --hex
$EXECUTABLE encode-item-parameter-table --gc-v3 $PMT_PREFIX-gc-v3.json $PMT_PREFIX-gc-v3.encoded.bin --decompressed
bindiff $PMT_PREFIX-gc-v3.expected.bin $PMT_PREFIX-gc-v3.encoded.bin
echo "... (item-parameter-table) GC Ep3 NTE"
$EXECUTABLE decode-item-parameter-table --gc-ep3-nte $PMT_PREFIX-gc-ep3-nte.expected.bin --decompressed $PMT_PREFIX-gc-ep3-nte.json --hex
$EXECUTABLE encode-item-parameter-table --gc-ep3-nte $PMT_PREFIX-gc-ep3-nte.json $PMT_PREFIX-gc-ep3-nte.encoded.bin --decompressed
bindiff $PMT_PREFIX-gc-ep3-nte.expected.bin $PMT_PREFIX-gc-ep3-nte.encoded.bin
echo "... (item-parameter-table) GC Ep3"
$EXECUTABLE decode-item-parameter-table --gc-ep3 $PMT_PREFIX-gc-ep3.expected.bin --decompressed $PMT_PREFIX-gc-ep3.json --hex
$EXECUTABLE encode-item-parameter-table --gc-ep3 $PMT_PREFIX-gc-ep3.json $PMT_PREFIX-gc-ep3.encoded.bin --decompressed
bindiff $PMT_PREFIX-gc-ep3.expected.bin $PMT_PREFIX-gc-ep3.encoded.bin
echo "... (item-parameter-table) XB"
$EXECUTABLE decode-item-parameter-table --xb-v3 $PMT_PREFIX-xb-v3.expected.bin --decompressed $PMT_PREFIX-xb-v3.json --hex
$EXECUTABLE encode-item-parameter-table --xb-v3 $PMT_PREFIX-xb-v3.json $PMT_PREFIX-xb-v3.encoded.bin --decompressed
bindiff $PMT_PREFIX-xb-v3.expected.bin $PMT_PREFIX-xb-v3.encoded.bin
echo "... (item-parameter-table) BB"
$EXECUTABLE decode-item-parameter-table --bb-v4 $PMT_PREFIX-bb-v4.expected.bin --decompressed $PMT_PREFIX-bb-v4.json --hex
$EXECUTABLE encode-item-parameter-table --bb-v4 $PMT_PREFIX-bb-v4.json $PMT_PREFIX-bb-v4.encoded.bin --decompressed
bindiff $PMT_PREFIX-bb-v4.expected.bin $PMT_PREFIX-bb-v4.encoded.bin
echo "... clean up"
rm -f $DIR/*.encoded.bin $DIR/*.json
Binary file not shown.
-73
View File
@@ -1,73 +0,0 @@
#!/bin/sh
set -e
EXECUTABLE="$1"
if [ -z "$EXECUTABLE" ]; then
EXECUTABLE="./newserv"
fi
DIR=tests/item-parameter-tables
echo "... DC NTE"
$EXECUTABLE decode-item-parameter-table --dc-nte $DIR/dc-nte.expected.bin --decompressed $DIR/dc-nte.json --hex
$EXECUTABLE encode-item-parameter-table --dc-nte $DIR/dc-nte.json $DIR/dc-nte.encoded.bin --decompressed
bindiff $DIR/dc-nte.expected.bin $DIR/dc-nte.encoded.bin
echo "... DC 11/2000"
$EXECUTABLE decode-item-parameter-table --dc-11-2000 $DIR/dc-11-2000.expected.bin --decompressed $DIR/dc-11-2000.json --hex
$EXECUTABLE encode-item-parameter-table --dc-11-2000 $DIR/dc-11-2000.json $DIR/dc-11-2000.encoded.bin --decompressed
bindiff $DIR/dc-11-2000.expected.bin $DIR/dc-11-2000.encoded.bin
echo "... DC V1"
$EXECUTABLE decode-item-parameter-table --dc-v1 $DIR/dc-v1.expected.bin --decompressed $DIR/dc-v1.json --hex
$EXECUTABLE encode-item-parameter-table --dc-v1 $DIR/dc-v1.json $DIR/dc-v1.encoded.bin --decompressed
bindiff $DIR/dc-v1.expected.bin $DIR/dc-v1.encoded.bin
echo "... DC V2"
$EXECUTABLE decode-item-parameter-table --dc-v2 $DIR/dc-v2.expected.bin --decompressed $DIR/dc-v2.json --hex
$EXECUTABLE encode-item-parameter-table --dc-v2 $DIR/dc-v2.json $DIR/dc-v2.encoded.bin --decompressed
bindiff $DIR/dc-v2.expected.bin $DIR/dc-v2.encoded.bin
echo "... PC NTE"
$EXECUTABLE decode-item-parameter-table --pc-nte $DIR/pc-nte.expected.bin --decompressed $DIR/pc-nte.json --hex
$EXECUTABLE encode-item-parameter-table --pc-nte $DIR/pc-nte.json $DIR/pc-nte.encoded.bin --decompressed
bindiff $DIR/pc-nte.expected.bin $DIR/pc-nte.encoded.bin
echo "... PC V2"
$EXECUTABLE decode-item-parameter-table --pc-v2 $DIR/pc-v2.expected.bin --decompressed $DIR/pc-v2.json --hex
$EXECUTABLE encode-item-parameter-table --pc-v2 $DIR/pc-v2.json $DIR/pc-v2.encoded.bin --decompressed
bindiff $DIR/pc-v2.expected.bin $DIR/pc-v2.encoded.bin
echo "... GC NTE"
$EXECUTABLE decode-item-parameter-table --gc-nte $DIR/gc-nte.expected.bin --decompressed $DIR/gc-nte.json --hex
$EXECUTABLE encode-item-parameter-table --gc-nte $DIR/gc-nte.json $DIR/gc-nte.encoded.bin --decompressed
bindiff $DIR/gc-nte.expected.bin $DIR/gc-nte.encoded.bin
echo "... GC V3"
$EXECUTABLE decode-item-parameter-table --gc-v3 $DIR/gc-v3.expected.bin --decompressed $DIR/gc-v3.json --hex
$EXECUTABLE encode-item-parameter-table --gc-v3 $DIR/gc-v3.json $DIR/gc-v3.encoded.bin --decompressed
bindiff $DIR/gc-v3.expected.bin $DIR/gc-v3.encoded.bin
echo "... GC Ep3 NTE"
$EXECUTABLE decode-item-parameter-table --gc-ep3-nte $DIR/gc-ep3-nte.expected.bin --decompressed $DIR/gc-ep3-nte.json --hex
$EXECUTABLE encode-item-parameter-table --gc-ep3-nte $DIR/gc-ep3-nte.json $DIR/gc-ep3-nte.encoded.bin --decompressed
bindiff $DIR/gc-ep3-nte.expected.bin $DIR/gc-ep3-nte.encoded.bin
echo "... GC Ep3"
$EXECUTABLE decode-item-parameter-table --gc-ep3 $DIR/gc-ep3.expected.bin --decompressed $DIR/gc-ep3.json --hex
$EXECUTABLE encode-item-parameter-table --gc-ep3 $DIR/gc-ep3.json $DIR/gc-ep3.encoded.bin --decompressed
bindiff $DIR/gc-ep3.expected.bin $DIR/gc-ep3.encoded.bin
echo "... XB"
$EXECUTABLE decode-item-parameter-table --xb-v3 $DIR/xb-v3.expected.bin --decompressed $DIR/xb-v3.json --hex
$EXECUTABLE encode-item-parameter-table --xb-v3 $DIR/xb-v3.json $DIR/xb-v3.encoded.bin --decompressed
bindiff $DIR/xb-v3.expected.bin $DIR/xb-v3.encoded.bin
echo "... BB"
$EXECUTABLE decode-item-parameter-table --bb-v4 $DIR/bb-v4.expected.bin --decompressed $DIR/bb-v4.json --hex
$EXECUTABLE encode-item-parameter-table --bb-v4 $DIR/bb-v4.json $DIR/bb-v4.encoded.bin --decompressed
bindiff $DIR/bb-v4.expected.bin $DIR/bb-v4.encoded.bin
echo "... clean up"
rm -f tests/item-parameter-tables/*.encoded.bin tests/item-parameter-tables/*.json