improve CommonItemSet JSON parser/serializer
This commit is contained in:
+171
-3
@@ -128,8 +128,15 @@ CommonItemSet::Table::Table(const phosg::JSON& json, Episode episode)
|
||||
for (size_t z = 0; z < 0x64; z++) {
|
||||
static const array<Episode, 3> episodes = {Episode::EP1, Episode::EP2, Episode::EP4};
|
||||
for (Episode episode : episodes) {
|
||||
auto types = enemy_types_for_rare_table_index(episode, z);
|
||||
vector<string> names;
|
||||
if (types.empty()) {
|
||||
names.emplace_back(std::format("{}:!{:02X}", abbreviation_for_episode(episode), z));
|
||||
}
|
||||
for (auto type : enemy_types_for_rare_table_index(episode, z)) {
|
||||
string name = std::format("{}:{}", abbreviation_for_episode(episode), phosg::name_for_enum(type));
|
||||
names.emplace_back(std::format("{}:{}", abbreviation_for_episode(episode), phosg::name_for_enum(type)));
|
||||
}
|
||||
for (const auto& name : names) {
|
||||
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();
|
||||
@@ -336,6 +343,122 @@ void CommonItemSet::Table::print(FILE* stream) const {
|
||||
}
|
||||
}
|
||||
|
||||
void CommonItemSet::Table::print_diff(FILE* stream, const Table& other) const {
|
||||
if (this->episode != other.episode) {
|
||||
phosg::fwrite_fmt(stream, "> Episode: {} -> {}\n", name_for_episode(this->episode), name_for_episode(other.episode));
|
||||
}
|
||||
if (this->base_weapon_type_prob_table != other.base_weapon_type_prob_table) {
|
||||
phosg::fwrite_fmt(stream, "> base_weapon_type_prob_table: {} -> {}\n",
|
||||
phosg::format_data_string(&this->base_weapon_type_prob_table, sizeof(this->base_weapon_type_prob_table)),
|
||||
phosg::format_data_string(&other.base_weapon_type_prob_table, sizeof(other.base_weapon_type_prob_table)));
|
||||
}
|
||||
if (this->subtype_base_table != other.subtype_base_table) {
|
||||
phosg::fwrite_fmt(stream, "> subtype_base_table: {} -> {}\n",
|
||||
phosg::format_data_string(&this->subtype_base_table, sizeof(this->subtype_base_table)),
|
||||
phosg::format_data_string(&other.subtype_base_table, sizeof(other.subtype_base_table)));
|
||||
}
|
||||
if (this->subtype_area_length_table != other.subtype_area_length_table) {
|
||||
phosg::fwrite_fmt(stream, "> subtype_area_length_table: {} -> {}\n",
|
||||
phosg::format_data_string(&this->subtype_area_length_table, sizeof(this->subtype_area_length_table)),
|
||||
phosg::format_data_string(&other.subtype_area_length_table, sizeof(other.subtype_area_length_table)));
|
||||
}
|
||||
if (this->grind_prob_table != other.grind_prob_table) {
|
||||
phosg::fwrite_fmt(stream, "> grind_prob_table: {} -> {}\n",
|
||||
phosg::format_data_string(&this->grind_prob_table, sizeof(this->grind_prob_table)),
|
||||
phosg::format_data_string(&other.grind_prob_table, sizeof(other.grind_prob_table)));
|
||||
}
|
||||
if (this->armor_shield_type_index_prob_table != other.armor_shield_type_index_prob_table) {
|
||||
phosg::fwrite_fmt(stream, "> armor_shield_type_index_prob_table: {} -> {}\n",
|
||||
phosg::format_data_string(&this->armor_shield_type_index_prob_table, sizeof(this->armor_shield_type_index_prob_table)),
|
||||
phosg::format_data_string(&other.armor_shield_type_index_prob_table, sizeof(other.armor_shield_type_index_prob_table)));
|
||||
}
|
||||
if (this->armor_slot_count_prob_table != other.armor_slot_count_prob_table) {
|
||||
phosg::fwrite_fmt(stream, "> armor_slot_count_prob_table: {} -> {}\n",
|
||||
phosg::format_data_string(&this->armor_slot_count_prob_table, sizeof(this->armor_slot_count_prob_table)),
|
||||
phosg::format_data_string(&other.armor_slot_count_prob_table, sizeof(other.armor_slot_count_prob_table)));
|
||||
}
|
||||
if (this->enemy_meseta_ranges != other.enemy_meseta_ranges) {
|
||||
phosg::fwrite_fmt(stream, "> enemy_meseta_ranges: {} -> {}\n",
|
||||
phosg::format_data_string(&this->enemy_meseta_ranges, sizeof(this->enemy_meseta_ranges)),
|
||||
phosg::format_data_string(&other.enemy_meseta_ranges, sizeof(other.enemy_meseta_ranges)));
|
||||
}
|
||||
if (this->enemy_type_drop_probs != other.enemy_type_drop_probs) {
|
||||
phosg::fwrite_fmt(stream, "> enemy_type_drop_probs: {} -> {}\n",
|
||||
phosg::format_data_string(&this->enemy_type_drop_probs, sizeof(this->enemy_type_drop_probs)),
|
||||
phosg::format_data_string(&other.enemy_type_drop_probs, sizeof(other.enemy_type_drop_probs)));
|
||||
}
|
||||
if (this->enemy_item_classes != other.enemy_item_classes) {
|
||||
phosg::fwrite_fmt(stream, "> enemy_item_classes: {} -> {}\n",
|
||||
phosg::format_data_string(&this->enemy_item_classes, sizeof(this->enemy_item_classes)),
|
||||
phosg::format_data_string(&other.enemy_item_classes, sizeof(other.enemy_item_classes)));
|
||||
}
|
||||
if (this->box_meseta_ranges != other.box_meseta_ranges) {
|
||||
phosg::fwrite_fmt(stream, "> box_meseta_ranges: {} -> {}\n",
|
||||
phosg::format_data_string(&this->box_meseta_ranges, sizeof(this->box_meseta_ranges)),
|
||||
phosg::format_data_string(&other.box_meseta_ranges, sizeof(other.box_meseta_ranges)));
|
||||
}
|
||||
if (this->has_rare_bonus_value_prob_table != other.has_rare_bonus_value_prob_table) {
|
||||
phosg::fwrite_fmt(stream, "> Has rare bonus value prob table: {} -> {}\n",
|
||||
this->has_rare_bonus_value_prob_table ? "true" : "false",
|
||||
other.has_rare_bonus_value_prob_table ? "true" : "false");
|
||||
}
|
||||
if (this->bonus_value_prob_table != other.bonus_value_prob_table) {
|
||||
phosg::fwrite_fmt(stream, "> bonus_value_prob_table: {} -> {}\n",
|
||||
phosg::format_data_string(&this->bonus_value_prob_table, sizeof(this->bonus_value_prob_table)),
|
||||
phosg::format_data_string(&other.bonus_value_prob_table, sizeof(other.bonus_value_prob_table)));
|
||||
}
|
||||
if (this->nonrare_bonus_prob_spec != other.nonrare_bonus_prob_spec) {
|
||||
phosg::fwrite_fmt(stream, "> nonrare_bonus_prob_spec: {} -> {}\n",
|
||||
phosg::format_data_string(&this->nonrare_bonus_prob_spec, sizeof(this->nonrare_bonus_prob_spec)),
|
||||
phosg::format_data_string(&other.nonrare_bonus_prob_spec, sizeof(other.nonrare_bonus_prob_spec)));
|
||||
}
|
||||
if (this->bonus_type_prob_table != other.bonus_type_prob_table) {
|
||||
phosg::fwrite_fmt(stream, "> bonus_type_prob_table: {} -> {}\n",
|
||||
phosg::format_data_string(&this->bonus_type_prob_table, sizeof(this->bonus_type_prob_table)),
|
||||
phosg::format_data_string(&other.bonus_type_prob_table, sizeof(other.bonus_type_prob_table)));
|
||||
}
|
||||
if (this->special_mult != other.special_mult) {
|
||||
phosg::fwrite_fmt(stream, "> special_mult: {} -> {}\n",
|
||||
phosg::format_data_string(&this->special_mult, sizeof(this->special_mult)),
|
||||
phosg::format_data_string(&other.special_mult, sizeof(other.special_mult)));
|
||||
}
|
||||
if (this->special_percent != other.special_percent) {
|
||||
phosg::fwrite_fmt(stream, "> special_percent: {} -> {}\n",
|
||||
phosg::format_data_string(&this->special_percent, sizeof(this->special_percent)),
|
||||
phosg::format_data_string(&other.special_percent, sizeof(other.special_percent)));
|
||||
}
|
||||
if (this->tool_class_prob_table != other.tool_class_prob_table) {
|
||||
phosg::fwrite_fmt(stream, "> tool_class_prob_table: {} -> {}\n",
|
||||
phosg::format_data_string(&this->tool_class_prob_table, sizeof(this->tool_class_prob_table)),
|
||||
phosg::format_data_string(&other.tool_class_prob_table, sizeof(other.tool_class_prob_table)));
|
||||
}
|
||||
if (this->technique_index_prob_table != other.technique_index_prob_table) {
|
||||
phosg::fwrite_fmt(stream, "> technique_index_prob_table: {} -> {}\n",
|
||||
phosg::format_data_string(&this->technique_index_prob_table, sizeof(this->technique_index_prob_table)),
|
||||
phosg::format_data_string(&other.technique_index_prob_table, sizeof(other.technique_index_prob_table)));
|
||||
}
|
||||
if (this->technique_level_ranges != other.technique_level_ranges) {
|
||||
phosg::fwrite_fmt(stream, "> technique_level_ranges: {} -> {}\n",
|
||||
phosg::format_data_string(&this->technique_level_ranges, sizeof(this->technique_level_ranges)),
|
||||
phosg::format_data_string(&other.technique_level_ranges, sizeof(other.technique_level_ranges)));
|
||||
}
|
||||
if (this->armor_or_shield_type_bias != other.armor_or_shield_type_bias) {
|
||||
phosg::fwrite_fmt(stream, "> Armor/shield type bias: {} -> {}\n",
|
||||
this->armor_or_shield_type_bias ? "true" : "false",
|
||||
other.armor_or_shield_type_bias ? "true" : "false");
|
||||
}
|
||||
if (this->unit_max_stars_table != other.unit_max_stars_table) {
|
||||
phosg::fwrite_fmt(stream, "> unit_max_stars_table: {} -> {}\n",
|
||||
phosg::format_data_string(&this->unit_max_stars_table, sizeof(this->unit_max_stars_table)),
|
||||
phosg::format_data_string(&other.unit_max_stars_table, sizeof(other.unit_max_stars_table)));
|
||||
}
|
||||
if (this->box_item_class_prob_table != other.box_item_class_prob_table) {
|
||||
phosg::fwrite_fmt(stream, "> box_item_class_prob_table: {} -> {}\n",
|
||||
phosg::format_data_string(&this->box_item_class_prob_table, sizeof(this->box_item_class_prob_table)),
|
||||
phosg::format_data_string(&other.box_item_class_prob_table, sizeof(other.box_item_class_prob_table)));
|
||||
}
|
||||
}
|
||||
|
||||
phosg::JSON CommonItemSet::Table::json() const {
|
||||
phosg::JSON enemy_meseta_ranges_json = phosg::JSON::dict();
|
||||
phosg::JSON enemy_type_drop_probs_json = phosg::JSON::dict();
|
||||
@@ -343,8 +466,16 @@ phosg::JSON CommonItemSet::Table::json() const {
|
||||
for (size_t z = 0; z < 0x64; z++) {
|
||||
static const array<Episode, 3> episodes = {Episode::EP1, Episode::EP2, Episode::EP4};
|
||||
for (Episode episode : episodes) {
|
||||
for (auto type : enemy_types_for_rare_table_index(episode, z)) {
|
||||
string name = std::format("{}:{}", abbreviation_for_episode(episode), phosg::name_for_enum(type));
|
||||
auto types = enemy_types_for_rare_table_index(episode, z);
|
||||
vector<string> names;
|
||||
if (types.empty()) {
|
||||
names.emplace_back(std::format("{}:!{:02X}", abbreviation_for_episode(episode), z));
|
||||
} else {
|
||||
for (auto type : types) {
|
||||
names.emplace_back(std::format("{}:{}", abbreviation_for_episode(episode), phosg::name_for_enum(type)));
|
||||
}
|
||||
}
|
||||
for (const auto& name : names) {
|
||||
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]);
|
||||
@@ -424,6 +555,43 @@ void CommonItemSet::print(FILE* stream) const {
|
||||
}
|
||||
}
|
||||
|
||||
void CommonItemSet::print_diff(FILE* stream, const CommonItemSet& other) const {
|
||||
static const array<GameMode, 4> modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO};
|
||||
for (const auto& mode : modes) {
|
||||
static const array<Episode, 3> 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++) {
|
||||
shared_ptr<const Table> this_table;
|
||||
shared_ptr<const Table> other_table;
|
||||
try {
|
||||
this_table = this->get_table(episode, mode, difficulty, section_id);
|
||||
} catch (const runtime_error&) {
|
||||
}
|
||||
try {
|
||||
other_table = other.get_table(episode, mode, difficulty, section_id);
|
||||
} catch (const runtime_error&) {
|
||||
}
|
||||
|
||||
if (!this_table && !other_table) {
|
||||
continue;
|
||||
} else if (!this_table) {
|
||||
phosg::fwrite_fmt(stream, "> Table present in other but not this: {} {} {} {}\n",
|
||||
name_for_mode(mode), name_for_episode(episode), name_for_difficulty(difficulty), name_for_section_id(section_id));
|
||||
} else if (!other_table) {
|
||||
phosg::fwrite_fmt(stream, "> Table present in this but not other: {} {} {} {}\n",
|
||||
name_for_mode(mode), name_for_episode(episode), name_for_difficulty(difficulty), name_for_section_id(section_id));
|
||||
} else if (*this_table != *other_table) {
|
||||
phosg::fwrite_fmt(stream, "> Tables do not match: {} {} {} {}\n",
|
||||
name_for_mode(mode), name_for_episode(episode), name_for_difficulty(difficulty), name_for_section_id(section_id));
|
||||
this_table->print_diff(stream, *other_table);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CommonItemSet::Table::Table(const phosg::StringReader& r, bool is_big_endian, bool is_v3, Episode episode)
|
||||
: episode(episode) {
|
||||
if (is_big_endian) {
|
||||
|
||||
@@ -18,10 +18,16 @@ public:
|
||||
Table(const phosg::JSON& json, Episode episode);
|
||||
Table(const phosg::StringReader& r, bool big_endian, bool is_v3, Episode episode);
|
||||
|
||||
bool operator==(const Table& other) const = default;
|
||||
bool operator!=(const Table& other) const = default;
|
||||
|
||||
template <typename IntT>
|
||||
struct Range {
|
||||
IntT min;
|
||||
IntT max;
|
||||
|
||||
bool operator==(const Range& other) const = default;
|
||||
bool operator!=(const Range& other) const = default;
|
||||
} __attribute__((packed));
|
||||
|
||||
Episode episode;
|
||||
@@ -50,6 +56,7 @@ public:
|
||||
|
||||
phosg::JSON json() const;
|
||||
void print(FILE* stream) const;
|
||||
void print_diff(FILE* stream, const Table& other) const;
|
||||
|
||||
private:
|
||||
template <bool BE>
|
||||
@@ -261,9 +268,13 @@ public:
|
||||
check_struct_size(OffsetsBE, 0x54);
|
||||
};
|
||||
|
||||
bool operator==(const CommonItemSet& other) const = default;
|
||||
bool operator!=(const CommonItemSet& other) const = default;
|
||||
|
||||
std::shared_ptr<const Table> get_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const;
|
||||
phosg::JSON json() const;
|
||||
void print(FILE* stream) const;
|
||||
void print_diff(FILE* stream, const CommonItemSet& other) const;
|
||||
|
||||
protected:
|
||||
CommonItemSet() = default;
|
||||
|
||||
+32
-13
@@ -2152,6 +2152,20 @@ Action a_convert_rare_item_set(
|
||||
throw runtime_error("cannot determine output format; use a filename ending with .json, .gsl, .gslb, or .afs");
|
||||
}
|
||||
});
|
||||
|
||||
static shared_ptr<CommonItemSet> load_common_item_set(const std::string& filename, bool big_endian) {
|
||||
auto data = make_shared<string>(phosg::load_file(filename));
|
||||
if (filename.ends_with(".json")) {
|
||||
return make_shared<JSONCommonItemSet>(phosg::JSON::parse(*data));
|
||||
} else if (filename.ends_with(".gsl")) {
|
||||
return make_shared<GSLV3V4CommonItemSet>(data, big_endian);
|
||||
} else if (filename.ends_with(".gslb")) {
|
||||
return make_shared<GSLV3V4CommonItemSet>(data, true);
|
||||
} else {
|
||||
throw runtime_error("cannot determine input format; use a filename ending with .json, .gsl, or .gslb");
|
||||
}
|
||||
}
|
||||
|
||||
Action a_convert_common_item_set(
|
||||
"convert-common-item-set", "\
|
||||
convert-common-item-set INPUT-FILENAME [OUTPUT-FILENAME]\n\
|
||||
@@ -2167,27 +2181,32 @@ Action a_convert_common_item_set(
|
||||
throw runtime_error("input filename must be given");
|
||||
}
|
||||
|
||||
auto data = make_shared<string>(read_input_data(args));
|
||||
shared_ptr<CommonItemSet> cs;
|
||||
if (input_filename.ends_with(".json")) {
|
||||
cs = make_shared<JSONCommonItemSet>(phosg::JSON::parse(*data));
|
||||
} else if (input_filename.ends_with(".gsl")) {
|
||||
cs = make_shared<GSLV3V4CommonItemSet>(data, args.get<bool>("big-endian"));
|
||||
} else if (input_filename.ends_with(".gslb")) {
|
||||
cs = make_shared<GSLV3V4CommonItemSet>(data, true);
|
||||
} else {
|
||||
throw runtime_error("cannot determine input format; use a filename ending with .json, .gsl, .gslb, or .afs");
|
||||
}
|
||||
|
||||
auto cs = load_common_item_set(input_filename, args.get<bool>("big-endian"));
|
||||
const string& output_filename = args.get<string>(2, false);
|
||||
if (output_filename.empty()) {
|
||||
cs->print(stdout);
|
||||
} else {
|
||||
auto json = cs->json();
|
||||
string json_data = json.serialize(phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::HEX_INTEGERS | phosg::JSON::SerializeOption::SORT_DICT_KEYS);
|
||||
string json_data = json.serialize(phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::SORT_DICT_KEYS);
|
||||
write_output_data(args, json_data.data(), json_data.size(), "json");
|
||||
}
|
||||
});
|
||||
Action a_compare_common_item_set(
|
||||
"compare-common-item-set", nullptr,
|
||||
+[](phosg::Arguments& args) {
|
||||
string input_filename1 = args.get<string>(1, false);
|
||||
if (input_filename1.empty() || (input_filename1 == "-")) {
|
||||
throw runtime_error("two input filenames must be given");
|
||||
}
|
||||
string input_filename2 = args.get<string>(2, false);
|
||||
if (input_filename2.empty() || (input_filename2 == "-")) {
|
||||
throw runtime_error("two input filenames must be given");
|
||||
}
|
||||
|
||||
auto cs1 = load_common_item_set(input_filename1, args.get<bool>("big-endian1"));
|
||||
auto cs2 = load_common_item_set(input_filename2, args.get<bool>("big-endian2"));
|
||||
cs1->print_diff(stdout, *cs2);
|
||||
});
|
||||
|
||||
Action a_describe_item(
|
||||
"describe-item", "\
|
||||
|
||||
Reference in New Issue
Block a user