diff --git a/src/Episode3/DataIndexes.cc b/src/Episode3/DataIndexes.cc index b2e344c8..f6525bcb 100644 --- a/src/Episode3/DataIndexes.cc +++ b/src/Episode3/DataIndexes.cc @@ -80,6 +80,16 @@ const char* name_for_link_color(uint8_t color) { } } +JSON json_for_link_colors(const parray& colors) { + JSON ret = JSON::list(); + for (size_t z = 0; z < colors.size(); z++) { + if (colors[z]) { + ret.emplace_back(name_for_link_color(colors[z])); + } + } + return ret; +} + Location::Location() : Location(0, 0) {} Location::Location(uint8_t x, uint8_t y) : Location(x, y, Direction::RIGHT) {} Location::Location(uint8_t x, uint8_t y, Direction direction) @@ -1016,6 +1026,111 @@ Card: %04" PRIX32 " \"%s\"\n\ } } +JSON CardDefinition::Stat::json() const { + const char* type_str = "unknown"; + switch (this->type) { + case Type::BLANK: + type_str = "BLANK"; + break; + case Type::STAT: + type_str = "DEFAULT"; + break; + case Type::PLUS_STAT: + type_str = "PLUS"; + break; + case Type::MINUS_STAT: + type_str = "MINUS"; + break; + case Type::EQUALS_STAT: + type_str = "EQUALS"; + break; + case Type::UNKNOWN: + type_str = "UNKNOWN"; + break; + case Type::PLUS_UNKNOWN: + type_str = "PLUS_UNKNOWN"; + break; + case Type::MINUS_UNKNOWN: + type_str = "MINUS_UNKNOWN"; + break; + case Type::EQUALS_UNKNOWN: + type_str = "EQUALS_UNKNOWN"; + break; + } + return JSON::dict({ + {"type", type_str}, + {"value", this->stat}, + }); +} + +JSON CardDefinition::Effect::json() const { + return JSON::dict({ + {"EffectNum", this->effect_num}, + {"ConditionType", name_for_enum(this->type)}, + {"Expression", this->expr.decode()}, + {"When", this->when}, + {"Arg1", this->arg1.decode()}, + {"Arg2", this->arg2.decode()}, + {"Arg3", this->arg3.decode()}, + {"ApplyCriterion", name_for_enum(this->apply_criterion)}, + {"NameIndex", this->name_index}, + }); +} + +JSON CardDefinition::json() const { + JSON range_json; + if (this->range[0] == 0x000FFFFF) { + range_json = "ENTIRE_FIELD"; + } else { + range_json = JSON::list(); + for (size_t y = 0; y < 6; y++) { + uint32_t row = this->range[y]; + auto& row_json = range_json.emplace_back(JSON::list()); + for (size_t x = 0; x < 5; x++) { + row_json.emplace_back((row & 0x00010000) ? true : false); + row <<= 4; + } + } + } + + JSON effects_json = JSON::list(); + for (size_t z = 0; z < this->effects.size(); z++) { + if (!this->effects[z].is_empty()) { + effects_json.emplace_back(this->effects[z].json()); + } + } + + return JSON::dict({ + {"CardID", this->card_id.load()}, + {"JPName", this->jp_name.decode()}, + {"CardType", name_for_enum(this->type)}, + {"SelfCost", this->self_cost}, + {"AllyCost", this->ally_cost}, + {"HP", this->hp.json()}, + {"AP", this->ap.json()}, + {"TP", this->tp.json()}, + {"MV", this->mv.json()}, + {"LeftColors", json_for_link_colors(this->left_colors)}, + {"RightColors", json_for_link_colors(this->right_colors)}, + {"TopColors", json_for_link_colors(this->top_colors)}, + {"Range", std::move(range_json)}, + {"TargetMode", name_for_target_mode(this->target_mode)}, + {"AssistTurns", this->assist_turns}, + {"CannotMove", this->cannot_move ? true : false}, + {"CannotAttack", this->cannot_attack ? true : false}, + {"CannotDrop", this->cannot_drop ? true : false}, + {"UsableCriterion", name_for_enum(this->usable_criterion)}, + {"Rank", name_for_rank(this->rank)}, + {"CardClass", name_for_enum(this->card_class())}, + {"AssistAIParams", this->assist_ai_params.load()}, + {"DropRates", JSON::list({this->drop_rates[0].load(), this->drop_rates[1].load()})}, + {"ENName", this->en_name.decode()}, + {"JPShortName", this->jp_short_name.decode()}, + {"ENShortName", this->en_short_name.decode()}, + {"Effects", std::move(effects_json)}, + }); +} + void PlayerConfig::decrypt() { if (!this->is_encrypted) { return; @@ -1498,6 +1613,97 @@ void MapDefinition::assert_semantically_equivalent(const MapDefinition& other) c } } +JSON MapDefinition::CameraSpec::json() const { + return JSON::dict({ + {"Camera", JSON::list({this->camera_x.load(), this->camera_y.load(), this->camera_z.load()})}, + {"Focus", JSON::list({this->focus_x.load(), this->focus_y.load(), this->focus_z.load()})}, + }); +} + +JSON MapDefinition::NPCDeck::json() const { + JSON card_ids_json = JSON::list(); + for (size_t z = 0; z < this->card_ids.size(); z++) { + if (this->card_ids[z] != 0xFFFF) { + card_ids_json.emplace_back(this->card_ids[z].load()); + } + } + return JSON::dict({ + {"Name", this->name.decode()}, + {"CardIDs", std::move(card_ids_json)}, + }); +} + +JSON MapDefinition::AIParams::json() const { + JSON params_json = JSON::list(); + for (size_t z = 0; z < this->params.size(); z++) { + params_json.emplace_back(this->params[z].load()); + } + return JSON::dict({ + {"IsArkz", this->is_arkz ? true : false}, + {"Name", this->name.decode()}, + {"CardIDs", std::move(params_json)}, + }); +} + +JSON MapDefinition::DialogueSet::json() const { + JSON strings_json = JSON::list(); + for (size_t z = 0; z < this->strings.size(); z++) { + strings_json.emplace_back(this->strings[z].decode()); + } + return JSON::dict({ + {"When", this->when.load()}, + {"PercentChance", this->percent_chance.load()}, + {"CardIDs", std::move(strings_json)}, + }); +} + +JSON MapDefinition::EntryState::json() const { + JSON player_type_json; + switch (this->player_type) { + case 0x00: + player_type_json = "Player"; + break; + case 0x01: + player_type_json = "Player/COM"; + break; + case 0x02: + player_type_json = "COM"; + break; + case 0x03: + player_type_json = "NPC"; + break; + case 0x04: + player_type_json = "NONE"; + break; + case 0xFF: + player_type_json = "FREE"; + break; + default: + player_type_json = this->player_type; + } + JSON deck_type_json; + switch (this->deck_type) { + case 0x00: + deck_type_json = "HERO ONLY"; + break; + case 0x01: + deck_type_json = "DARK ONLY"; + break; + case 0xFF: + deck_type_json = "ANY"; + break; + default: + deck_type_json = this->deck_type; + } + return JSON::dict({ + {"PlayerType", std::move(player_type_json)}, + {"DeckType", std::move(deck_type_json)}, + }); +} + +// TODO: +// JSON MapDefinition::json() const { ... } + string MapDefinition::CameraSpec::str() const { return string_printf( "CameraSpec[a1=(%g %g %g %g %g %g %g %g %g) camera=(%g %g %g) focus=(%g %g %g) a2=(%g %g %g)]", @@ -2259,6 +2465,14 @@ uint64_t CardIndex::definitions_mtime() const { return this->mtime_for_card_definitions; } +JSON CardIndex::definitions_json() const { + JSON ret = JSON::dict(); + for (const auto& it : this->card_definitions_by_name) { + ret.emplace(it.first, it.second->def.json()); + } + return ret; +} + string CardIndex::normalize_card_name(const string& name) { string ret; for (char ch : name) { diff --git a/src/Episode3/DataIndexes.hh b/src/Episode3/DataIndexes.hh index f90ba736..cfaef5ec 100644 --- a/src/Episode3/DataIndexes.hh +++ b/src/Episode3/DataIndexes.hh @@ -468,6 +468,7 @@ struct CardDefinition { void decode_code(); std::string str() const; + JSON json() const; } __attribute__((packed)); struct Effect { @@ -504,6 +505,7 @@ struct CardDefinition { bool is_empty() const; static std::string str_for_arg(const std::string& arg); std::string str(const char* separator = ", ", const TextSet* text_archive = nullptr) const; + JSON json() const; } __attribute__((packed)); /* 0000 */ be_uint32_t card_id; @@ -770,6 +772,7 @@ struct CardDefinition { void decode_range(); std::string str(bool single_line = true, const TextSet* text_archive = nullptr) const; + JSON json() const; } __attribute__((packed)); // 0x128 bytes in total struct CardDefinitionsFooter { @@ -1150,6 +1153,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests /* 48 */ std::string str() const; + JSON json() const; } __attribute__((packed)); // This array specifies the camera zone maps. A camera zone map is a subset of @@ -1207,6 +1211,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests /* 00 */ pstring name; /* 18 */ parray card_ids; // Last one appears to always be FFFF /* 58 */ + JSON json() const; } __attribute__((packed)); /* 1FE8 */ parray npc_decks; // Unused if name[0] == 0 @@ -1222,6 +1227,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests // TODO: Figure out exactly how these are used and document here. /* 0018 */ parray params; /* 0114 */ + JSON json() const; } __attribute__((packed)); /* 20F0 */ parray npc_ai_params; // Unused if name[0] == 0 @@ -1274,6 +1280,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests // strings, excluding any that are empty or begin with the character '^'. /* 0004 */ parray, 4> strings; /* 0104 */ + JSON json() const; } __attribute__((packed)); // There are up to 0x10 of these per valid NPC, but only the first 13 of them @@ -1356,6 +1363,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests bool operator==(const EntryState& other) const = default; bool operator!=(const EntryState& other) const = default; + JSON json() const; } __attribute__((packed)); /* 5A10 */ parray entry_states; /* 5A18 */ @@ -1371,6 +1379,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests void assert_semantically_equivalent(const MapDefinition& other) const; std::string str(const CardIndex* card_index, uint8_t language) const; + JSON json() const; } __attribute__((packed)); struct MapDefinitionTrial { @@ -1453,6 +1462,7 @@ public: std::shared_ptr definition_for_name_normalized(const std::string& name) const; std::set all_ids() const; uint64_t definitions_mtime() const; + JSON definitions_json() const; private: static std::string normalize_card_name(const std::string& name); diff --git a/src/HTTPServer.cc b/src/HTTPServer.cc index b11dd94e..4e69de53 100644 --- a/src/HTTPServer.cc +++ b/src/HTTPServer.cc @@ -815,6 +815,38 @@ JSON HTTPServer::generate_all_json() const { }); } +JSON HTTPServer::generate_ep3_cards_json(bool trial) const { + const auto& index = trial ? this->state->ep3_card_index_trial : this->state->ep3_card_index; + return index->definitions_json(); +} + +JSON HTTPServer::generate_rare_tables_json() const { + JSON ret = JSON::list(); + for (const auto& it : this->state->rare_item_sets) { + ret.emplace_back(it.first); + } + return ret; +} + +JSON HTTPServer::generate_rare_table_json(const std::string& table_name) const { + try { + const auto& table = this->state->rare_item_sets.at(table_name); + shared_ptr name_index; + if (ends_with(table_name, "-v1")) { + name_index = this->state->item_name_index(Version::DC_V1); + } else if (ends_with(table_name, "-v2")) { + name_index = this->state->item_name_index(Version::PC_V2); + } else if (ends_with(table_name, "-v3")) { + name_index = this->state->item_name_index(Version::GC_V3); + } else if (ends_with(table_name, "-v4")) { + name_index = this->state->item_name_index(Version::BB_V4); + } + return table->json(name_index); + } catch (const out_of_range&) { + throw http_error(404, "table does not exist"); + } +} + void HTTPServer::handle_request(struct evhttp_request* req) { JSON ret; uint32_t serialize_options = 0; @@ -835,14 +867,29 @@ void HTTPServer::handle_request(struct evhttp_request* req) { if (uri == "/") { auto endpoints_json = JSON::list({ + "/y/data/ep3-cards", + "/y/data/ep3-cards-trial", + "/y/data/rare-tables", + "/y/data/config", "/y/clients", "/y/proxy-clients", "/y/lobbies", + "/y/server", "/y/summary", "/y/all", }); ret = JSON::dict({{"endpoints", std::move(endpoints_json)}}); + } else if (uri == "/y/data/ep3-cards") { + ret = this->generate_ep3_cards_json(false); + } else if (uri == "/y/data/ep3-cards-trial") { + ret = this->generate_ep3_cards_json(true); + } else if (uri == "/y/data/rare-tables") { + ret = this->generate_rare_tables_json(); + } else if (!strncmp(uri.c_str(), "/y/data/rare-tables/", 20)) { + ret = this->generate_rare_table_json(uri.substr(20)); + } else if (uri == "/y/data/config") { + ret = this->state->config_json; } else if (uri == "/y/clients") { ret = this->generate_game_server_clients_json(); } else if (uri == "/y/proxy-clients") { diff --git a/src/HTTPServer.hh b/src/HTTPServer.hh index 686fe8e5..72de625a 100644 --- a/src/HTTPServer.hh +++ b/src/HTTPServer.hh @@ -61,4 +61,8 @@ protected: JSON generate_lobbies_json() const; JSON generate_summary_json() const; JSON generate_all_json() const; + + JSON generate_ep3_cards_json(bool trial) const; + JSON generate_rare_tables_json() const; + JSON generate_rare_table_json(const std::string& table_name) const; }; diff --git a/src/Main.cc b/src/Main.cc index 95d47ad7..8c928d48 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -1649,7 +1649,8 @@ Action a_convert_rare_item_set( if (output_filename.empty() || (output_filename == "-")) { rs->print_all_collections(stdout, s->item_name_index(version)); } else if (ends_with(output_filename, ".json")) { - string data = rs->serialize_json(s->item_name_index(version)); + auto json = rs->json(s->item_name_index(version)); + string data = json.serialize(JSON::SerializeOption::FORMAT | JSON::SerializeOption::HEX_INTEGERS | JSON::SerializeOption::SORT_DICT_KEYS); write_output_data(args, data.data(), data.size(), nullptr); } else if (ends_with(output_filename, ".gsl")) { string data = rs->serialize_gsl(args.get("big-endian")); diff --git a/src/RareItemSet.cc b/src/RareItemSet.cc index cc916d55..6b7b8b79 100644 --- a/src/RareItemSet.cc +++ b/src/RareItemSet.cc @@ -427,7 +427,7 @@ std::string RareItemSet::serialize_gsl(bool big_endian) const { return GSLArchive::generate(files, big_endian); } -std::string RareItemSet::serialize_json(shared_ptr name_index) const { +JSON RareItemSet::json(shared_ptr name_index) const { auto modes_dict = JSON::dict(); static const array modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO}; for (const auto& mode : modes) { @@ -507,7 +507,7 @@ std::string RareItemSet::serialize_json(shared_ptr name_ind modes_dict.emplace(name_for_mode(mode), std::move(episodes_dict)); } - return modes_dict.serialize(JSON::SerializeOption::FORMAT | JSON::SerializeOption::HEX_INTEGERS | JSON::SerializeOption::SORT_DICT_KEYS); + return modes_dict; } void RareItemSet::print_collection( diff --git a/src/RareItemSet.hh b/src/RareItemSet.hh index b2ec40e0..c328f5f4 100644 --- a/src/RareItemSet.hh +++ b/src/RareItemSet.hh @@ -37,7 +37,7 @@ public: std::string serialize_afs(bool is_v1) const; std::string serialize_gsl(bool big_endian) const; - std::string serialize_json(std::shared_ptr name_index = nullptr) const; + JSON json(std::shared_ptr name_index = nullptr) const; void print_collection( FILE* stream,