diff --git a/CMakeLists.txt b/CMakeLists.txt index d6db6386..ec48b544 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,7 +141,7 @@ foreach(LogTestCase IN ITEMS ${LogTestCases}) add_test( NAME ${LogTestCase} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - COMMAND ${CMAKE_BINARY_DIR}/newserv replay-log ${LogTestCase} --config=${CMAKE_SOURCE_DIR}/tests/config.json) + COMMAND ${CMAKE_BINARY_DIR}/newserv --replay-log=${LogTestCase} --config=${CMAKE_SOURCE_DIR}/tests/config.json) endforeach() file(GLOB ScriptTestCases ${CMAKE_SOURCE_DIR}/tests/*.test.sh) diff --git a/src/ARCodeTranslator-Stub.hh b/src/ARCodeTranslator-Stub.hh index 3989c757..80fa6e09 100644 --- a/src/ARCodeTranslator-Stub.hh +++ b/src/ARCodeTranslator-Stub.hh @@ -3,6 +3,6 @@ #include #include -inline void run_ar_code_translator(const std::string&) { +void run_ar_code_translator(const string& initial_directory, const string& use_file, const string&) { throw std::runtime_error("resource_file is not available; install it and rebuild newserv"); } diff --git a/src/ARCodeTranslator.cc b/src/ARCodeTranslator.cc index 73e13613..8896c77f 100644 --- a/src/ARCodeTranslator.cc +++ b/src/ARCodeTranslator.cc @@ -6,7 +6,7 @@ using namespace std; -void run_ar_code_translator(const std::string& initial_directory) { +void run_ar_code_translator(const std::string& initial_directory, const std::string& use_file, const std::string& command) { string directory = initial_directory; while (ends_with(directory, "/")) { directory.resize(directory.size() - 1); @@ -93,51 +93,62 @@ void run_ar_code_translator(const std::string& initial_directory) { throw runtime_error("scan field too long; too many matches"); }; - while (!feof(stdin)) { - if (!source_filename.empty()) { - fprintf(stdout, "ar-trans:%s/%s> ", directory.c_str(), source_filename.c_str()); - } else { - fprintf(stdout, "ar-trans:%s> ", directory.c_str()); + auto handle_command = [&](const string& command) -> void { + auto tokens = split(command, ' '); + if (tokens.empty()) { + throw runtime_error("no command given"); } - fflush(stdout); + strip_trailing_whitespace(tokens[tokens.size() - 1]); - string command = fgets(stdin); - try { - strip_trailing_whitespace(command); - auto tokens = split(command, ' '); - if (tokens.empty()) { - throw runtime_error("no command given"); + if (tokens[0] == "use") { + source_filename = tokens.at(1); + source_file = files.at(source_filename); + } else if (tokens[0] == "match") { + if (!source_file) { + throw runtime_error("no source file selected"); + } - } else if (tokens[0] == "use") { - source_filename = tokens.at(1); - source_file = files.at(source_filename); - - } else if (tokens[0] == "match") { - if (!source_file) { - throw runtime_error("no source file selected"); - } - - uint32_t source_addr = stoul(tokens.at(1), nullptr, 16); - for (const auto& it : files) { - if (it.second == source_file) { - log.info("(%s) %08" PRIX32, it.first.c_str(), source_addr); - } else { - try { - uint32_t match_addr = find_match(it.second, source_addr); - log.info("(%s) %08" PRIX32, it.first.c_str(), match_addr); - } catch (const exception& e) { - log.error("(%s) failed: %s", it.first.c_str(), e.what()); - } + uint32_t source_addr = stoul(tokens.at(1), nullptr, 16); + for (const auto& it : files) { + if (it.second == source_file) { + log.info("(%s) %08" PRIX32, it.first.c_str(), source_addr); + } else { + try { + uint32_t match_addr = find_match(it.second, source_addr); + log.info("(%s) %08" PRIX32, it.first.c_str(), match_addr); + } catch (const exception& e) { + log.error("(%s) failed: %s", it.first.c_str(), e.what()); } } - - } else if (!tokens[0].empty()) { - throw runtime_error("unknown command"); } - } catch (const exception& e) { - log.error("Failed: %s", e.what()); + } else if (!tokens[0].empty()) { + throw runtime_error("unknown command"); } + }; + + if (!use_file.empty()) { + source_filename = use_file; + source_file = files.at(source_filename); } - fputc('\n', stdout); + if (!command.empty()) { + handle_command(command); + } else { + while (!feof(stdin)) { + if (!source_filename.empty()) { + fprintf(stdout, "ar-trans:%s/%s> ", directory.c_str(), source_filename.c_str()); + } else { + fprintf(stdout, "ar-trans:%s> ", directory.c_str()); + } + fflush(stdout); + + string command = fgets(stdin); + try { + handle_command(command); + } catch (const exception& e) { + log.error("Failed: %s", e.what()); + } + } + fputc('\n', stdout); + } } diff --git a/src/ARCodeTranslator.hh b/src/ARCodeTranslator.hh index 8f68d332..ba632940 100644 --- a/src/ARCodeTranslator.hh +++ b/src/ARCodeTranslator.hh @@ -2,4 +2,4 @@ #include -void run_ar_code_translator(const std::string& directory); +void run_ar_code_translator(const std::string& initial_directory, const std::string& use_file, const std::string& command); diff --git a/src/CatSession.cc b/src/CatSession.cc index 35c01045..8a6849a4 100644 --- a/src/CatSession.cc +++ b/src/CatSession.cc @@ -34,10 +34,10 @@ using namespace std; CatSession::CatSession( shared_ptr base, const struct sockaddr_storage& remote, - GameVersion version, + Version version, shared_ptr bb_key_file) : Shell(base), - log(string_printf("[CatSession:%s] ", name_for_version(version)), proxy_server_log.min_level), + log(string_printf("[CatSession:%s] ", name_for_enum(version)), proxy_server_log.min_level), channel( version, 1, @@ -74,11 +74,10 @@ void CatSession::dispatch_on_channel_input( void CatSession::on_channel_input( uint16_t command, uint32_t flag, std::string& data) { - if (this->channel.version != GameVersion::BB) { + if (!uses_v4_encryption(this->channel.version)) { if (command == 0x02 || command == 0x17 || command == 0x91 || command == 0x9B) { const auto& cmd = check_size_t(data, 0xFFFF); - if ((this->channel.version == GameVersion::GC) || - (this->channel.version == GameVersion::XB)) { + if (uses_v3_encryption(this->channel.version)) { this->channel.crypt_in.reset(new PSOV3Encryption(cmd.server_key)); this->channel.crypt_out.reset(new PSOV3Encryption(cmd.client_key)); this->log.info("Enabled V3 encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")", diff --git a/src/CatSession.hh b/src/CatSession.hh index a3873680..d13c1611 100644 --- a/src/CatSession.hh +++ b/src/CatSession.hh @@ -21,7 +21,7 @@ public: CatSession( std::shared_ptr base, const struct sockaddr_storage& remote, - GameVersion version, + Version version, std::shared_ptr bb_key_file); virtual ~CatSession() = default; diff --git a/src/Channel.cc b/src/Channel.cc index 38e79fff..5563c798 100644 --- a/src/Channel.cc +++ b/src/Channel.cc @@ -23,7 +23,7 @@ static void flush_and_free_bufferevent(struct bufferevent* bev) { } Channel::Channel( - GameVersion version, + Version version, uint8_t language, on_command_received_t on_command_received, on_error_t on_error, @@ -44,7 +44,7 @@ Channel::Channel( Channel::Channel( struct bufferevent* bev, - GameVersion version, + Version version, uint8_t language, on_command_received_t on_command_received, on_error_t on_error, @@ -157,7 +157,7 @@ void Channel::disconnect() { Channel::Message Channel::recv() { struct evbuffer* buf = bufferevent_get_input(this->bev.get()); - size_t header_size = (this->version == GameVersion::BB) ? 8 : 4; + size_t header_size = (this->version == Version::BB_V4) ? 8 : 4; PSOCommandHeader header; if (evbuffer_copyout(buf, &header, header_size) < static_cast(header_size)) { throw out_of_range("no command available"); @@ -172,7 +172,7 @@ Channel::Message Channel::recv() { // If encryption is enabled, BB pads commands to 8-byte boundaries, and this // is not reflected in the size field. This logic does not occur if encryption // is not yet enabled. - size_t command_physical_size = (this->crypt_in.get() && (version == GameVersion::BB)) + size_t command_physical_size = (this->crypt_in.get() && (version == Version::BB_V4)) ? ((command_logical_size + 7) & ~7) : command_logical_size; if (evbuffer_get_length(buf) < command_physical_size) { @@ -214,7 +214,7 @@ Channel::Message Channel::recv() { print_color_escape(stderr, this->terminal_recv_color, TerminalFormat::BOLD, TerminalFormat::END); } - if (version == GameVersion::BB) { + if (version == Version::BB_V4) { command_data_log.info( "Received from %s (version=BB command=%04hX flag=%08" PRIX32 ")", this->name.c_str(), @@ -224,7 +224,7 @@ Channel::Message Channel::recv() { command_data_log.info( "Received from %s (version=%s command=%02hX flag=%02" PRIX32 ")", this->name.c_str(), - name_for_version(this->version), + name_for_enum(this->version), header.command(this->version), header.flag(this->version)); } @@ -265,11 +265,20 @@ void Channel::send(uint16_t cmd, uint32_t flag, const std::vectorversion) { - case GameVersion::DC: - case GameVersion::GC: - case GameVersion::XB: { + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + case Version::XB_V3: { PSOCommandHeaderDCV3 header; - if (this->crypt_out.get()) { + if (this->crypt_out.get() && + (this->version != Version::DC_NTE) && + (this->version != Version::DC_V1_12_2000_PROTOTYPE) && + (this->version != Version::DC_V1)) { send_data_size = (sizeof(header) + size + 3) & ~3; } else { send_data_size = (sizeof(header) + size); @@ -281,9 +290,9 @@ void Channel::send(uint16_t cmd, uint32_t flag, const std::vector(&header), sizeof(header)); break; } - - case GameVersion::PC: - case GameVersion::PATCH: { + case Version::PC_PATCH: + case Version::BB_PATCH: + case Version::PC_V2: { PSOCommandHeaderPC header; if (this->crypt_out.get()) { send_data_size = (sizeof(header) + size + 3) & ~3; @@ -297,8 +306,7 @@ void Channel::send(uint16_t cmd, uint32_t flag, const std::vector(&header), sizeof(header)); break; } - - case GameVersion::BB: { + case Version::BB_V4: { // BB has an annoying behavior here: command lengths must be multiples of // 4, but the actual data length must be a multiple of 8. If the size // field is not divisible by 8, 4 extra bytes are sent anyway. This @@ -340,12 +348,12 @@ void Channel::send(uint16_t cmd, uint32_t flag, const std::vectorterminal_send_color != TerminalFormat::NORMAL) { print_color_escape(stderr, TerminalFormat::FG_YELLOW, TerminalFormat::BOLD, TerminalFormat::END); } - if (version == GameVersion::BB) { + if (version == Version::BB_V4) { command_data_log.info("Sending to %s (version=BB command=%04hX flag=%08" PRIX32 ")", this->name.c_str(), cmd, flag); } else { command_data_log.info("Sending to %s (version=%s command=%02hX flag=%02" PRIX32 ")", - this->name.c_str(), name_for_version(version), cmd, flag); + this->name.c_str(), name_for_enum(version), cmd, flag); } print_data(stderr, send_data.data(), logical_size, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS); if (use_terminal_colors && this->terminal_send_color != TerminalFormat::NORMAL) { @@ -370,7 +378,7 @@ void Channel::send(uint16_t cmd, uint32_t flag, const string& data, bool silent) } void Channel::send(const void* data, size_t size, bool silent) { - size_t header_size = (this->version == GameVersion::BB) ? 8 : 4; + size_t header_size = (this->version == Version::BB_V4) ? 8 : 4; const auto* header = reinterpret_cast(data); this->send( header->command(this->version), diff --git a/src/Channel.hh b/src/Channel.hh index ce562928..ddc15641 100644 --- a/src/Channel.hh +++ b/src/Channel.hh @@ -15,7 +15,7 @@ struct Channel { struct sockaddr_storage remote_addr; bool is_virtual_connection; - GameVersion version; + Version version; uint8_t language; std::shared_ptr crypt_in; std::shared_ptr crypt_out; @@ -39,7 +39,7 @@ struct Channel { // Creates an unconnected channel Channel( - GameVersion version, + Version version, uint8_t language, on_command_received_t on_command_received, on_error_t on_error, @@ -50,7 +50,7 @@ struct Channel { // Creates a connected channel Channel( struct bufferevent* bev, - GameVersion version, + Version version, uint8_t language, on_command_received_t on_command_received, on_error_t on_error, diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 6e89e38a..f5495126 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -46,13 +46,13 @@ static void check_license_flags(shared_ptr c, uint32_t mask) { } } -static void check_version(shared_ptr c, GameVersion version) { +static void check_version(shared_ptr c, Version version) { if (c->version() != version) { throw precondition_failed("$C6This command cannot\nbe used for your\nversion of PSO."); } } -static void check_not_version(shared_ptr c, GameVersion version) { +static void check_not_version(shared_ptr c, Version version) { if (c->version() == version) { throw precondition_failed("$C6This command cannot\nbe used for your\nversion of PSO."); } @@ -65,7 +65,7 @@ static void check_is_game(shared_ptr l, bool is_game) { } static void check_is_ep3(shared_ptr c, bool is_ep3) { - if (c->config.check_flag(Client::Flag::IS_EPISODE_3) != is_ep3) { + if (::is_ep3(c->version()) != is_ep3) { throw precondition_failed(is_ep3 ? "$C6This command can only\nbe used in Episode 3." : "$C6This command cannot\nbe used in Episode 3."); } } @@ -282,7 +282,7 @@ static void server_command_qset_qclear(shared_ptr c, const std::string& uint16_t flag_num = stoul(args, nullptr, 0); - if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::PC)) { + if (is_v1_or_v2(c->version())) { G_SetQuestFlag_DC_PC_6x75 cmd = {{0x75, 0x02, 0x0000}, flag_num, should_set ? 0 : 1}; send_command_t(l, 0x60, 0x00, cmd); } else { @@ -355,7 +355,7 @@ static void proxy_command_qcall(shared_ptr ses, cons static void server_command_show_material_counts(shared_ptr c, const std::string&) { auto p = c->game_data.character(); - if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::PC)) { + if (is_v1_or_v2(c->version())) { send_text_message_printf(c, "%hhu HP, %hhu TP", p->get_material_usage(PSOBBCharacterFile::MaterialType::HP), p->get_material_usage(PSOBBCharacterFile::MaterialType::TP)); @@ -429,8 +429,8 @@ static void proxy_command_patch(shared_ptr ses, cons }; auto send_version_detect_or_send_call = [args, ses, send_call]() { - if (ses->version() == GameVersion::GC && - ses->config.specific_version == default_specific_version_for_version(GameVersion::GC, -1)) { + if (is_gc(ses->version()) && + ses->config.specific_version == default_specific_version_for_version(ses->version(), -1)) { auto s = ses->require_server_state(); send_function_call( ses->client_channel, @@ -475,7 +475,7 @@ static void server_command_persist(shared_ptr c, const std::string&) { static void server_command_exit(shared_ptr c, const std::string&) { auto l = c->require_lobby(); if (l->is_game()) { - if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (is_ep3(c->version())) { c->channel.send(0xED, 0x00); } else if (l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS) || l->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS)) { G_UnusedHeader cmd = {0x73, 0x01, 0x0000}; @@ -496,7 +496,7 @@ static void server_command_exit(shared_ptr c, const std::string&) { static void proxy_command_exit(shared_ptr ses, const std::string&) { if (ses->is_in_game) { - if (ses->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (is_ep3(ses->version())) { ses->client_channel.send(0xED, 0x00); } else if (ses->is_in_quest) { G_UnusedHeader cmd = {0x73, 0x01, 0x0000}; @@ -595,10 +595,7 @@ static void proxy_command_lobby_event(shared_ptr ses send_text_message(ses->client_channel, "$C6No such lobby event."); } else { ses->config.override_lobby_event = new_event; - // This command is supported on all V3 versions except Ep1&2 Trial - if ((ses->version() == GameVersion::GC && !ses->config.check_flag(Client::Flag::IS_GC_TRIAL_EDITION)) || - (ses->version() == GameVersion::XB) || - (ses->version() == GameVersion::BB)) { + if (!is_v1_or_v2(ses->version())) { ses->client_channel.send(0xDA, ses->config.override_lobby_event); } } @@ -680,7 +677,7 @@ static void server_command_saverec(shared_ptr c, const std::string& args } static void server_command_playrec(shared_ptr c, const std::string& args) { - if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (!is_ep3(c->version())) { send_text_message(c, "$C4This command can\nonly be used on\nEpisode 3"); return; } @@ -888,7 +885,7 @@ static void server_command_edit(shared_ptr c, const std::string& args) { auto s = c->require_server_state(); auto l = c->require_lobby(); check_is_game(l, false); - check_version(c, GameVersion::BB); + check_version(c, Version::BB_V4); if (s->cheat_mode_behavior == ServerState::BehaviorSwitch::OFF) { send_text_message(l, "$C6Cheats are disabled\non this server"); @@ -988,7 +985,7 @@ static void server_command_edit(shared_ptr c, const std::string& args) { // TODO: implement this (and make sure the bank name is filesystem-safe) /* static void server_command_change_bank(shared_ptr c, const std::string&) { - check_version(c, GameVersion::BB); + check_version(c, Version::BB_V4); ... } */ @@ -997,7 +994,7 @@ static void server_command_convert_char_to_bb(shared_ptr c, const std::s auto s = c->require_server_state(); auto l = c->require_lobby(); check_is_game(l, false); - check_not_version(c, GameVersion::BB); + check_not_version(c, Version::BB_V4); vector tokens = split(args, ' '); if (tokens.size() != 3) { @@ -1027,7 +1024,7 @@ static void server_command_convert_char_to_bb(shared_ptr c, const std::s } static void server_command_save(shared_ptr c, const std::string&) { - check_version(c, GameVersion::BB); + check_version(c, Version::BB_V4); try { c->game_data.save_character_file(); send_text_message(c, "Character data saved"); @@ -1373,7 +1370,7 @@ static void server_command_itemtable(shared_ptr c, const std::string&) { check_is_leader(l, c); if (l->check_flag(Lobby::Flag::CANNOT_CHANGE_ITEM_TABLE)) { send_text_message(c, "Cannot switch item\ntables on this\nserver"); - } else if (l->base_version == GameVersion::BB) { + } else if (l->base_version == Version::BB_V4) { send_text_message(c, "Cannot use client\nitem table on BB"); } else if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { send_text_message(c, "Cannot use server\nitem tables if item\ntracking is off"); @@ -1405,7 +1402,7 @@ static void server_command_item(shared_ptr c, const std::string& args) { static void proxy_command_item(shared_ptr ses, const std::string& args) { auto s = ses->require_server_state(); check_proxy_cheats_allowed(s); - if (ses->version() == GameVersion::BB) { + if (ses->version() == Version::BB_V4) { send_text_message(ses->client_channel, "$C6This command cannot\nbe used on the proxy\nserver in BB games"); return; } diff --git a/src/Client.cc b/src/Client.cc index 21d0c64d..acfbc52e 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -21,34 +21,41 @@ const uint64_t CLIENT_CONFIG_MAGIC = 0x8399AC32; static atomic next_id(1); -void Client::Config::set_flags_for_version(GameVersion version, int64_t sub_version) { +void Client::Config::set_flags_for_version(Version version, int64_t sub_version) { this->set_flag(Flag::PROXY_CHAT_COMMANDS_ENABLED); this->set_flag(Flag::PROXY_CHAT_FILTER_ENABLED); switch (sub_version) { case -1: // Initial check (before sub_version recognition) switch (version) { - case GameVersion::DC: + case Version::PC_PATCH: + case Version::BB_PATCH: + this->set_flag(Flag::NO_D6); + this->set_flag(Flag::NO_SEND_FUNCTION_CALL); + break; + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: this->set_flag(Flag::NO_D6); this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH); break; - case GameVersion::GC: - break; - case GameVersion::XB: - // TODO: Do all versions of XB need this flag? US does, at least. - this->set_flag(Flag::NO_D6_AFTER_LOBBY); - this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH); - break; - case GameVersion::PC: + case Version::PC_V2: this->set_flag(Flag::NO_D6); this->set_flag(Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY); this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH); break; - case GameVersion::PATCH: - this->set_flag(Flag::NO_D6); - this->set_flag(Flag::NO_SEND_FUNCTION_CALL); + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: break; - case GameVersion::BB: + case Version::XB_V3: + // TODO: Do all versions of XB need this flag? US does, at least. + this->set_flag(Flag::NO_D6_AFTER_LOBBY); + this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH); + break; + case Version::BB_V4: this->set_flag(Flag::NO_D6); this->set_flag(Flag::SAVE_ENABLED); this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH); @@ -60,14 +67,12 @@ void Client::Config::set_flags_for_version(GameVersion version, int64_t sub_vers case 0x20: // DCNTE, possibly also DCv1 JP case 0x21: // DCv1 US - this->set_flag(Flag::IS_DC_V1); this->set_flag(Flag::NO_D6); this->set_flag(Flag::NO_SEND_FUNCTION_CALL); // In the case of DCNTE, the IS_DC_TRIAL_EDITION flag is already set when // we get here break; case 0x23: // DCv1 EU - this->set_flag(Flag::IS_DC_V1); this->set_flag(Flag::NO_D6); this->set_flag(Flag::NO_SEND_FUNCTION_CALL); break; @@ -82,7 +87,7 @@ void Client::Config::set_flags_for_version(GameVersion version, int64_t sub_vers this->set_flag(Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY); this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH); break; - case 0x30: // GC Ep1&2 GameJam demo, GC Ep1&2 JP v1.02, at least one version of PSO XB + case 0x30: // GC Ep1&2 GameJam demo, GC Ep1&2 Trial Edition, GC Ep1&2 JP v1.02, at least one version of XB case 0x31: // GC Ep1&2 US v1.00, GC US v1.01, XB US case 0x34: // GC Ep1&2 JP v1.03 // In the case of GC Trial Edition, the IS_GC_TRIAL_EDITION flag is @@ -105,7 +110,6 @@ void Client::Config::set_flags_for_version(GameVersion version, int64_t sub_vers break; case 0x40: // GC Ep3 JP and Trial Edition this->set_flag(Flag::NO_D6_AFTER_LOBBY); - this->set_flag(Flag::IS_EPISODE_3); this->set_flag(Flag::ENCRYPTED_SEND_FUNCTION_CALL); this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH); // sub_version can't be used to tell JP final and Trial Edition apart; we @@ -114,14 +118,12 @@ void Client::Config::set_flags_for_version(GameVersion version, int64_t sub_vers break; case 0x41: // GC Ep3 US this->set_flag(Flag::NO_D6_AFTER_LOBBY); - this->set_flag(Flag::IS_EPISODE_3); this->set_flag(Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL); this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH); break; case 0x42: // GC Ep3 EU 50Hz case 0x43: // GC Ep3 EU 60Hz this->set_flag(Flag::NO_D6_AFTER_LOBBY); - this->set_flag(Flag::IS_EPISODE_3); this->set_flag(Flag::NO_SEND_FUNCTION_CALL); break; default: @@ -132,7 +134,7 @@ void Client::Config::set_flags_for_version(GameVersion version, int64_t sub_vers Client::Client( shared_ptr server, struct bufferevent* bev, - GameVersion version, + Version version, ServerBehavior server_behavior) : server(server), id(next_id++), @@ -198,7 +200,7 @@ Client::~Client() { } void Client::reschedule_save_game_data_event() { - if (this->version() == GameVersion::BB) { + if (this->version() == Version::BB_V4) { struct timeval tv = usecs_to_timeval(60000000); // 1 minute event_add(this->save_game_data_event.get(), &tv); } @@ -211,39 +213,10 @@ void Client::reschedule_ping_and_timeout_events() { event_add(this->idle_timeout_event.get(), &idle_tv); } -QuestScriptVersion Client::quest_version() const { - switch (this->version()) { - case GameVersion::DC: - if (this->config.check_flag(Flag::IS_DC_TRIAL_EDITION)) { - return QuestScriptVersion::DC_NTE; - } else if (this->config.check_flag(Flag::IS_DC_V1)) { - return QuestScriptVersion::DC_V1; - } else { - return QuestScriptVersion::DC_V2; - } - case GameVersion::PC: - return QuestScriptVersion::PC_V2; - case GameVersion::GC: - if (this->config.check_flag(Flag::IS_GC_TRIAL_EDITION)) { - return QuestScriptVersion::GC_NTE; - } else if (this->config.check_flag(Flag::IS_EPISODE_3)) { - return QuestScriptVersion::GC_EP3; - } else { - return QuestScriptVersion::GC_V3; - } - case GameVersion::XB: - return QuestScriptVersion::XB_V3; - case GameVersion::BB: - return QuestScriptVersion::BB_V4; - default: - throw logic_error("client\'s game version does not have a quest version"); - } -} - void Client::set_license(shared_ptr l) { this->license = l; this->game_data.guild_card_number = this->license->serial_number; - if (this->version() == GameVersion::BB) { + if (this->version() == Version::BB_V4) { this->game_data.bb_username = this->license->bb_username; } } @@ -309,7 +282,7 @@ void Client::dispatch_save_game_data(evutil_socket_t, short, void* ctx) { } void Client::save_game_data() { - if (this->version() != GameVersion::BB) { + if (this->version() != Version::BB_V4) { throw logic_error("save_game_data called for non-BB client"); } if (this->game_data.character(false)) { @@ -322,7 +295,7 @@ void Client::dispatch_send_ping(evutil_socket_t, short, void* ctx) { } void Client::send_ping() { - if (this->version() != GameVersion::PATCH) { + if (!is_patch(this->version())) { this->log.info("Sending ping command"); // The game doesn't use this timestamp; we only use it for debugging purposes be_uint64_t timestamp = now(); diff --git a/src/Client.hh b/src/Client.hh index 06e9efac..f5c7a953 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -30,14 +30,7 @@ struct Client : public std::enable_shared_from_this { // clang-format off // Version-related flags - IS_DC_TRIAL_EDITION = 0x0000000000000001, CHECKED_FOR_DC_V1_PROTOTYPE = 0x0000000000000002, - IS_DC_V1_PROTOTYPE = 0x0000000000000004, - IS_DC_V1 = 0x0000000000000008, - IS_GC_TRIAL_EDITION = 0x0000000000000010, - IS_EP3_TRIAL_EDITION = 0x0000000000000020, - IS_EPISODE_3 = 0x0000000000000040, - IS_BB_PATCH = 0x0000000000000080, NO_D6_AFTER_LOBBY = 0x0000000000000100, NO_D6 = 0x0000000000000200, FORCE_ENGLISH_LANGUAGE_BB = 0x0000000000000400, @@ -114,7 +107,7 @@ struct Client : public std::enable_shared_from_this { this->enabled_flags ^= static_cast(flag); } - void set_flags_for_version(GameVersion version, int64_t sub_version); + void set_flags_for_version(Version version, int64_t sub_version); template void parse_from(const parray& data) { @@ -220,20 +213,19 @@ struct Client : public std::enable_shared_from_this { Client( std::shared_ptr server, struct bufferevent* bev, - GameVersion version, + Version version, ServerBehavior server_behavior); ~Client(); void reschedule_save_game_data_event(); void reschedule_ping_and_timeout_events(); - inline GameVersion version() const { + inline Version version() const { return this->channel.version; } inline uint8_t language() const { return this->channel.language; } - QuestScriptVersion quest_version() const; void set_license(std::shared_ptr l); diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 7163c837..f83c2480 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -4380,6 +4380,7 @@ struct G_Unknown_6x5C { } __packed__; // 6x5D: Drop meseta or stacked item +// On DC NTE, this command has the same format, but is subcommand 6x4F instead. struct G_DropStackedItem_DC_6x5D { G_ClientIDHeader header; diff --git a/src/Episode3/Server.cc b/src/Episode3/Server.cc index 226221bb..04824008 100644 --- a/src/Episode3/Server.cc +++ b/src/Episode3/Server.cc @@ -242,8 +242,7 @@ void Server::send_6xB4x46() const { this->send(cmd46); } -string Server::prepare_6xB6x41_map_definition( - shared_ptr map, uint8_t language, bool is_trial) { +string Server::prepare_6xB6x41_map_definition(shared_ptr map, uint8_t language, bool is_trial) { auto vm = map->version(language); const auto& compressed = vm->compressed(is_trial); @@ -255,7 +254,7 @@ string Server::prepare_6xB6x41_map_definition( return std::move(w.str()); } -void Server::send_commands_for_joining_spectator(Channel& ch, bool is_trial) const { +void Server::send_commands_for_joining_spectator(Channel& ch) const { bool should_send_state = true; if (this->setup_phase == SetupPhase::REGISTRATION) { // If registration is still in progress, we only need to send the map data @@ -267,7 +266,8 @@ void Server::send_commands_for_joining_spectator(Channel& ch, bool is_trial) con } if (this->last_chosen_map) { - string data = this->prepare_6xB6x41_map_definition(this->last_chosen_map, ch.language, is_trial); + string data = this->prepare_6xB6x41_map_definition( + this->last_chosen_map, ch.language, (ch.version == Version::GC_EP3_TRIAL_EDITION)); this->log().info("Sending %c version of map %08" PRIX32, char_for_language_code(ch.language), this->last_chosen_map->map_number); ch.send(0x6C, 0x00, data); } @@ -2349,7 +2349,7 @@ void Server::send_6xB6x41_to_all_clients() const { } if (map_commands_by_language[c->language()].empty()) { map_commands_by_language[c->language()] = this->prepare_6xB6x41_map_definition( - this->last_chosen_map, c->language(), l->check_flag(Lobby::Flag::IS_EP3_TRIAL)); + this->last_chosen_map, c->language(), (l->base_version == Version::GC_EP3_TRIAL_EDITION)); } this->log().info("Sending %c version of map %08" PRIX32, char_for_language_code(c->language()), this->last_chosen_map->map_number); send_command(c, 0x6C, 0x00, map_commands_by_language[c->language()]); diff --git a/src/Episode3/Server.hh b/src/Episode3/Server.hh index b2d6d9a2..d63bdd9d 100644 --- a/src/Episode3/Server.hh +++ b/src/Episode3/Server.hh @@ -112,7 +112,7 @@ public: this->send(&cmd, cmd.header.size * 4); } void send(const void* data, size_t size) const; - void send_commands_for_joining_spectator(Channel& ch, bool is_trial) const; + void send_commands_for_joining_spectator(Channel& ch) const; void force_battle_result(uint8_t surrendered_client_id, bool set_winner); void force_destroy_field_character(uint8_t client_id, size_t set_index); diff --git a/src/Episode3/Tournament.cc b/src/Episode3/Tournament.cc index ad3daa15..05994c85 100644 --- a/src/Episode3/Tournament.cc +++ b/src/Episode3/Tournament.cc @@ -701,10 +701,7 @@ void Tournament::send_all_state_updates() const { // Note: The last check here is to make sure the client is still linked // with this instance of the tournament - an intervening shell command // `reload ep3` could have changed the client's linkage - if (c && - c->config.check_flag(Client::Flag::IS_EPISODE_3) && - !c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION) && - (c->ep3_tournament_team.lock() == team)) { + if (c && (c->version() == Version::GC_EP3) && (c->ep3_tournament_team.lock() == team)) { send_ep3_confirm_tournament_entry(c, this->shared_from_this()); } } @@ -715,10 +712,7 @@ void Tournament::send_all_state_updates_on_deletion() const { for (const auto& team : this->teams) { for (const auto& player : team->players) { auto c = player.client.lock(); - if (c && - c->config.check_flag(Client::Flag::IS_EPISODE_3) && - !c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION) && - (c->ep3_tournament_team.lock() == team)) { + if (c && (c->version() == Version::GC_EP3) && (c->ep3_tournament_team.lock() == team)) { send_ep3_confirm_tournament_entry(c, nullptr); } } @@ -916,7 +910,7 @@ shared_ptr TournamentIndex::team_for_serial_number(uint32_t se } void TournamentIndex::link_client(shared_ptr c) { - if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (!is_ep3(c->version())) { return; } @@ -927,7 +921,7 @@ void TournamentIndex::link_client(shared_ptr c) { if (player.serial_number == c->license->serial_number) { c->ep3_tournament_team = team; player.client = c; - if (!c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION)) { + if (c->version() == Version::GC_EP3) { send_ep3_confirm_tournament_entry(c, tourn); } return; @@ -936,7 +930,7 @@ void TournamentIndex::link_client(shared_ptr c) { throw logic_error("tournament team found for player, but player not found on team"); } else { c->ep3_tournament_team.reset(); - if (!c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION)) { + if (c->version() == Version::GC_EP3) { send_ep3_confirm_tournament_entry(c, nullptr); } } diff --git a/src/ItemCreator.cc b/src/ItemCreator.cc index a83c5ac1..93ed3dd0 100644 --- a/src/ItemCreator.cc +++ b/src/ItemCreator.cc @@ -16,14 +16,14 @@ ItemCreator::ItemCreator( shared_ptr weapon_random_set, shared_ptr tekker_adjustment_set, shared_ptr item_parameter_table, - GameVersion version, + Version version, Episode episode, GameMode mode, uint8_t difficulty, uint8_t section_id, uint32_t random_seed, shared_ptr restrictions) - : log(string_printf("[ItemCreator:%s/%s/%s/%c/%hhu] ", name_for_version(version), abbreviation_for_episode(episode), abbreviation_for_mode(mode), abbreviation_for_difficulty(difficulty), section_id)), + : log(string_printf("[ItemCreator:%s/%s/%s/%c/%hhu] ", name_for_enum(version), abbreviation_for_episode(episode), abbreviation_for_mode(mode), abbreviation_for_difficulty(difficulty), section_id)), version(version), episode(episode), mode(mode), @@ -786,17 +786,27 @@ void ItemCreator::generate_unit_stars_tables() { size_t star_base_index; uint8_t num_units; switch (this->version) { - case GameVersion::DC: - case GameVersion::PC: + case Version::PC_PATCH: + case Version::BB_PATCH: + case Version::GC_NTE: + throw logic_error("unknown parameters for version"); + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + throw logic_error("ItemCreator cannot be created for Episode 3 games"); + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::PC_V2: star_base_index = 0x1D1; num_units = 0x44; break; - case GameVersion::GC: - case GameVersion::XB: + case Version::GC_V3: + case Version::XB_V3: star_base_index = 0x2AF; num_units = 0x48; break; - case GameVersion::BB: + case Version::BB_V4: star_base_index = 0x37D; num_units = 0x64; break; diff --git a/src/ItemCreator.hh b/src/ItemCreator.hh index 082cb15c..896f7205 100644 --- a/src/ItemCreator.hh +++ b/src/ItemCreator.hh @@ -19,7 +19,7 @@ public: std::shared_ptr weapon_random_set, std::shared_ptr tekker_adjustment_set, std::shared_ptr item_parameter_table, - GameVersion version, + Version version, Episode episode, GameMode mode, uint8_t difficulty, @@ -50,7 +50,7 @@ public: private: PrefixedLogger log; - GameVersion version; + Version version; Episode episode; GameMode mode; uint8_t difficulty; @@ -77,7 +77,7 @@ private: std::unordered_set destroyed_boxes; inline bool is_v3() const { - return (this->version != GameVersion::DC) && (this->version != GameVersion::PC); + return !is_v1_or_v2(this->version); } bool are_rare_drops_allowed() const; diff --git a/src/ItemData.cc b/src/ItemData.cc index 0a60bb7e..b10be8e9 100644 --- a/src/ItemData.cc +++ b/src/ItemData.cc @@ -287,11 +287,10 @@ void ItemData::add_mag_photon_blast(uint8_t pb_num) { } } -void ItemData::decode_for_version(GameVersion from_version) { - bool is_v2 = (from_version == GameVersion::DC) || (from_version == GameVersion::PC); - +void ItemData::decode_for_version(Version from_version) { uint8_t encoded_v2_data = this->get_encoded_v2_data(); - bool should_decode_v2_data = is_v2 && (encoded_v2_data != 0x00) && this->has_encoded_v2_data(); + bool should_decode_v2_data = (is_v1(from_version) || is_v2(from_version)) && + (encoded_v2_data != 0x00) && this->has_encoded_v2_data(); switch (this->data1[0]) { case 0x00: @@ -314,12 +313,12 @@ void ItemData::decode_for_version(GameVersion from_version) { this->data1[1] = encoded_v2_data + 0x2B; } - if (from_version == GameVersion::GC) { + if (is_big_endian(from_version)) { // PSO GC erroneously byteswaps the data2d field, even though it's actually // just four individual bytes, so we correct for that here. this->data2d = bswap32(this->data2d); - } else if (from_version == GameVersion::DC || from_version == GameVersion::PC) { + } else if (is_v1(from_version) || is_v2(from_version)) { // PSO PC encodes mags in a tediously annoying manner. The first four bytes are the same, but then... // V2: pHHHHHHHHHHHHHHc pIIIIIIIIIIIIIIc JJJJJJJJJJJJJJJc KKKKKKKKKKKKKKKc QQQQQQQQ QQQQQQQQ YYYYYYYY pYYYYYYY // V3: HHHHHHHHHHHHHHHH IIIIIIIIIIIIIIII JJJJJJJJJJJJJJJJ KKKKKKKKKKKKKKKK YYYYYYYY QQQQQQQQ PPPPPPPP CCCCCCCC @@ -369,9 +368,8 @@ void ItemData::decode_for_version(GameVersion from_version) { } } -void ItemData::encode_for_version(GameVersion to_version, shared_ptr item_parameter_table) { - bool is_v2 = (to_version == GameVersion::DC) || (to_version == GameVersion::PC); - bool should_encode_v2_data = is_v2 && !this->has_encoded_v2_data(); +void ItemData::encode_for_version(Version to_version, shared_ptr item_parameter_table) { + bool should_encode_v2_data = (is_v1(to_version) || is_v2(to_version)) && !this->has_encoded_v2_data(); switch (this->data1[0]) { case 0x00: @@ -410,9 +408,9 @@ void ItemData::encode_for_version(GameVersion to_version, shared_ptrdata2d = bswap32(this->data2d); - } else if (to_version == GameVersion::DC || to_version == GameVersion::PC) { + } else if (is_v1(to_version) || is_v2(to_version)) { this->data1w[2] = (this->data1w[2] & 0x7FFE) | ((this->data2[2] << 14) & 0x8000) | (this->data2[3] & 1); this->data1w[3] = (this->data1w[3] & 0x7FFE) | ((this->data2[2] << 13) & 0x8000) | ((this->data2[3] >> 1) & 1); this->data1w[4] = (this->data1w[4] & 0xFFFE) | ((this->data2[3] >> 2) & 1); diff --git a/src/ItemData.hh b/src/ItemData.hh index 9494dbb4..c003873c 100644 --- a/src/ItemData.hh +++ b/src/ItemData.hh @@ -152,8 +152,8 @@ struct ItemData { // 0x14 bytes uint8_t mag_photon_blast_for_slot(uint8_t slot) const; bool mag_has_photon_blast_in_any_slot(uint8_t pb_num) const; void add_mag_photon_blast(uint8_t pb_num); - void decode_for_version(GameVersion version); - void encode_for_version(GameVersion version, std::shared_ptr item_parameter_table); + void decode_for_version(Version version); + void encode_for_version(Version version, std::shared_ptr item_parameter_table); uint8_t get_encoded_v2_data() const; bool has_encoded_v2_data() const; diff --git a/src/ItemNameIndex.cc b/src/ItemNameIndex.cc index 6a0468fb..e2f79517 100644 --- a/src/ItemNameIndex.cc +++ b/src/ItemNameIndex.cc @@ -106,7 +106,7 @@ const array name_for_s_rank_special = { }; std::string ItemNameIndex::describe_item( - GameVersion version, + Version version, const ItemData& item, std::shared_ptr item_parameter_table) const { if (item.data1[0] == 0x04) { @@ -171,20 +171,12 @@ std::string ItemNameIndex::describe_item( try { auto meta = this->primary_identifier_index.at(primary_identifier); const string* name; - switch (version) { - case GameVersion::DC: - case GameVersion::PC: - name = &meta->v2_name; - break; - case GameVersion::GC: - case GameVersion::XB: - name = &meta->v3_name; - break; - case GameVersion::BB: - name = &meta->v4_name; - break; - default: - throw logic_error("invalid game version"); + if (is_v4(version)) { + name = &meta->v4_name; + } else if (is_v3(version)) { + name = &meta->v3_name; + } else { + name = &meta->v2_name; } if (name->empty()) { throw out_of_range("item does not exist"); @@ -368,7 +360,7 @@ std::string ItemNameIndex::describe_item( } } -ItemData ItemNameIndex::parse_item_description(GameVersion version, const std::string& desc) const { +ItemData ItemNameIndex::parse_item_description(Version version, const std::string& desc) const { try { return this->parse_item_description_phase(version, desc, false); } catch (const exception& e1) { @@ -406,7 +398,7 @@ ItemData ItemNameIndex::parse_item_description(GameVersion version, const std::s } } -ItemData ItemNameIndex::parse_item_description_phase(GameVersion version, const std::string& description, bool skip_special) const { +ItemData ItemNameIndex::parse_item_description_phase(Version version, const std::string& description, bool skip_special) const { ItemData ret; ret.data1d.clear(0); ret.id = 0xFFFFFFFF; @@ -468,20 +460,12 @@ ItemData ItemNameIndex::parse_item_description_phase(GameVersion version, const } const map>* name_index; - switch (version) { - case GameVersion::DC: - case GameVersion::PC: - name_index = &this->v2_name_index; - break; - case GameVersion::GC: - case GameVersion::XB: - name_index = &this->v3_name_index; - break; - case GameVersion::BB: - name_index = &this->v4_name_index; - break; - default: - throw logic_error("invalid game version"); + if (is_v4(version)) { + name_index = &this->v4_name_index; + } else if (is_v3(version)) { + name_index = &this->v3_name_index; + } else { + name_index = &this->v2_name_index; } auto name_it = name_index->lower_bound(desc); diff --git a/src/ItemNameIndex.hh b/src/ItemNameIndex.hh index ef98e8f9..26cee92f 100644 --- a/src/ItemNameIndex.hh +++ b/src/ItemNameIndex.hh @@ -17,13 +17,13 @@ public: ItemNameIndex(JSON&& v2_names, JSON&& v3_names, JSON&& v4_names); std::string describe_item( - GameVersion version, + Version version, const ItemData& item, std::shared_ptr item_parameter_table = nullptr) const; - ItemData parse_item_description(GameVersion version, const std::string& description) const; + ItemData parse_item_description(Version version, const std::string& description) const; private: - ItemData parse_item_description_phase(GameVersion version, const std::string& description, bool skip_special) const; + ItemData parse_item_description_phase(Version version, const std::string& description, bool skip_special) const; struct ItemMetadata { uint32_t primary_identifier; diff --git a/src/Items.cc b/src/Items.cc index a938c279..9cfd5cd6 100644 --- a/src/Items.cc +++ b/src/Items.cc @@ -14,7 +14,8 @@ void player_use_item(shared_ptr c, size_t item_index) { // On PC (and presumably DC), the client sends a 6x29 after this to delete the // used item. On GC and later versions, this does not happen, so we should // delete the item here. - bool should_delete_item = (c->version() != GameVersion::DC) && (c->version() != GameVersion::PC); + bool is_v3_or_later = is_v3(c->version()) || is_v4(c->version()); + bool should_delete_item = is_v3_or_later; auto player = c->game_data.character(); auto& item = player->inventory.items[item_index]; @@ -43,7 +44,7 @@ void player_use_item(shared_ptr c, size_t item_index) { // be no way to disable this behavior, so there's no way for the server to // get an accurate picture of what's actually in the player's inventory, so // there's no way to know if we would be enforcing the correct grind limit. - if ((c->version() != GameVersion::DC) && (c->version() != GameVersion::PC)) { + if (is_v3_or_later) { auto item_parameter_table = s->item_parameter_table_for_version(c->version()); auto weapon_def = item_parameter_table->get_weapon(weapon.data.data1[1], weapon.data.data1[2]); if (weapon.data.data1[3] >= weapon_def.max_grind) { @@ -54,7 +55,6 @@ void player_use_item(shared_ptr c, size_t item_index) { } else if ((item_identifier & 0xFFFF00) == 0x030B00) { // Material auto p = c->game_data.character(); - bool track_non_hp_tp_materials = (c->version() != GameVersion::DC) && (c->version() != GameVersion::PC); using Type = PSOBBCharacterFile::MaterialType; Type type; @@ -83,7 +83,7 @@ void player_use_item(shared_ptr c, size_t item_index) { break; case 6: // Hit Material (v1/v2) or Luck Material (v3/v4) type = Type::LUCK; - if (c->version() == GameVersion::DC || c->version() == GameVersion::PC) { + if (!is_v3_or_later) { // Hit material doesn't exist on v3/v4, but we'll ignore type anyway // in this case because track_non_hp_tp_materials is false p->disp.stats.char_stats.ata += 2; @@ -93,7 +93,7 @@ void player_use_item(shared_ptr c, size_t item_index) { break; case 7: // Luck Material (v1/v2) type = Type::LUCK; - if (c->version() == GameVersion::DC || c->version() == GameVersion::PC) { + if (!is_v3_or_later) { p->disp.stats.char_stats.lck += 2; } else { throw runtime_error("unknown material used"); @@ -102,7 +102,7 @@ void player_use_item(shared_ptr c, size_t item_index) { default: throw runtime_error("unknown material used"); } - if (track_non_hp_tp_materials || (type == Type::HP) || (type == Type::TP)) { + if (is_v3_or_later || (type == Type::HP) || (type == Type::TP)) { p->set_material_usage(type, p->get_material_usage(type) + 1); } @@ -256,7 +256,7 @@ void player_use_item(shared_ptr c, size_t item_index) { if (should_delete_item) { // Allow overdrafting meseta if the client is not BB, since the server isn't // informed when meseta is added or removed from the bank. - player->remove_item(item.data.id, 1, c->version() != GameVersion::BB); + player->remove_item(item.data.id, 1, !is_v4(c->version())); } } diff --git a/src/Lobby.cc b/src/Lobby.cc index 6ae01893..9c3b009a 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -17,8 +17,8 @@ Lobby::Lobby(shared_ptr s, uint32_t id) min_level(0), max_level(0xFFFFFFFF), next_game_item_id(0x00810000), - base_version(GameVersion::GC), - allowed_versions(0xFFFF), + base_version(Version::GC_V3), + allowed_versions(0x0000), section_id(0), episode(Episode::NONE), mode(GameMode::NORMAL), @@ -56,19 +56,36 @@ void Lobby::create_item_creator() { shared_ptr rare_item_set; shared_ptr common_item_set; - if (this->base_version == GameVersion::BB) { - common_item_set = s->common_item_set_v3; - rare_item_set = s->rare_item_sets.at("rare-table-v4"); - } else if (this->base_version == GameVersion::GC || this->base_version == GameVersion::XB) { - common_item_set = s->common_item_set_v3; - rare_item_set = s->rare_item_sets.at("rare-table-v3"); - } else if (!this->check_flag(Lobby::Flag::USE_DCV1_RARE_TABLE)) { - common_item_set = s->common_item_set_v2; - rare_item_set = s->rare_item_sets.at("rare-table-v2"); - } else { - // TODO: We should probably have a v1 common item set at some point too - common_item_set = s->common_item_set_v2; - rare_item_set = s->rare_item_sets.at("rare-table-v1"); + switch (this->base_version) { + case Version::PC_PATCH: + case Version::BB_PATCH: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + throw runtime_error("cannot create item creator for this base version"); + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + // TODO: We should probably have a v1 common item set at some point too + common_item_set = s->common_item_set_v2; + rare_item_set = s->rare_item_sets.at("rare-table-v1"); + break; + case Version::DC_V2: + case Version::PC_V2: + common_item_set = s->common_item_set_v2; + rare_item_set = s->rare_item_sets.at("rare-table-v2"); + break; + case Version::GC_NTE: + case Version::GC_V3: + case Version::XB_V3: + common_item_set = s->common_item_set_v3; + rare_item_set = s->rare_item_sets.at("rare-table-v3"); + break; + case Version::BB_V4: + common_item_set = s->common_item_set_v3; + rare_item_set = s->rare_item_sets.at("rare-table-v4"); + break; + default: + throw logic_error("invalid lobby base version"); } this->item_creator.reset(new ItemCreator( common_item_set, @@ -95,7 +112,7 @@ void Lobby::create_ep3_server() { this->log.info("Recreating Episode 3 server state"); } auto tourn = this->tournament_match ? this->tournament_match->tournament.lock() : nullptr; - bool is_trial = this->check_flag(Lobby::Flag::IS_EP3_TRIAL); + bool is_trial = this->base_version == Version::GC_EP3_TRIAL_EDITION; Episode3::Server::Options options = { .card_index = is_trial ? s->ep3_card_index_trial : s->ep3_card_index, .map_index = s->ep3_map_index, @@ -355,9 +372,6 @@ uint32_t Lobby::generate_item_id(uint8_t client_id) { } void Lobby::on_item_id_generated_externally(uint32_t item_id) { - if (this->base_version == GameVersion::BB) { - throw logic_error("BB games cannot have externally-generated item IDs"); - } // Note: The client checks for the range (0x00010000, 0x02010000) here, but // server-side item drop logic uses 0x00810000 as its base ID, so we restrict // the range further here. diff --git a/src/Lobby.hh b/src/Lobby.hh index dcff4404..46726753 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -37,17 +37,14 @@ struct Lobby : public std::enable_shared_from_this { IS_SPECTATOR_TEAM = 0x00002000, // episode must be EP3 also SPECTATORS_FORBIDDEN = 0x00004000, START_BATTLE_PLAYER_IMMEDIATELY = 0x00008000, - IS_EP3_TRIAL = 0x00010000, DROPS_ENABLED = 0x00020000, CANNOT_CHANGE_DROPS_ENABLED = 0x00040000, CANNOT_CHANGE_ITEM_TABLE = 0x00080000, CANNOT_CHANGE_CHEAT_MODE = 0x00100000, - USE_DCV1_RARE_TABLE = 0x00200000, // Flags used only for lobbies PUBLIC = 0x01000000, DEFAULT = 0x02000000, - V2_AND_LATER = 0x04000000, // Lobby does not appear on v1 IS_OVERFLOW = 0x08000000, }; @@ -73,10 +70,10 @@ struct Lobby : public std::enable_shared_from_this { parray variations; // Game config - GameVersion base_version; + Version base_version; // Bits in allowed_versions specify who is allowed to join this game. The // bits are indexed as (1 << version), where version is a value from the - // QuestScriptVersion enum. + // Version enum. uint16_t allowed_versions; uint8_t section_id; Episode episode; @@ -164,10 +161,10 @@ struct Lobby : public std::enable_shared_from_this { return this->episode == Episode::EP3; } - [[nodiscard]] inline bool version_is_allowed(QuestScriptVersion v) const { + [[nodiscard]] inline bool version_is_allowed(Version v) const { return this->allowed_versions & (1 << static_cast(v)); } - inline void allow_version(QuestScriptVersion v) { + inline void allow_version(Version v) { this->allowed_versions |= (1 << static_cast(v)); } diff --git a/src/Main.cc b/src/Main.cc index 9e97ed7b..c391897b 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -48,6 +49,8 @@ using namespace std; bool use_terminal_colors = false; +void print_usage(); + template vector parse_int_vector(const JSON& o) { vector ret; @@ -84,29 +87,215 @@ void drop_privileges(const string& username) { config_log.info("Switched to user %s (%d:%d)", username.c_str(), pw->pw_uid, pw->pw_gid); } -void print_usage() { - fputs("\ -Usage:\n\ - newserv [ACTION [OPTIONS...]]\n\ -\n\ -If ACTION is not specified, newserv runs in server mode. PSO clients can\n\ -connect normally, join lobbies, play games, and use the proxy server. See\n\ -README.md and system/config.json for more information.\n\ -\n\ -When ACTION is given, newserv will do things other than running the server.\n\ -\n\ -Some actions accept input and/or output filenames; see the descriptions below\n\ -for details. If INPUT-FILENAME is missing or is '-', newserv reads from stdin.\n\ -If OUTPUT-FILENAME is missing and the input is not from stdin, newserv writes\n\ -the output to INPUT-FILENAME.dec or a similarly-named file; if OUTPUT-FILENAME\n\ -is '-', newserv writes the output to stdout. If stdout is a terminal and the\n\ -output is not text, the data written to stdout is formatted in a hex/ASCII\n\ -view; in any other case, the raw output is written to stdout, which (for most\n\ -actions) may include arbitrary binary data.\n\ -\n\ -The actions are:\n\ +Version get_cli_version(Arguments& args) { + if (args.get("pc-patch")) { + return Version::PC_PATCH; + } else if (args.get("bb-patch")) { + return Version::BB_PATCH; + } else if (args.get("dc-nte")) { + return Version::DC_NTE; + } else if (args.get("dc-proto")) { + return Version::DC_V1_12_2000_PROTOTYPE; + } else if (args.get("dc-v1")) { + return Version::DC_V1; + } else if (args.get("dc-v2") || args.get("dc")) { + return Version::DC_V2; + } else if (args.get("pc")) { + return Version::PC_V2; + } else if (args.get("gc-nte")) { + return Version::GC_NTE; + } else if (args.get("gc")) { + return Version::GC_V3; + } else if (args.get("xb")) { + return Version::XB_V3; + } else if (args.get("ep3-trial")) { + return Version::GC_EP3_TRIAL_EDITION; + } else if (args.get("ep3")) { + return Version::GC_EP3; + } else if (args.get("bb")) { + return Version::BB_V4; + } else { + throw runtime_error("a version option is required"); + } +} + +string read_input_data(Arguments& args) { + const string& input_filename = args.get(1, false); + + string data; + if (!input_filename.empty() && (input_filename != "-")) { + data = load_file(input_filename); + } else { + data = read_all(stdin); + } + if (args.get("parse-data")) { + data = parse_data_string(data, nullptr, ParseDataFlags::ALLOW_FILES); + } + return data; +} + +bool is_text_extension(const char* extension) { + return (!strcmp(extension, "txt") || !strcmp(extension, "json")); +} + +void write_output_data(Arguments& args, const void* data, size_t size, const char* extension) { + const string& input_filename = args.get(1, false); + const string& output_filename = args.get(2, false); + + if (!output_filename.empty() && (output_filename != "-")) { + // If the output is to a specified file, write it there + save_file(output_filename, data, size); + + } else if (output_filename.empty() && !input_filename.empty() && (input_filename != "-")) { + // If no output filename is given and an input filename is given, write to + // . + if (!extension) { + throw runtime_error("an output filename is required"); + } + string filename = input_filename; + filename += "."; + filename += extension; + save_file(filename, data, size); + + } else if (isatty(fileno(stdout)) && (!extension || !is_text_extension(extension))) { + // If stdout is a terminal and the data is not known to be text, use + // print_data to write the result + print_data(stdout, data, size); + fflush(stdout); + + } else { + // If stdout is not a terminal, write the data as-is + fwritex(stdout, data, size); + fflush(stdout); + } +} + +struct Action; +unordered_map all_actions; +vector action_order; + +struct Action { + const char* name; + const char* help_text; // May be null + function run; + + Action( + const char* name, + const char* help_text, + function run) + : name(name), + help_text(help_text), + run(run) { + auto emplace_ret = all_actions.emplace(this->name, this); + if (!emplace_ret.second) { + throw logic_error(string_printf("multiple actions with the same name: %s", this->name)); + } + action_order.emplace_back(this); + } +}; + +Action a_help( + "help", "\ help\n\ - You\'re reading it now.\n\ + You\'re reading it now.\n", + +[](Arguments&) -> void { + print_usage(); + }); + +static void a_compress_decompress_fn(Arguments& args) { + const auto& action = args.get(0); + bool is_prs = ends_with(action, "-prs"); + bool is_bc0 = ends_with(action, "-bc0"); + bool is_pr2 = ends_with(action, "-pr2"); + bool is_decompress = starts_with(action, "decompress-"); + bool is_big_endian = args.get("big-endian"); + bool is_optimal = args.get("optimal"); + int8_t compression_level = args.get("compression-level", 0); + size_t bytes = args.get("bytes", 0); + string seed = args.get("seed"); + + string data = read_input_data(args); + + size_t pr2_expected_size = 0; + if (is_decompress && is_pr2) { + auto decrypted = is_big_endian ? decrypt_pr2_data(data) : decrypt_pr2_data(data); + pr2_expected_size = decrypted.decompressed_size; + data = std::move(decrypted.compressed_data); + } + + size_t input_bytes = data.size(); + auto progress_fn = [&](auto, size_t input_progress, size_t, size_t output_progress) -> void { + float progress = static_cast(input_progress * 100) / input_bytes; + float size_ratio = static_cast(output_progress * 100) / input_progress; + fprintf(stderr, "... %zu/%zu (%g%%) => %zu (%g%%) \r", + input_progress, input_bytes, progress, output_progress, size_ratio); + }; + auto optimal_progress_fn = [&](auto phase, size_t input_progress, size_t input_bytes, size_t output_progress) -> void { + const char* phase_name = name_for_enum(phase); + float progress = static_cast(input_progress * 100) / input_bytes; + float size_ratio = static_cast(output_progress * 100) / input_progress; + fprintf(stderr, "... [%s] %zu/%zu (%g%%) => %zu (%g%%) \r", + phase_name, input_progress, input_bytes, progress, output_progress, size_ratio); + }; + + uint64_t start = now(); + if (!is_decompress && (is_prs || is_pr2)) { + if (is_optimal) { + data = prs_compress_optimal(data.data(), data.size(), optimal_progress_fn); + } else { + data = prs_compress(data, compression_level, progress_fn); + } + } else if (is_decompress && (is_prs || is_pr2)) { + data = prs_decompress(data, bytes, (bytes != 0)); + } else if (!is_decompress && is_bc0) { + if (is_optimal) { + data = bc0_compress_optimal(data.data(), data.size(), optimal_progress_fn); + } else if (compression_level < 0) { + data = bc0_encode(data.data(), data.size()); + } else { + data = bc0_compress(data, progress_fn); + } + } else if (is_decompress && is_bc0) { + data = bc0_decompress(data); + } else { + throw logic_error("invalid behavior"); + } + uint64_t end = now(); + string time_str = format_duration(end - start); + + float size_ratio = static_cast(data.size() * 100) / input_bytes; + double bytes_per_sec = input_bytes / (static_cast(end - start) / 1000000.0); + string bytes_per_sec_str = format_size(bytes_per_sec); + log_info("%zu (0x%zX) bytes input => %zu (0x%zX) bytes output (%g%%) in %s (%s / sec)", + input_bytes, input_bytes, data.size(), data.size(), size_ratio, time_str.c_str(), bytes_per_sec_str.c_str()); + + if (is_decompress && is_pr2 && (data.size() != pr2_expected_size)) { + log_warning("Result data size (%zu bytes) does not match expected size from PR2 header (%zu bytes)", data.size(), pr2_expected_size); + } else if (!is_decompress && is_pr2) { + uint32_t pr2_seed = seed.empty() ? random_object() : stoul(seed, nullptr, 16); + data = is_big_endian + ? encrypt_pr2_data(data, input_bytes, pr2_seed) + : encrypt_pr2_data(data, input_bytes, pr2_seed); + } + + const char* extension; + if (is_decompress) { + extension = "dec"; + } else if (is_prs) { + extension = "prs"; + } else if (is_bc0) { + extension = "bc0"; + } else if (is_pr2) { + extension = "pr2"; + } else { + throw logic_error("unknown action"); + } + write_output_data(args, data.data(), data.size(), extension); +} + +Action a_compress_prs("compress-prs", nullptr, a_compress_decompress_fn); +Action a_compress_bc0("compress-bc0", nullptr, a_compress_decompress_fn); +Action a_compress_pr2("compress-pr2", "\ compress-prs [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ compress-pr2 [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ compress-bc0 [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ @@ -121,22 +310,113 @@ The actions are:\n\ in valid PRS data which is about 9/8 the size of the input.\n\ There is also a compressor which produces the absolute smallest output\n\ size, but uses much more memory and CPU time. To use this compressor, use\n\ - the --optimal option.\n\ + the --optimal option.\n", + a_compress_decompress_fn); +Action a_decompress_prs("decompress-prs", nullptr, a_compress_decompress_fn); +Action a_decompress_bc0("decompress-bc0", nullptr, a_compress_decompress_fn); +Action a_decompress_pr2("decompress-pr2", "\ decompress-prs [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ decompress-pr2 [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ decompress-bc0 [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ - Decompress data compressed using the PRS, PR2, or BC0 algorithms.\n\ - recompress-prs-optimal [INPUT-AND-OUTPUT-FILENAME]\n\ - Recompress the input PRS file optimally, overwriting it with the optimally-\n\ - compressed result.\n\ + Decompress data compressed using the PRS, PR2, or BC0 algorithms.\n", + a_compress_decompress_fn); + +Action a_prs_size( + "prs-size", "\ prs-size [INPUT-FILENAME]\n\ Compute the decompressed size of the PRS-compressed input data, but don\'t\n\ - write the decompressed data anywhere.\n\ + write the decompressed data anywhere.\n", + +[](Arguments& args) { + string data = read_input_data(args); + size_t input_bytes = data.size(); + size_t output_bytes = prs_decompress_size(data); + log_info("%zu (0x%zX) bytes input => %zu (0x%zX) bytes output", + input_bytes, input_bytes, output_bytes, output_bytes); + }); + +Action a_disassemble_prs( + "disassemble-prs", nullptr, +[](Arguments& args) { + prs_disassemble(stdout, read_input_data(args)); + }); +Action a_disassemble_bc0( + "disassemble-bc0", "\ disassemble-prs [INPUT-FILENAME]\n\ disassemble-bc0 [INPUT-FILENAME]\n\ Write a textual representation of the commands contained in a PRS or BC0\n\ command stream. The output is written to stdout. This is mainly useful for\n\ - debugging the compressors and decompressors themselves.\n\ + debugging the compressors and decompressors themselves.\n", + +[](Arguments& args) { + bc0_disassemble(stdout, read_input_data(args)); + }); + +static void a_encrypt_decrypt_fn(Arguments& args) { + bool is_decrypt = (args.get(0) == "decrypt-data"); + string seed = args.get("seed"); + bool is_big_endian = args.get("big-endian"); + auto version = get_cli_version(args); + + shared_ptr crypt; + switch (version) { + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::PC_V2: + case Version::GC_NTE: + crypt.reset(new PSOV2Encryption(stoul(seed, nullptr, 16))); + break; + case Version::GC_V3: + case Version::XB_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + crypt.reset(new PSOV3Encryption(stoul(seed, nullptr, 16))); + break; + case Version::BB_V4: { + string key_name = args.get("key"); + if (key_name.empty()) { + throw runtime_error("the --key option is required for BB"); + } + seed = parse_data_string(seed, nullptr, ParseDataFlags::ALLOW_FILES); + auto key = load_object_file("system/blueburst/keys/" + key_name + ".nsk"); + crypt.reset(new PSOBBEncryption(key, seed.data(), seed.size())); + break; + } + default: + throw logic_error("invalid game version"); + } + + string data = read_input_data(args); + + size_t original_size = data.size(); + data.resize((data.size() + 7) & (~7), '\0'); + + if (is_big_endian) { + uint32_t* dwords = reinterpret_cast(data.data()); + for (size_t x = 0; x < (data.size() >> 2); x++) { + dwords[x] = bswap32(dwords[x]); + } + } + + if (is_decrypt) { + crypt->decrypt(data.data(), data.size()); + } else { + crypt->encrypt(data.data(), data.size()); + } + + if (is_big_endian) { + uint32_t* dwords = reinterpret_cast(data.data()); + for (size_t x = 0; x < (data.size() >> 2); x++) { + dwords[x] = bswap32(dwords[x]); + } + } + + data.resize(original_size); + + write_output_data(args, data.data(), data.size(), "dec"); +} + +Action a_encrypt_data("encrypt-data", nullptr, a_encrypt_decrypt_fn); +Action a_decrypt_data("decrypt-data", "\ encrypt-data [INPUT-FILENAME [OUTPUT-FILENAME] [OPTIONS...]]\n\ decrypt-data [INPUT-FILENAME [OUTPUT-FILENAME] [OPTIONS...]]\n\ Encrypt or decrypt data using PSO\'s standard network protocol encryption.\n\ @@ -147,7 +427,47 @@ The actions are:\n\ file in system/blueburst/keys (without the directory or .nsk extension).\n\ For non-BB ciphers, the --big-endian option applies the cipher masks as\n\ big-endian instead of little-endian, which is necessary for some GameCube\n\ - file formats.\n\ + file formats.\n", + a_encrypt_decrypt_fn); + +static void a_encrypt_decrypt_trivial_fn(Arguments& args) { + bool is_decrypt = (args.get(0) == "decrypt-trivial-data"); + string seed = args.get("seed"); + + if (seed.empty() && !is_decrypt) { + throw logic_error("--seed is required when encrypting data"); + } + string data = read_input_data(args); + uint8_t basis; + if (seed.empty()) { + uint8_t best_seed = 0x00; + size_t best_seed_score = 0; + for (size_t z = 0; z < 0x100; z++) { + string decrypted = data; + decrypt_trivial_gci_data(decrypted.data(), decrypted.size(), z); + size_t score = 0; + for (size_t x = 0; x < decrypted.size(); x++) { + if (decrypted[x] == '\0') { + score++; + } + } + if (score > best_seed_score) { + best_seed = z; + best_seed_score = score; + } + } + fprintf(stderr, "Basis appears to be %02hhX (%zu zero bytes in output)\n", + best_seed, best_seed_score); + basis = best_seed; + } else { + basis = stoul(seed, nullptr, 16); + } + decrypt_trivial_gci_data(data.data(), data.size(), basis); + write_output_data(args, data.data(), data.size(), "dec"); +} + +Action a_encrypt_trivial_data("encrypt-trivial-data", nullptr, a_encrypt_decrypt_trivial_fn); +Action a_decrypt_trivial_data("decrypt-trivial-data", "\ encrypt-trivial-data --seed=BASIS [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ decrypt-trivial-data [--seed=BASIS] [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ Encrypt or decrypt data using the Episode 3 trivial algorithm. When\n\ @@ -155,1002 +475,219 @@ The actions are:\n\ specified in hexadecimal. When decrypting, BASIS should be specified the\n\ same way, but if it is not given, newserv will try all possible basis\n\ values and return the one that results in the greatest number of zero bytes\n\ - in the output.\n\ + in the output.\n", + a_encrypt_decrypt_trivial_fn); + +Action a_decrypt_registry_value( + "decrypt-registry-value", nullptr, +[](Arguments& args) { + string data = read_input_data(args); + string out_data = decrypt_v2_registry_value(data.data(), data.size()); + write_output_data(args, out_data.data(), out_data.size(), "dec"); + }); + +Action a_encrypt_challenge_data( + "encrypt-challenge-data", nullptr, +[](Arguments& args) { + string data = read_input_data(args); + encrypt_challenge_rank_text_t(data.data(), data.size()); + write_output_data(args, data.data(), data.size(), "dec"); + }); +Action a_decrypt_challenge_data( + "decrypt-challenge-data", "\ encrypt-challenge-data [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ decrypt-challenge-data [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ - Encrypt or decrypt data using the challenge mode trivial algorithm.\n\ - encrypt-pc-save --seed=SEED [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ - decrypt-pc-save --seed=SEED [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ - Encrypt or decrypt a PSO PC character file (PSO______SYS or PSO______SYD)\n\ - or Guild Card file (PSO______GUD). SEED should be the serial number\n\ - associated with the save file, as a 32-bit hexadecimal integer.\n\ + Encrypt or decrypt data using the challenge mode trivial algorithm.\n", + +[](Arguments& args) { + string data = read_input_data(args); + decrypt_challenge_rank_text_t(data.data(), data.size()); + write_output_data(args, data.data(), data.size(), "dec"); + }); + +static void a_encrypt_decrypt_gci_save_fn(Arguments& args) { + bool is_decrypt = (args.get(0) == "decrypt-gci-save"); + bool skip_checksum = args.get("skip-checksum"); + string seed = args.get("seed"); + string system_filename = args.get("sys"); + int64_t override_round2_seed = args.get("round2-seed", -1, Arguments::IntFormat::HEX); + + uint32_t round1_seed; + if (!system_filename.empty()) { + string system_data = load_file(system_filename); + StringReader r(system_data); + const auto& header = r.get(); + header.check(); + const auto& system = r.get(); + round1_seed = system.creation_timestamp; + } else if (!seed.empty()) { + round1_seed = stoul(seed, nullptr, 16); + } else { + throw runtime_error("either --sys or --seed must be given"); + } + + auto data = read_input_data(args); + StringReader r(data); + const auto& header = r.get(); + header.check(); + + size_t data_start_offset = r.where(); + + auto process_file = [&]() { + if (is_decrypt) { + const void* data_section = r.getv(header.data_size); + auto decrypted = decrypt_fixed_size_data_section_t( + data_section, header.data_size, round1_seed, skip_checksum, override_round2_seed); + *reinterpret_cast(data.data() + data_start_offset) = decrypted; + } else { + const auto& s = r.get(); + auto encrypted = encrypt_fixed_size_data_section_t(s, round1_seed); + if (data_start_offset + encrypted.size() > data.size()) { + throw runtime_error("encrypted result exceeds file size"); + } + memcpy(data.data() + data_start_offset, encrypted.data(), encrypted.size()); + } + }; + + if (header.data_size == sizeof(PSOGCGuildCardFile)) { + process_file.template operator()(); + } else if (header.is_ep12() && (header.data_size == sizeof(PSOGCCharacterFile))) { + process_file.template operator()(); + } else if (header.is_ep3() && (header.data_size == sizeof(PSOGCEp3CharacterFile))) { + auto* charfile = reinterpret_cast(data.data() + data_start_offset); + if (!is_decrypt) { + for (size_t z = 0; z < charfile->characters.size(); z++) { + charfile->characters[z].ep3_config.encrypt(random_object()); + } + } + process_file.template operator()(); + if (is_decrypt) { + for (size_t z = 0; z < charfile->characters.size(); z++) { + charfile->characters[z].ep3_config.decrypt(); + } + } + } else { + throw runtime_error("unrecognized save type"); + } + + write_output_data(args, data.data(), data.size(), is_decrypt ? "gcid" : "gci"); +} + +Action a_decrypt_gci_save("decrypt-gci-save", nullptr, a_encrypt_decrypt_gci_save_fn); +Action a_encrypt_gci_save("encrypt-gci-save", "\ encrypt-gci-save CRYPT-OPTION INPUT-FILENAME [OUTPUT-FILENAME]\n\ decrypt-gci-save CRYPT-OPTION INPUT-FILENAME [OUTPUT-FILENAME]\n\ Encrypt or decrypt a character or Guild Card file in GCI format. If\n\ encrypting, the checksum is also recomputed and stored in the encrypted\n\ file. CRYPT-OPTION is required; it can be either --sys=SYSTEM-FILENAME\n\ (specifying the name of the corresponding PSO_SYSTEM .gci file) or\n\ - --seed=ROUND1-SEED (specified as a 32-bit hexadecimal number).\n\ - salvage-gci INPUT-FILENAME [--round2] [CRYPT-OPTION] [--bytes=SIZE]\n\ - Attempt to find either the round-1 or round-2 decryption seed for a\n\ - corrupted GCI file. If --round2 is given, then CRYPT-OPTION must be given\n\ - (and should specify either a valid system file or the round1 seed).\n\ - find-decryption-seed OPTIONS...\n\ - Perform a brute-force search for a decryption seed of the given data. The\n\ - ciphertext is specified with the --encrypted=DATA option and the expected\n\ - plaintext is specified with the --decrypted=DATA option. The plaintext may\n\ - include unmatched bytes (specified with the Phosg parse_data_string ?\n\ - operator), but overall it must be the same length as the ciphertext. By\n\ - default, this option uses PSO V3 encryption, but this can be overridden\n\ - with --pc. (BB encryption seeds are too long to be searched for with this\n\ - function.) By default, the number of worker threads is equal to the number\n\ - of CPU cores in the system, but this can be overridden with the\n\ - --threads=NUM-THREADS option.\n\ - decode-sjis [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ - Apply newserv\'s text decoding algorithm to the input data, producing\n\ - little-endian UTF-16 output data.\n\ - decode-gci INPUT-FILENAME [OPTIONS...]\n\ - decode-vms INPUT-FILENAME [OPTIONS...]\n\ - decode-dlq INPUT-FILENAME\n\ - decode-qst INPUT-FILENAME\n\ - Decode the input quest file into a compressed, unencrypted .bin or .dat\n\ - file (or in the case of decode-qst, both a .bin and a .dat file).\n\ - INPUT-FILENAME must be specified, but there is no OUTPUT-FILENAME; the\n\ - output is written to INPUT-FILENAME.dec (or .bin, or .dat). If the output\n\ - is a .dec file, you can rename it to .bin or .dat manually. DLQ and QST\n\ - decoding are relatively simple operations, but GCI and VMS decoding can be\n\ - computationally expensive if the file is encrypted and doesn\'t contain an\n\ - embedded seed. If you know the player\'s serial number who generated the\n\ - GCI or VMS file, use the --seed=SEED option and give the serial number (as\n\ - a hex-encoded 32-bit integer). If you don\'t know the serial number,\n\ - newserv will find it via a brute-force search, which will take a long time.\n\ - encode-qst INPUT-FILENAME [OUTPUT-FILENAME] [OPTIONS...]\n\ - Encode the input quest file (in .bin/.dat format) into a .qst file. If\n\ - --download is given, generates a download .qst instead of an online .qst.\n\ - Specify the quest\'s game version with one of the --dc-nte, --dc-v1,\n\ - --dc-v2, --pc, --gc-nte, --gc, --gc-ep3, --xb, or --bb options.\n\ - disassemble-quest-script [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ - Disassemble the input quest script (.bin file) into a text representation\n\ - of the commands and metadata it contains. Specify the quest\'s game version\n\ - with one of the --dc-nte, --dc-v1, --dc-v2, --pc, --gc-nte, --gc, --gc-ep3,\n\ - --xb, or --bb options.\n\ - disassemble-quest-map [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ - Disassemble the input quest map (.dat file) into a text representation of\n\ - the data it contains. Specify the quest\'s game version with one of the\n\ - --dc-nte, --dc-v1, --dc-v2, --pc, --gc-nte, --gc, --xb, or --bb options.\n\ - cat-client ADDR:PORT\n\ - Connect to the given server and simulate a PSO client. newserv will then\n\ - print all the received commands to stdout, and forward any commands typed\n\ - into stdin to the remote server. It is assumed that the input and output\n\ - are terminals, so all commands are hex-encoded. The --patch, --dc, --pc,\n\ - --gc, and --bb options can be used to select the command format and\n\ - encryption. If --bb is used, the --key=KEY-NAME option is also required (as\n\ - in decrypt-data above).\n\ - show-ep3-maps\n\ - Print the Episode 3 maps from the system/ep3 directory in a (sort of)\n\ - human-readable format.\n\ - show-ep3-cards\n\ - Print the Episode 3 card definitions from the system/ep3 directory in a\n\ - human-readable format.\n\ - describe-item DATA\n\ - Print the name of the item given by DATA (in hex). DATA must not contain\n\ - spaces. If DATA is 20 bytes, newserv assumes it contains an unused item ID\n\ - field; if it is fewer bytes, up to 16 bytes are used.\n\ - encode-item DESCRIPTION [--pc|--bb]\n\ - Encode the description of an item into its corresponding ItemData (hex)\n\ - representation. If DESCRIPTION contains spaces, it must be quoted, such as\n\ - \"L&K14 COMBAT +10 0/10/15/0/35\".\n\ - replay-log [INPUT-FILENAME] [OPTIONS...]\n\ - Replay a terminal log as if it were a client session. input-filename may be\n\ - specified for this option. This is used for regression testing, to make\n\ - sure client sessions are repeatable and code changes don\'t affect existing\n\ - (working) functionality.\n\ - extract-afs [INPUT-FILENAME] [--big-endian]\n\ - extract-gsl [INPUT-FILENAME] [--big-endian]\n\ - extract-bml [INPUT-FILENAME] [--big-endian]\n\ - Extract all files from an AFS, GSL, or BML archive into the current\n\ - directory. input-filename may be specified. If output-filename is\n\ - specified, then it is treated as a prefix which is prepended to the\n\ - filename of each file contained in the archive. If --big-endian is given,\n\ - the archive header is read in GameCube format; otherwise it is read in\n\ - PC/BB format.\n\ - decode-text-archive [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ - encode-text-archive [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ - Decode a text archive (e.g. TextEnglish.pr2) to JSON for easy editing, or\n\ - encode a JSON file to a text archive.\n\ - decode-unicode-text-set [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ - encode-unicode-text-set [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ - Decode a Unicode text set (e.g. unitxt_e.prs) to JSON for easy editing, or\n\ - encode a JSON file to a Unicode text set.\n\ - convert-rare-item-set INPUT-FILENAME [OUTPUT-FILENAME]\n\ - If OUTPUT-FILENAME is not given, print the contents of a rare item table in\n\ - a human-readable format. Otherwise, convert the input rare item set to a\n\ - different format and write it to OUTPUT-FILENAME. Both filenames must end\n\ - in one of the following extensions:\n\ - .json (newserv JSON rare item table)\n\ - .gsl (PSO BB little-endian GSL archive)\n\ - .gslb (PSO GC big-endian GSL archive)\n\ - .afs (PSO V2 little-endian AFS archive)\n\ - .rel (Schtserv rare table; cannot be used in output filename)\n\ - generate-dc-serial-number [--domain=DOMAIN] [--subdomain=SUBDOMAIN]\n\ - Generate a PSO DC serial number. DOMAIN should be 0 for Japanese, 1 for\n\ - USA, or 2 for Europe. SUBDOMAIN should be 0 for v1, or 1 for v2.\n\ - generate-all-dc-serial-numbers\n\ - Generate all possible PSO DC serial numbers.\n\ - inspect-dc-serial-number SERIAL-NUMBER\n\ - Show which domain and subdomain the serial number belongs to. (As with\n\ - generate-dc-serial-number, described above, this will tell you which PSO\n\ - version it is valid for.)\n\ - dc-serial-number-speed-test\n\ - Run a speed test of the two DC serial number validation functions.\n\ -\n\ -A few options apply to multiple modes described above:\n\ - --parse-data\n\ - For modes that take input (from a file or from stdin), parse the input as\n\ - a hex string before encrypting/decoding/etc.\n\ - --config=FILENAME\n\ - Use this file instead of system/config.json.\n\ -", - stderr); -} + --seed=ROUND1-SEED (specified as a 32-bit hexadecimal number).\n", + a_encrypt_decrypt_gci_save_fn); -enum class Behavior { - RUN_SERVER = 0, - COMPRESS_PRS, - DECOMPRESS_PRS, - RECOMPRESS_PRS_OPTIMAL, - COMPRESS_PR2, - DECOMPRESS_PR2, - COMPRESS_BC0, - DECOMPRESS_BC0, - PRS_SIZE, - DISASSEMBLE_PRS, - DISASSEMBLE_BC0, - ENCRYPT_DATA, - DECRYPT_DATA, - ENCRYPT_TRIVIAL_DATA, - DECRYPT_TRIVIAL_DATA, - DECRYPT_REGISTRY_VALUE, - ENCRYPT_CHALLENGE_DATA, - DECRYPT_CHALLENGE_DATA, - ENCRYPT_GCI_SAVE, - DECRYPT_GCI_SAVE, - ENCRYPT_PC_SAVE, - DECRYPT_PC_SAVE, - ENCRYPT_SAVE_DATA, - DECRYPT_SAVE_DATA, - DECODE_GCI_SNAPSHOT, - ENCODE_GVM, - FIND_DECRYPTION_SEED, - SALVAGE_GCI, - DECODE_QUEST_FILE, - ENCODE_QST, - DISASSEMBLE_QUEST_SCRIPT, - DISASSEMBLE_QUEST_MAP, - EXTRACT_AFS, - EXTRACT_GSL, - EXTRACT_BML, - DECODE_TEXT_ARCHIVE, - ENCODE_TEXT_ARCHIVE, - DECODE_UNICODE_TEXT_SET, - ENCODE_UNICODE_TEXT_SET, - CONVERT_RARE_ITEM_SET, - SHOW_EP3_MAPS, - SHOW_EP3_CARDS, - GENERATE_EP3_CARDS_HTML, - DESCRIBE_ITEM, - ENCODE_ITEM, - PARSE_OBJECT_GRAPH, - REPLAY_LOG, - CAT_CLIENT, - GENERATE_DC_SERIAL_NUMBER, - GENERATE_ALL_DC_SERIAL_NUMBERS, - INSPECT_DC_SERIAL_NUMBER, - DC_SERIAL_NUMBER_SPEED_TEST, - AR_CODE_TRANSLATOR, -}; +static void a_encrypt_decrypt_pc_save_fn(Arguments& args) { + bool is_decrypt = (args.get(0) == "decrypt-pc-save"); + bool skip_checksum = args.get("skip-checksum"); + string seed = args.get("seed"); + string system_filename = args.get("sys"); + int64_t override_round2_seed = args.get("round2-seed", -1, Arguments::IntFormat::HEX); -static bool behavior_takes_input_filename(Behavior b) { - return (b == Behavior::COMPRESS_PRS) || - (b == Behavior::DECOMPRESS_PRS) || - (b == Behavior::RECOMPRESS_PRS_OPTIMAL) || - (b == Behavior::COMPRESS_PR2) || - (b == Behavior::DECOMPRESS_PR2) || - (b == Behavior::COMPRESS_BC0) || - (b == Behavior::DECOMPRESS_BC0) || - (b == Behavior::PRS_SIZE) || - (b == Behavior::DISASSEMBLE_PRS) || - (b == Behavior::DISASSEMBLE_BC0) || - (b == Behavior::ENCRYPT_DATA) || - (b == Behavior::DECRYPT_DATA) || - (b == Behavior::ENCRYPT_TRIVIAL_DATA) || - (b == Behavior::DECRYPT_TRIVIAL_DATA) || - (b == Behavior::DECRYPT_REGISTRY_VALUE) || - (b == Behavior::ENCRYPT_CHALLENGE_DATA) || - (b == Behavior::DECRYPT_CHALLENGE_DATA) || - (b == Behavior::ENCRYPT_GCI_SAVE) || - (b == Behavior::DECRYPT_GCI_SAVE) || - (b == Behavior::ENCRYPT_PC_SAVE) || - (b == Behavior::DECRYPT_PC_SAVE) || - (b == Behavior::ENCRYPT_SAVE_DATA) || - (b == Behavior::DECRYPT_SAVE_DATA) || - (b == Behavior::DECODE_GCI_SNAPSHOT) || - (b == Behavior::ENCODE_GVM) || - (b == Behavior::SALVAGE_GCI) || - (b == Behavior::DECODE_QUEST_FILE) || - (b == Behavior::ENCODE_QST) || - (b == Behavior::DISASSEMBLE_QUEST_SCRIPT) || - (b == Behavior::DISASSEMBLE_QUEST_MAP) || - (b == Behavior::CONVERT_RARE_ITEM_SET) || - (b == Behavior::EXTRACT_AFS) || - (b == Behavior::EXTRACT_GSL) || - (b == Behavior::EXTRACT_BML) || - (b == Behavior::DECODE_TEXT_ARCHIVE) || - (b == Behavior::ENCODE_TEXT_ARCHIVE) || - (b == Behavior::DECODE_UNICODE_TEXT_SET) || - (b == Behavior::ENCODE_UNICODE_TEXT_SET) || - (b == Behavior::DESCRIBE_ITEM) || - (b == Behavior::ENCODE_ITEM) || - (b == Behavior::PARSE_OBJECT_GRAPH) || - (b == Behavior::REPLAY_LOG) || - (b == Behavior::CAT_CLIENT) || - (b == Behavior::INSPECT_DC_SERIAL_NUMBER) || - (b == Behavior::AR_CODE_TRANSLATOR); -} - -static bool behavior_takes_output_filename(Behavior b) { - return (b == Behavior::COMPRESS_PRS) || - (b == Behavior::DECOMPRESS_PRS) || - (b == Behavior::COMPRESS_PR2) || - (b == Behavior::DECOMPRESS_PR2) || - (b == Behavior::COMPRESS_BC0) || - (b == Behavior::DECOMPRESS_BC0) || - (b == Behavior::ENCRYPT_DATA) || - (b == Behavior::DECRYPT_DATA) || - (b == Behavior::ENCRYPT_TRIVIAL_DATA) || - (b == Behavior::DECRYPT_TRIVIAL_DATA) || - (b == Behavior::DECRYPT_REGISTRY_VALUE) || - (b == Behavior::ENCRYPT_CHALLENGE_DATA) || - (b == Behavior::DECRYPT_CHALLENGE_DATA) || - (b == Behavior::ENCRYPT_GCI_SAVE) || - (b == Behavior::DECRYPT_GCI_SAVE) || - (b == Behavior::ENCRYPT_PC_SAVE) || - (b == Behavior::DECRYPT_PC_SAVE) || - (b == Behavior::ENCRYPT_SAVE_DATA) || - (b == Behavior::DECRYPT_SAVE_DATA) || - (b == Behavior::DECODE_GCI_SNAPSHOT) || - (b == Behavior::ENCODE_GVM) || - (b == Behavior::ENCODE_QST) || - (b == Behavior::DISASSEMBLE_QUEST_SCRIPT) || - (b == Behavior::DISASSEMBLE_QUEST_MAP) || - (b == Behavior::CONVERT_RARE_ITEM_SET) || - (b == Behavior::EXTRACT_AFS) || - (b == Behavior::EXTRACT_GSL) || - (b == Behavior::EXTRACT_BML) || - (b == Behavior::DECODE_TEXT_ARCHIVE) || - (b == Behavior::ENCODE_TEXT_ARCHIVE) || - (b == Behavior::DECODE_UNICODE_TEXT_SET) || - (b == Behavior::ENCODE_UNICODE_TEXT_SET); -} - -int main(int argc, char** argv) { - Behavior behavior = Behavior::RUN_SERVER; - GameVersion cli_version = GameVersion::GC; - QuestScriptVersion cli_quest_version = QuestScriptVersion::GC_V3; - QuestFileFormat quest_file_type = QuestFileFormat::BIN_DAT_GCI; - string seed; - string key_file_name; - const char* config_filename = "system/config.json"; - bool parse_data = false; - bool big_endian = false; - bool skip_little_endian = false; - bool skip_big_endian = false; - bool round2 = false; - bool skip_checksum = false; - uint64_t override_round2_seed = 0xFFFFFFFFFFFFFFFF; - size_t offset = 0; - size_t stride = 1; - size_t num_threads = 0; - size_t bytes = 0; - ssize_t compression_level = 0; - bool expect_decompressed = false; - bool compress_optimal = false; - bool download = false; - bool one_line = false; - const char* find_decryption_seed_ciphertext = nullptr; - vector find_decryption_seed_plaintexts; - const char* input_filename = nullptr; - const char* output_filename = nullptr; - const char* system_filename = nullptr; - bool replay_require_basic_credentials = false; - uint32_t root_object_address = 0; - uint8_t domain = 1; - uint8_t subdomain = 0xFF; - for (int x = 1; x < argc; x++) { - if (!strcmp(argv[x], "--help")) { - print_usage(); - return 0; - } else if (!strncmp(argv[x], "--threads=", 10)) { - num_threads = strtoull(&argv[x][10], nullptr, 0); - } else if (!strcmp(argv[x], "--one-line")) { - one_line = true; - } else if (!strcmp(argv[x], "--download")) { - download = true; - } else if (!strcmp(argv[x], "--patch")) { - cli_version = GameVersion::PATCH; - cli_quest_version = QuestScriptVersion::PC_V2; - } else if (!strcmp(argv[x], "--dc-nte")) { - cli_version = GameVersion::DC; - cli_quest_version = QuestScriptVersion::DC_NTE; - } else if (!strcmp(argv[x], "--dc-v1")) { - cli_version = GameVersion::DC; - cli_quest_version = QuestScriptVersion::DC_V1; - } else if (!strcmp(argv[x], "--dc-v2") || !strcmp(argv[x], "--dc")) { - cli_version = GameVersion::DC; - cli_quest_version = QuestScriptVersion::DC_V2; - } else if (!strcmp(argv[x], "--pc")) { - cli_version = GameVersion::PC; - cli_quest_version = QuestScriptVersion::PC_V2; - } else if (!strcmp(argv[x], "--gc")) { - cli_version = GameVersion::GC; - cli_quest_version = QuestScriptVersion::GC_V3; - } else if (!strcmp(argv[x], "--gc-nte")) { - cli_version = GameVersion::GC; - cli_quest_version = QuestScriptVersion::GC_NTE; - } else if (!strcmp(argv[x], "--gc-ep3")) { - cli_version = GameVersion::GC; - cli_quest_version = QuestScriptVersion::GC_EP3; - } else if (!strcmp(argv[x], "--xb")) { - cli_version = GameVersion::XB; - cli_quest_version = QuestScriptVersion::XB_V3; - } else if (!strcmp(argv[x], "--bb")) { - cli_version = GameVersion::BB; - cli_quest_version = QuestScriptVersion::BB_V4; - } else if (!strncmp(argv[x], "--compression-level=", 20)) { - compression_level = strtoll(&argv[x][20], nullptr, 0); - } else if (!strcmp(argv[x], "--optimal")) { - compress_optimal = true; - } else if (!strcmp(argv[x], "--decompressed")) { - expect_decompressed = true; - } else if (!strcmp(argv[x], "--round2")) { - round2 = true; - } else if (!strncmp(argv[x], "--bytes=", 8)) { - bytes = strtoull(&argv[x][8], nullptr, 0); - } else if (!strncmp(argv[x], "--offset=", 9)) { - offset = strtoull(&argv[x][9], nullptr, 0); - } else if (!strncmp(argv[x], "--stride=", 9)) { - stride = strtoull(&argv[x][9], nullptr, 0); - } else if (!strcmp(argv[x], "--skip-checksum")) { - skip_checksum = true; - } else if (!strncmp(argv[x], "--seed=", 7)) { - seed = &argv[x][7]; - } else if (!strncmp(argv[x], "--round2-seed=", 14)) { - override_round2_seed = strtoull(&argv[x][14], nullptr, 16); - } else if (!strncmp(argv[x], "--key=", 6)) { - key_file_name = &argv[x][6]; - } else if (!strncmp(argv[x], "--sys=", 6)) { - system_filename = &argv[x][6]; - } else if (!strncmp(argv[x], "--domain=", 9)) { - domain = atoi(&argv[x][9]); - } else if (!strncmp(argv[x], "--subdomain=", 12)) { - subdomain = atoi(&argv[x][12]); - } else if (!strncmp(argv[x], "--encrypted=", 12)) { - find_decryption_seed_ciphertext = &argv[x][12]; - } else if (!strncmp(argv[x], "--decrypted=", 12)) { - find_decryption_seed_plaintexts.emplace_back(&argv[x][12]); - } else if (!strcmp(argv[x], "--parse-data")) { - parse_data = true; - } else if (!strcmp(argv[x], "--big-endian")) { - big_endian = true; - } else if (!strcmp(argv[x], "--skip-little-endian")) { - skip_little_endian = true; - } else if (!strcmp(argv[x], "--skip-big-endian")) { - skip_big_endian = true; - } else if (!strcmp(argv[x], "--require-basic-credentials")) { - replay_require_basic_credentials = true; - } else if (!strncmp(argv[x], "--root-addr=", 12)) { - root_object_address = strtoul(&argv[x][12], nullptr, 16); - } else if (!strncmp(argv[x], "--config=", 9)) { - config_filename = &argv[x][9]; - - } else if (!strcmp(argv[x], "-") || argv[x][0] != '-') { - if (behavior == Behavior::RUN_SERVER) { - if (!strcmp(argv[x], "help")) { - print_usage(); - return 0; - } - if (!strcmp(argv[x], "compress-prs")) { - behavior = Behavior::COMPRESS_PRS; - } else if (!strcmp(argv[x], "decompress-prs")) { - behavior = Behavior::DECOMPRESS_PRS; - } else if (!strcmp(argv[x], "recompress-prs-optimal")) { - behavior = Behavior::RECOMPRESS_PRS_OPTIMAL; - } else if (!strcmp(argv[x], "compress-pr2")) { - behavior = Behavior::COMPRESS_PR2; - } else if (!strcmp(argv[x], "decompress-pr2")) { - behavior = Behavior::DECOMPRESS_PR2; - } else if (!strcmp(argv[x], "compress-bc0")) { - behavior = Behavior::COMPRESS_BC0; - } else if (!strcmp(argv[x], "decompress-bc0")) { - behavior = Behavior::DECOMPRESS_BC0; - } else if (!strcmp(argv[x], "prs-size")) { - behavior = Behavior::PRS_SIZE; - } else if (!strcmp(argv[x], "disassemble-prs")) { - behavior = Behavior::DISASSEMBLE_PRS; - } else if (!strcmp(argv[x], "disassemble-bc0")) { - behavior = Behavior::DISASSEMBLE_BC0; - } else if (!strcmp(argv[x], "encrypt-data")) { - behavior = Behavior::ENCRYPT_DATA; - } else if (!strcmp(argv[x], "decrypt-data")) { - behavior = Behavior::DECRYPT_DATA; - } else if (!strcmp(argv[x], "encrypt-trivial-data")) { - behavior = Behavior::ENCRYPT_TRIVIAL_DATA; - } else if (!strcmp(argv[x], "decrypt-trivial-data")) { - behavior = Behavior::DECRYPT_TRIVIAL_DATA; - } else if (!strcmp(argv[x], "decrypt-registry-value")) { - behavior = Behavior::DECRYPT_REGISTRY_VALUE; - } else if (!strcmp(argv[x], "encrypt-challenge-data")) { - behavior = Behavior::ENCRYPT_CHALLENGE_DATA; - } else if (!strcmp(argv[x], "decrypt-challenge-data")) { - behavior = Behavior::DECRYPT_CHALLENGE_DATA; - } else if (!strcmp(argv[x], "decrypt-gci-save")) { - behavior = Behavior::DECRYPT_GCI_SAVE; - } else if (!strcmp(argv[x], "encrypt-gci-save")) { - behavior = Behavior::ENCRYPT_GCI_SAVE; - } else if (!strcmp(argv[x], "decrypt-pc-save")) { - behavior = Behavior::DECRYPT_PC_SAVE; - } else if (!strcmp(argv[x], "encrypt-pc-save")) { - behavior = Behavior::ENCRYPT_PC_SAVE; - } else if (!strcmp(argv[x], "decrypt-save-data")) { - behavior = Behavior::DECRYPT_SAVE_DATA; - } else if (!strcmp(argv[x], "encrypt-save-data")) { - behavior = Behavior::ENCRYPT_SAVE_DATA; - } else if (!strcmp(argv[x], "decode-gci-snapshot")) { - behavior = Behavior::DECODE_GCI_SNAPSHOT; - } else if (!strcmp(argv[x], "encode-gvm")) { - behavior = Behavior::ENCODE_GVM; - } else if (!strcmp(argv[x], "find-decryption-seed")) { - behavior = Behavior::FIND_DECRYPTION_SEED; - } else if (!strcmp(argv[x], "salvage-gci")) { - behavior = Behavior::SALVAGE_GCI; - } else if (!strcmp(argv[x], "decode-gci")) { - behavior = Behavior::DECODE_QUEST_FILE; - quest_file_type = QuestFileFormat::BIN_DAT_GCI; - } else if (!strcmp(argv[x], "decode-vms")) { - behavior = Behavior::DECODE_QUEST_FILE; - quest_file_type = QuestFileFormat::BIN_DAT_VMS; - } else if (!strcmp(argv[x], "decode-dlq")) { - behavior = Behavior::DECODE_QUEST_FILE; - quest_file_type = QuestFileFormat::BIN_DAT_DLQ; - } else if (!strcmp(argv[x], "decode-qst")) { - behavior = Behavior::DECODE_QUEST_FILE; - quest_file_type = QuestFileFormat::QST; - } else if (!strcmp(argv[x], "encode-qst")) { - behavior = Behavior::ENCODE_QST; - } else if (!strcmp(argv[x], "disassemble-quest-script")) { - behavior = Behavior::DISASSEMBLE_QUEST_SCRIPT; - } else if (!strcmp(argv[x], "disassemble-quest-map")) { - behavior = Behavior::DISASSEMBLE_QUEST_MAP; - } else if (!strcmp(argv[x], "cat-client")) { - behavior = Behavior::CAT_CLIENT; - } else if (!strcmp(argv[x], "convert-rare-item-set")) { - behavior = Behavior::CONVERT_RARE_ITEM_SET; - } else if (!strcmp(argv[x], "show-ep3-maps")) { - behavior = Behavior::SHOW_EP3_MAPS; - } else if (!strcmp(argv[x], "show-ep3-cards")) { - behavior = Behavior::SHOW_EP3_CARDS; - } else if (!strcmp(argv[x], "generate-ep3-cards-html")) { - behavior = Behavior::GENERATE_EP3_CARDS_HTML; - } else if (!strcmp(argv[x], "describe-item")) { - behavior = Behavior::DESCRIBE_ITEM; - } else if (!strcmp(argv[x], "encode-item")) { - behavior = Behavior::ENCODE_ITEM; - } else if (!strcmp(argv[x], "parse-object-graph")) { - behavior = Behavior::PARSE_OBJECT_GRAPH; - } else if (!strcmp(argv[x], "replay-log")) { - behavior = Behavior::REPLAY_LOG; - } else if (!strcmp(argv[x], "extract-afs")) { - behavior = Behavior::EXTRACT_AFS; - } else if (!strcmp(argv[x], "extract-gsl")) { - behavior = Behavior::EXTRACT_GSL; - } else if (!strcmp(argv[x], "extract-bml")) { - behavior = Behavior::EXTRACT_BML; - } else if (!strcmp(argv[x], "decode-text-archive")) { - behavior = Behavior::DECODE_TEXT_ARCHIVE; - } else if (!strcmp(argv[x], "encode-text-archive")) { - behavior = Behavior::ENCODE_TEXT_ARCHIVE; - } else if (!strcmp(argv[x], "decode-unicode-text-set")) { - behavior = Behavior::DECODE_UNICODE_TEXT_SET; - } else if (!strcmp(argv[x], "encode-unicode-text-set")) { - behavior = Behavior::ENCODE_UNICODE_TEXT_SET; - } else if (!strcmp(argv[x], "generate-dc-serial-number")) { - behavior = Behavior::GENERATE_DC_SERIAL_NUMBER; - } else if (!strcmp(argv[x], "generate-all-dc-serial-numbers")) { - behavior = Behavior::GENERATE_ALL_DC_SERIAL_NUMBERS; - } else if (!strcmp(argv[x], "inspect-dc-serial-number")) { - behavior = Behavior::INSPECT_DC_SERIAL_NUMBER; - } else if (!strcmp(argv[x], "dc-serial-number-speed-test")) { - behavior = Behavior::DC_SERIAL_NUMBER_SPEED_TEST; - } else if (!strcmp(argv[x], "ar-code-translator")) { - behavior = Behavior::AR_CODE_TRANSLATOR; - } else { - throw invalid_argument(string_printf("unknown command: %s (try --help)", argv[x])); - } - } else if (!input_filename && behavior_takes_input_filename(behavior)) { - input_filename = argv[x]; - } else if (!output_filename && behavior_takes_output_filename(behavior)) { - output_filename = argv[x]; - } else { - throw invalid_argument(string_printf("unknown option: %s (try --help)", argv[x])); - } + if (seed.empty()) { + throw runtime_error("--seed must be given to specify the serial number"); + } + uint32_t round1_seed = stoul(seed, nullptr, 16); + auto data = read_input_data(args); + if (data.size() == sizeof(PSOPCGuildCardFile)) { + if (is_decrypt) { + data = decrypt_fixed_size_data_section_s( + data.data(), offsetof(PSOPCGuildCardFile, end_padding), round1_seed, skip_checksum, override_round2_seed); } else { - throw invalid_argument(string_printf("unknown option: %s (try --help)", argv[x])); + data = encrypt_fixed_size_data_section_s( + data.data(), offsetof(PSOPCGuildCardFile, end_padding), round1_seed); } + data.resize((sizeof(PSOPCGuildCardFile) + 0x1FF) & (~0x1FF), '\0'); + } else if (data.size() == sizeof(PSOPCCharacterFile)) { + PSOPCCharacterFile* charfile = reinterpret_cast(data.data()); + if (is_decrypt) { + for (size_t z = 0; z < charfile->entries.size(); z++) { + if (charfile->entries[z].present) { + try { + charfile->entries[z].character = decrypt_fixed_size_data_section_t( + &charfile->entries[z].character, sizeof(charfile->entries[z].character), round1_seed, skip_checksum, override_round2_seed); + } catch (const exception& e) { + fprintf(stderr, "warning: cannot decrypt character %zu: %s\n", z, e.what()); + } + } + } + } else { + for (size_t z = 0; z < charfile->entries.size(); z++) { + if (charfile->entries[z].present) { + string encrypted = encrypt_fixed_size_data_section_t( + charfile->entries[z].character, round1_seed); + if (encrypted.size() != sizeof(PSOPCCharacterFile::CharacterEntry::Character)) { + throw logic_error("incorrect encrypted result size"); + } + charfile->entries[z].character = *reinterpret_cast(encrypted.data()); + } + } + } + } else if (data.size() == sizeof(PSOPCCreationTimeFile)) { + throw runtime_error("the PSO______FLS file is not encrypted; it is just random data"); + } else if (data.size() == sizeof(PSOPCSystemFile)) { + throw runtime_error("the PSO______COM file is not encrypted"); + } else { + throw runtime_error("unknown save file type"); } - auto read_input_data = [&]() -> string { - string data; - if (input_filename && strcmp(input_filename, "-")) { - data = load_file(input_filename); - } else { - data = read_all(stdin); - } - if (parse_data) { - data = parse_data_string(data, nullptr, ParseDataFlags::ALLOW_FILES); - } - return data; - }; + write_output_data(args, data.data(), data.size(), "dec"); +} - auto write_output_data = [&](const void* data, size_t size) { - if (output_filename && strcmp(output_filename, "-")) { - // If the output is to a specified file, write it there - save_file(output_filename, data, size); +Action a_decrypt_pc_save("decrypt-pc-save", nullptr, a_encrypt_decrypt_pc_save_fn); +Action a_encrypt_pc_save("encrypt-pc-save", "\ + encrypt-pc-save --seed=SEED [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ + decrypt-pc-save --seed=SEED [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ + Encrypt or decrypt a PSO PC character file (PSO______SYS or PSO______SYD)\n\ + or Guild Card file (PSO______GUD). SEED should be the serial number\n\ + associated with the save file, as a 32-bit hexadecimal integer.\n", + a_encrypt_decrypt_pc_save_fn); - } else if (!output_filename && input_filename && strcmp(input_filename, "-")) { - // If no output filename is given and an input filename is given, write to - // .dec (or an appropriate extension, if it can be - // autodetected) - string filename = input_filename; - if (behavior == Behavior::COMPRESS_PRS) { - if (ends_with(filename, ".bind") || - ends_with(filename, ".datd") || - ends_with(filename, ".mnmd")) { - filename.resize(filename.size() - 1); - } else { - filename += ".prs"; - } - } else if (behavior == Behavior::DECOMPRESS_PRS) { - if (ends_with(filename, ".bin") || - ends_with(filename, ".dat") || - ends_with(filename, ".mnm")) { - filename += "d"; - } else { - filename += ".dec"; - } - } else if (behavior == Behavior::ENCRYPT_GCI_SAVE) { - if (ends_with(filename, ".gcid")) { - filename.resize(filename.size() - 1); - } else { - filename += ".gci"; - } - } else if (behavior == Behavior::DECRYPT_GCI_SAVE) { - if (ends_with(filename, ".gci")) { - filename += "d"; - } else { - filename += ".dec"; - } - } else if (behavior == Behavior::DECODE_GCI_SNAPSHOT) { - filename += ".bmp"; - } else if (behavior == Behavior::ENCODE_GVM) { - filename += ".gvm"; - } else if ((behavior == Behavior::DECODE_TEXT_ARCHIVE) || (behavior == Behavior::DECODE_UNICODE_TEXT_SET)) { - filename += ".json"; - } else if ((behavior == Behavior::DISASSEMBLE_QUEST_SCRIPT) || (behavior == Behavior::DISASSEMBLE_QUEST_MAP)) { - filename += ".txt"; - } else { - filename += ".dec"; - } - save_file(filename, data, size); +static void a_encrypt_decrypt_save_data_fn(Arguments& args) { + bool is_decrypt = (args.get(0) == "decrypt-save-data"); + bool skip_checksum = args.get("skip-checksum"); + bool is_big_endian = args.get("big-endian"); + string seed = args.get("seed"); + int64_t override_round2_seed = args.get("round2-seed", -1, Arguments::IntFormat::HEX); + size_t bytes = args.get("bytes", 0); - } else if (isatty(fileno(stdout)) && (behavior != Behavior::DISASSEMBLE_QUEST_SCRIPT) && (behavior != Behavior::DISASSEMBLE_QUEST_MAP)) { - // If stdout is a terminal and the data is not known to be text, use - // print_data to write the result - print_data(stdout, data, size); - fflush(stdout); + if (seed.empty()) { + throw runtime_error("--seed must be given to specify the round1 seed"); + } + uint32_t round1_seed = stoul(seed, nullptr, 16); - } else { - // If stdout is not a terminal, write the data as-is - fwritex(stdout, data, size); - fflush(stdout); - } - }; + auto data = read_input_data(args); + StringReader r(data); - switch (behavior) { - case Behavior::COMPRESS_PRS: - case Behavior::DECOMPRESS_PRS: - case Behavior::COMPRESS_PR2: - case Behavior::DECOMPRESS_PR2: - case Behavior::COMPRESS_BC0: - case Behavior::DECOMPRESS_BC0: { - string data = read_input_data(); + string output_data; + size_t effective_size = bytes ? min(bytes, data.size()) : data.size(); + if (is_decrypt) { + output_data = is_big_endian + ? decrypt_fixed_size_data_section_s(data.data(), effective_size, round1_seed, skip_checksum, override_round2_seed) + : decrypt_fixed_size_data_section_s(data.data(), effective_size, round1_seed, skip_checksum, override_round2_seed); + } else { + output_data = is_big_endian + ? encrypt_fixed_size_data_section_s(data.data(), effective_size, round1_seed) + : encrypt_fixed_size_data_section_s(data.data(), effective_size, round1_seed); + } + write_output_data(args, output_data.data(), output_data.size(), "dec"); +} - size_t pr2_expected_size = 0; - if (behavior == Behavior::DECOMPRESS_PR2) { - auto decrypted = big_endian ? decrypt_pr2_data(data) : decrypt_pr2_data(data); - pr2_expected_size = decrypted.decompressed_size; - data = std::move(decrypted.compressed_data); - } +// TODO: Write usage text for these actions +Action a_decrypt_save_data("decrypt-save-data", nullptr, a_encrypt_decrypt_save_data_fn); +Action a_encrypt_save_data("encrypt-save-data", nullptr, a_encrypt_decrypt_save_data_fn); - size_t input_bytes = data.size(); - auto progress_fn = [&](auto, size_t input_progress, size_t, size_t output_progress) -> void { - float progress = static_cast(input_progress * 100) / input_bytes; - float size_ratio = static_cast(output_progress * 100) / input_progress; - fprintf(stderr, "... %zu/%zu (%g%%) => %zu (%g%%) \r", - input_progress, input_bytes, progress, output_progress, size_ratio); - }; - auto optimal_progress_fn = [&](auto phase, size_t input_progress, size_t input_bytes, size_t output_progress) -> void { - const char* phase_name = name_for_enum(phase); - float progress = static_cast(input_progress * 100) / input_bytes; - float size_ratio = static_cast(output_progress * 100) / input_progress; - fprintf(stderr, "... [%s] %zu/%zu (%g%%) => %zu (%g%%) \r", - phase_name, input_progress, input_bytes, progress, output_progress, size_ratio); - }; - - uint64_t start = now(); - if ((behavior == Behavior::COMPRESS_PRS) || (behavior == Behavior::COMPRESS_PR2)) { - if (compress_optimal) { - data = prs_compress_optimal(data.data(), data.size(), optimal_progress_fn); - } else { - data = prs_compress(data, compression_level, progress_fn); - } - } else if ((behavior == Behavior::DECOMPRESS_PRS) || (behavior == Behavior::DECOMPRESS_PR2)) { - data = prs_decompress(data, bytes, (bytes != 0)); - } else if (behavior == Behavior::COMPRESS_BC0) { - if (compress_optimal) { - data = bc0_compress_optimal(data.data(), data.size(), optimal_progress_fn); - } else if (compression_level < 0) { - data = bc0_encode(data.data(), data.size()); - } else { - data = bc0_compress(data, progress_fn); - } - } else if (behavior == Behavior::DECOMPRESS_BC0) { - data = bc0_decompress(data); - } else { - throw logic_error("invalid behavior"); - } - uint64_t end = now(); - string time_str = format_duration(end - start); - - float size_ratio = static_cast(data.size() * 100) / input_bytes; - double bytes_per_sec = input_bytes / (static_cast(end - start) / 1000000.0); - string bytes_per_sec_str = format_size(bytes_per_sec); - log_info("%zu (0x%zX) bytes input => %zu (0x%zX) bytes output (%g%%) in %s (%s / sec)", - input_bytes, input_bytes, data.size(), data.size(), size_ratio, time_str.c_str(), bytes_per_sec_str.c_str()); - - if ((behavior == Behavior::DECOMPRESS_PR2) && (data.size() != pr2_expected_size)) { - log_warning("Result data size (%zu bytes) does not match expected size from PR2 header (%zu bytes)", data.size(), pr2_expected_size); - } else if (behavior == Behavior::COMPRESS_PR2) { - uint32_t pr2_seed = seed.empty() ? random_object() : stoul(seed, nullptr, 16); - data = big_endian - ? encrypt_pr2_data(data, input_bytes, pr2_seed) - : encrypt_pr2_data(data, input_bytes, pr2_seed); - } - - write_output_data(data.data(), data.size()); - break; - } - - case Behavior::RECOMPRESS_PRS_OPTIMAL: { - string input_data = read_input_data(); - string decompressed_data = prs_decompress(input_data); - - auto progress_fn = [&](auto phase, size_t input_progress, size_t input_bytes, size_t output_progress) -> void { - const char* phase_name = name_for_enum(phase); - float progress = static_cast(input_progress * 100) / decompressed_data.size(); - float size_ratio = static_cast(output_progress * 100) / input_progress; - fprintf(stderr, "... [%s] %zu/%zu (%g%%) => %zu (%g%%) \r", - phase_name, input_progress, input_bytes, progress, output_progress, size_ratio); - }; - - uint64_t start = now(); - string output_data = prs_compress_optimal(decompressed_data.data(), decompressed_data.size(), progress_fn); - uint64_t end = now(); - string time_str = format_duration(end - start); - - float output_size_ratio = static_cast(output_data.size() * 100) / decompressed_data.size(); - float input_size_ratio = static_cast(input_data.size() * 100) / decompressed_data.size(); - ssize_t size_difference = output_data.size() - input_data.size(); - log_info("%zu (0x%zX) bytes input (%g%%) => %zu (0x%zX) bytes decompressed => %zu (0x%zX) bytes output (%g%%; %+zd bytes)", - input_data.size(), input_data.size(), input_size_ratio, decompressed_data.size(), decompressed_data.size(), output_data.size(), output_data.size(), output_size_ratio, size_difference); - - output_filename = input_filename; - write_output_data(output_data.data(), output_data.size()); - break; - } - - case Behavior::PRS_SIZE: { - string data = read_input_data(); - size_t input_bytes = data.size(); - size_t output_bytes = prs_decompress_size(data); - log_info("%zu (0x%zX) bytes input => %zu (0x%zX) bytes output", - input_bytes, input_bytes, output_bytes, output_bytes); - break; - } - - case Behavior::DISASSEMBLE_PRS: { - prs_disassemble(stdout, read_input_data()); - break; - } - case Behavior::DISASSEMBLE_BC0: { - bc0_disassemble(stdout, read_input_data()); - break; - } - - case Behavior::DECRYPT_DATA: - case Behavior::ENCRYPT_DATA: { - shared_ptr crypt; - switch (cli_version) { - case GameVersion::PATCH: - case GameVersion::DC: - case GameVersion::PC: - crypt.reset(new PSOV2Encryption(stoul(seed, nullptr, 16))); - break; - case GameVersion::GC: - case GameVersion::XB: - crypt.reset(new PSOV3Encryption(stoul(seed, nullptr, 16))); - break; - case GameVersion::BB: { - seed = parse_data_string(seed, nullptr, ParseDataFlags::ALLOW_FILES); - auto key = load_object_file( - "system/blueburst/keys/" + key_file_name + ".nsk"); - crypt.reset(new PSOBBEncryption(key, seed.data(), seed.size())); - break; - } - default: - throw logic_error("invalid game version"); - } - - string data = read_input_data(); - - size_t original_size = data.size(); - data.resize((data.size() + 7) & (~7), '\0'); - - if (big_endian) { - uint32_t* dwords = reinterpret_cast(data.data()); - for (size_t x = 0; x < (data.size() >> 2); x++) { - dwords[x] = bswap32(dwords[x]); - } - } - - if (behavior == Behavior::DECRYPT_DATA) { - crypt->decrypt(data.data(), data.size()); - } else if (behavior == Behavior::ENCRYPT_DATA) { - crypt->encrypt(data.data(), data.size()); - } else { - throw logic_error("invalid behavior"); - } - - if (big_endian) { - uint32_t* dwords = reinterpret_cast(data.data()); - for (size_t x = 0; x < (data.size() >> 2); x++) { - dwords[x] = bswap32(dwords[x]); - } - } - - data.resize(original_size); - - write_output_data(data.data(), data.size()); - - break; - } - - case Behavior::ENCRYPT_TRIVIAL_DATA: - case Behavior::DECRYPT_TRIVIAL_DATA: { - if (seed.empty() && behavior == Behavior::ENCRYPT_TRIVIAL_DATA) { - throw logic_error("--seed is required when encrypting data"); - } - string data = read_input_data(); - uint8_t basis; - if (seed.empty()) { - uint8_t best_seed = 0x00; - size_t best_seed_score = 0; - for (size_t z = 0; z < 0x100; z++) { - string decrypted = data; - decrypt_trivial_gci_data(decrypted.data(), decrypted.size(), z); - size_t score = 0; - for (size_t x = 0; x < decrypted.size(); x++) { - if (decrypted[x] == '\0') { - score++; - } - } - if (score > best_seed_score) { - best_seed = z; - best_seed_score = score; - } - } - fprintf(stderr, "Basis appears to be %02hhX (%zu zero bytes in output)\n", - best_seed, best_seed_score); - basis = best_seed; - } else { - basis = stoul(seed, nullptr, 16); - } - decrypt_trivial_gci_data(data.data(), data.size(), basis); - write_output_data(data.data(), data.size()); - break; - } - - case Behavior::DECRYPT_REGISTRY_VALUE: { - string data = read_input_data(); - string out_data = decrypt_v2_registry_value(data.data(), data.size()); - write_output_data(out_data.data(), out_data.size()); - break; - } - - case Behavior::ENCRYPT_CHALLENGE_DATA: - case Behavior::DECRYPT_CHALLENGE_DATA: { - string data = read_input_data(); - if (behavior == Behavior::DECRYPT_CHALLENGE_DATA) { - decrypt_challenge_rank_text_t(data.data(), data.size()); - } else { - encrypt_challenge_rank_text_t(data.data(), data.size()); - } - write_output_data(data.data(), data.size()); - break; - } - - case Behavior::ENCRYPT_GCI_SAVE: - case Behavior::DECRYPT_GCI_SAVE: { - uint32_t round1_seed; - if (system_filename) { - string system_data = load_file(system_filename); - StringReader r(system_data); - const auto& header = r.get(); - header.check(); - const auto& system = r.get(); - round1_seed = system.creation_timestamp; - } else if (!seed.empty()) { - round1_seed = stoul(seed, nullptr, 16); - } else { - throw runtime_error("either --sys or --seed must be given"); - } - - bool is_decrypt = (behavior == Behavior::DECRYPT_GCI_SAVE); - - auto data = read_input_data(); - StringReader r(data); - const auto& header = r.get(); - header.check(); - - size_t data_start_offset = r.where(); - - auto process_file = [&]() { - if (is_decrypt) { - const void* data_section = r.getv(header.data_size); - auto decrypted = decrypt_fixed_size_data_section_t( - data_section, header.data_size, round1_seed, skip_checksum, override_round2_seed); - *reinterpret_cast(data.data() + data_start_offset) = decrypted; - } else { - const auto& s = r.get(); - auto encrypted = encrypt_fixed_size_data_section_t(s, round1_seed); - if (data_start_offset + encrypted.size() > data.size()) { - throw runtime_error("encrypted result exceeds file size"); - } - memcpy(data.data() + data_start_offset, encrypted.data(), encrypted.size()); - } - }; - - if (header.data_size == sizeof(PSOGCGuildCardFile)) { - process_file.template operator()(); - } else if (header.is_ep12() && (header.data_size == sizeof(PSOGCCharacterFile))) { - process_file.template operator()(); - } else if (header.is_ep3() && (header.data_size == sizeof(PSOGCEp3CharacterFile))) { - auto* charfile = reinterpret_cast(data.data() + data_start_offset); - if (!is_decrypt) { - for (size_t z = 0; z < charfile->characters.size(); z++) { - charfile->characters[z].ep3_config.encrypt(random_object()); - } - } - process_file.template operator()(); - if (is_decrypt) { - for (size_t z = 0; z < charfile->characters.size(); z++) { - charfile->characters[z].ep3_config.decrypt(); - } - } - } else { - throw runtime_error("unrecognized save type"); - } - - write_output_data(data.data(), data.size()); - - break; - } - - case Behavior::ENCRYPT_PC_SAVE: - case Behavior::DECRYPT_PC_SAVE: { - if (seed.empty()) { - throw runtime_error("--seed must be given to specify the serial number"); - } - uint32_t round1_seed = stoul(seed, nullptr, 16); - - bool is_decrypt = (behavior == Behavior::DECRYPT_PC_SAVE); - - auto data = read_input_data(); - if (data.size() == sizeof(PSOPCGuildCardFile)) { - if (is_decrypt) { - data = decrypt_fixed_size_data_section_s( - data.data(), offsetof(PSOPCGuildCardFile, end_padding), round1_seed, skip_checksum, override_round2_seed); - } else { - data = encrypt_fixed_size_data_section_s( - data.data(), offsetof(PSOPCGuildCardFile, end_padding), round1_seed); - } - data.resize((sizeof(PSOPCGuildCardFile) + 0x1FF) & (~0x1FF), '\0'); - } else if (data.size() == sizeof(PSOPCCharacterFile)) { - PSOPCCharacterFile* charfile = reinterpret_cast(data.data()); - if (is_decrypt) { - for (size_t z = 0; z < charfile->entries.size(); z++) { - if (charfile->entries[z].present) { - try { - charfile->entries[z].character = decrypt_fixed_size_data_section_t( - &charfile->entries[z].character, sizeof(charfile->entries[z].character), round1_seed, skip_checksum, override_round2_seed); - } catch (const exception& e) { - fprintf(stderr, "warning: cannot decrypt character %zu: %s\n", z, e.what()); - } - } - } - } else { - for (size_t z = 0; z < charfile->entries.size(); z++) { - if (charfile->entries[z].present) { - string encrypted = encrypt_fixed_size_data_section_t( - charfile->entries[z].character, round1_seed); - if (encrypted.size() != sizeof(PSOPCCharacterFile::CharacterEntry::Character)) { - throw logic_error("incorrect encrypted result size"); - } - charfile->entries[z].character = *reinterpret_cast(encrypted.data()); - } - } - } - } else if (data.size() == sizeof(PSOPCCreationTimeFile)) { - throw runtime_error("the PSO______FLS file is not encrypted; it is just random data"); - } else if (data.size() == sizeof(PSOPCSystemFile)) { - throw runtime_error("the PSO______COM file is not encrypted"); - } else { - throw runtime_error("unknown save file type"); - } - - write_output_data(data.data(), data.size()); - break; - } - - case Behavior::ENCRYPT_SAVE_DATA: - case Behavior::DECRYPT_SAVE_DATA: { - if (seed.empty()) { - throw runtime_error("--seed must be given to specify the round1 seed"); - } - uint32_t round1_seed = stoul(seed, nullptr, 16); - - bool is_decrypt = (behavior == Behavior::DECRYPT_SAVE_DATA); - - auto data = read_input_data(); - StringReader r(data); - - string output_data; - size_t effective_size = bytes ? min(bytes, data.size()) : data.size(); - if (is_decrypt) { - output_data = big_endian - ? decrypt_fixed_size_data_section_s(data.data(), effective_size, round1_seed, skip_checksum, override_round2_seed) - : decrypt_fixed_size_data_section_s(data.data(), effective_size, round1_seed, skip_checksum, override_round2_seed); - } else { - output_data = big_endian - ? encrypt_fixed_size_data_section_s(data.data(), effective_size, round1_seed) - : encrypt_fixed_size_data_section_s(data.data(), effective_size, round1_seed); - } - write_output_data(output_data.data(), output_data.size()); - break; - } - - case Behavior::DECODE_GCI_SNAPSHOT: { - auto data = read_input_data(); +Action a_decode_gci_snapshot( + "decode-gci-snapshot", "\ + decode-gci-snapshot [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ + Decode a PSO GC snapshot file into a Windows BMP image.\n", + +[](Arguments& args) { + auto data = read_input_data(args); StringReader r(data); const auto& header = r.get(); try { @@ -1165,25 +702,43 @@ int main(int argc, char** argv) { auto img = file.decode_image(); string saved = img.save(Image::Format::WINDOWS_BITMAP); - write_output_data(saved.data(), saved.size()); - break; - } + write_output_data(args, saved.data(), saved.size(), "bmp"); + }); - case Behavior::ENCODE_GVM: { +Action a_encode_gvm( + "encode-gvm", "\ + encode-gvm [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ + Encode an image in BMP or PPM/PNM format into a GVM texture. The resulting\n\ + GVM file can be used as an Episode 3 lobby banner.\n", + +[](Arguments& args) { + const string& input_filename = args.get(1, false); Image img; - if (input_filename && strcmp(input_filename, "-")) { + if (!input_filename.empty() && (input_filename != "-")) { img = Image(input_filename); } else { img = Image(stdin); } string encoded = encode_gvm(img, img.get_has_alpha() ? GVRDataFormat::RGB5A3 : GVRDataFormat::RGB565); - write_output_data(encoded.data(), encoded.size()); - break; - } + write_output_data(args, encoded.data(), encoded.size(), "gvm"); + }); + +Action a_salvage_gci( + "salvage-gci", "\ + salvage-gci INPUT-FILENAME [--round2] [CRYPT-OPTION] [--bytes=SIZE]\n\ + Attempt to find either the round-1 or round-2 decryption seed for a\n\ + corrupted GCI file. If --round2 is given, then CRYPT-OPTION must be given\n\ + (and should specify either a valid system file or the round1 seed).\n", + +[](Arguments& args) { + bool round2 = args.get("round2"); + string seed = args.get("seed"); + string system_filename = args.get("sys"); + size_t num_threads = args.get("threads", 0); + size_t offset = args.get("offset", 0); + size_t stride = args.get("stride", 1); + size_t bytes = args.get("bytes", 0); - case Behavior::SALVAGE_GCI: { uint64_t likely_round1_seed = 0xFFFFFFFFFFFFFFFF; - if (system_filename) { + if (!system_filename.empty()) { try { string system_data = load_file(system_filename); StringReader r(system_data); @@ -1204,7 +759,7 @@ int main(int argc, char** argv) { throw invalid_argument("cannot find round2 seed without known round1 seed"); } - auto data = read_input_data(); + auto data = read_input_data(args); StringReader r(data); const auto& header = r.get(); header.check(); @@ -1272,21 +827,38 @@ int main(int argc, char** argv) { } else { throw runtime_error("unrecognized save type"); } + }); - break; - } - - case Behavior::FIND_DECRYPTION_SEED: { - if (find_decryption_seed_plaintexts.empty() || !find_decryption_seed_ciphertext) { +Action a_find_decryption_seed( + "find-decryption-seed", "\ + find-decryption-seed OPTIONS...\n\ + Perform a brute-force search for a decryption seed of the given data. The\n\ + ciphertext is specified with the --encrypted=DATA option and the expected\n\ + plaintext is specified with the --decrypted=DATA option. The plaintext may\n\ + include unmatched bytes (specified with the Phosg parse_data_string ?\n\ + operator), but overall it must be the same length as the ciphertext. By\n\ + default, this option uses PSO V3 encryption, but this can be overridden\n\ + with --pc. (BB encryption seeds are too long to be searched for with this\n\ + function.) By default, the number of worker threads is equal to the number\n\ + of CPU cores in the system, but this can be overridden with the\n\ + --threads=NUM-THREADS option.\n", + +[](Arguments& args) { + const auto& plaintexts_ascii = args.get_multi("decrypted"); + const auto& ciphertext_ascii = args.get("encrypted"); + auto version = get_cli_version(args); + if (plaintexts_ascii.empty() || ciphertext_ascii.empty()) { throw runtime_error("both --encrypted and --decrypted must be specified"); } - if (cli_version == GameVersion::BB) { + if (uses_v4_encryption(version)) { throw runtime_error("--find-decryption-seed cannot be used for BB ciphers"); } + bool skip_little_endian = args.get("skip-little-endian"); + bool skip_big_endian = args.get("skip-big-endian"); + size_t num_threads = args.get("threads", 0); size_t max_plaintext_size = 0; vector> plaintexts; - for (const auto& plaintext_ascii : find_decryption_seed_plaintexts) { + for (const auto& plaintext_ascii : plaintexts_ascii) { string mask; string data = parse_data_string(plaintext_ascii, &mask, ParseDataFlags::ALLOW_FILES); if (data.size() != mask.size()) { @@ -1295,7 +867,7 @@ int main(int argc, char** argv) { max_plaintext_size = max(max_plaintext_size, data.size()); plaintexts.emplace_back(std::move(data), std::move(mask)); } - string ciphertext = parse_data_string(find_decryption_seed_ciphertext, nullptr, ParseDataFlags::ALLOW_FILES); + string ciphertext = parse_data_string(ciphertext_ascii, nullptr, ParseDataFlags::ALLOW_FILES); auto mask_match = +[](const void* a, const void* b, const void* m, size_t size) -> bool { const uint8_t* a8 = reinterpret_cast(a); @@ -1309,11 +881,10 @@ int main(int argc, char** argv) { return true; }; - bool is_v3 = ((cli_version == GameVersion::GC) || (cli_version == GameVersion::XB)); uint64_t seed = parallel_range([&](uint64_t seed, size_t) -> bool { string be_decrypt_buf = ciphertext.substr(0, max_plaintext_size); string le_decrypt_buf = ciphertext.substr(0, max_plaintext_size); - if (is_v3) { + if (uses_v3_encryption(version)) { PSOV3Encryption(seed).encrypt_both_endian( le_decrypt_buf.data(), be_decrypt_buf.data(), @@ -1346,41 +917,85 @@ int main(int argc, char** argv) { } else { log_error("No seed found"); } - break; - } + }); - case Behavior::DECODE_QUEST_FILE: { - if (!input_filename || !strcmp(input_filename, "-")) { +Action a_decode_gci( + "decode-gci", nullptr, +[](Arguments& args) { + string input_filename = args.get(1, false); + if (input_filename.empty() || (input_filename == "-")) { throw invalid_argument("an input filename is required"); } - - string output_filename_base = input_filename; - if (quest_file_type == QuestFileFormat::BIN_DAT_GCI) { - int64_t dec_seed = seed.empty() ? -1 : stoul(seed, nullptr, 16); - auto decoded = decode_gci_data(read_input_data(), num_threads, dec_seed, skip_checksum); - save_file(output_filename_base + ".dec", decoded); - } else if (quest_file_type == QuestFileFormat::BIN_DAT_VMS) { - int64_t dec_seed = seed.empty() ? -1 : stoul(seed, nullptr, 16); - auto decoded = decode_vms_data(read_input_data(), num_threads, dec_seed, skip_checksum); - save_file(output_filename_base + ".dec", decoded); - } else if (quest_file_type == QuestFileFormat::BIN_DAT_DLQ) { - auto decoded = decode_dlq_data(read_input_data()); - save_file(output_filename_base + ".dec", decoded); - } else if (quest_file_type == QuestFileFormat::QST) { - auto files = decode_qst_data(read_input_data()); - for (const auto& it : files) { - save_file(output_filename_base + "-" + it.first, it.second); - } - } else { - throw logic_error("invalid quest file format"); - } - break; - } - - case Behavior::ENCODE_QST: { - if (!input_filename || !strcmp(input_filename, "-")) { + string seed = args.get("seed"); + size_t num_threads = args.get("threads", 0); + bool skip_checksum = args.get("skip-checksum"); + int64_t dec_seed = seed.empty() ? -1 : stoul(seed, nullptr, 16); + auto decoded = decode_gci_data(read_input_data(args), num_threads, dec_seed, skip_checksum); + save_file(input_filename + ".dec", decoded); + }); +Action a_decode_vmg( + "decode-vms", nullptr, +[](Arguments& args) { + string input_filename = args.get(1, false); + if (input_filename.empty() || (input_filename == "-")) { throw invalid_argument("an input filename is required"); } + string seed = args.get("seed"); + size_t num_threads = args.get("threads", 0); + bool skip_checksum = args.get("skip-checksum"); + int64_t dec_seed = seed.empty() ? -1 : stoul(seed, nullptr, 16); + auto decoded = decode_vms_data(read_input_data(args), num_threads, dec_seed, skip_checksum); + save_file(input_filename + ".dec", decoded); + }); +Action a_decode_dlq( + "decode-dlq", nullptr, +[](Arguments& args) { + string input_filename = args.get(1, false); + if (input_filename.empty() || (input_filename == "-")) { + throw invalid_argument("an input filename is required"); + } + auto decoded = decode_dlq_data(read_input_data(args)); + save_file(input_filename + ".dec", decoded); + }); +Action a_decode_qst( + "decode-qst", "\ + decode-gci INPUT-FILENAME [OPTIONS...]\n\ + decode-vms INPUT-FILENAME [OPTIONS...]\n\ + decode-dlq INPUT-FILENAME\n\ + decode-qst INPUT-FILENAME\n\ + Decode the input quest file into a compressed, unencrypted .bin or .dat\n\ + file (or in the case of decode-qst, both a .bin and a .dat file).\n\ + INPUT-FILENAME must be specified, but there is no OUTPUT-FILENAME; the\n\ + output is written to INPUT-FILENAME.dec (or .bin, or .dat). If the output\n\ + is a .dec file, you can rename it to .bin or .dat manually. DLQ and QST\n\ + decoding are relatively simple operations, but GCI and VMS decoding can be\n\ + computationally expensive if the file is encrypted and doesn\'t contain an\n\ + embedded seed. If you know the player\'s serial number who generated the\n\ + GCI or VMS file, use the --seed=SEED option and give the serial number (as\n\ + a hex-encoded 32-bit integer). If you don\'t know the serial number,\n\ + newserv will find it via a brute-force search, which will take a long time.\n", + +[](Arguments& args) { + string input_filename = args.get(1, false); + if (input_filename.empty() || (input_filename == "-")) { + throw invalid_argument("an input filename is required"); + } + auto files = decode_qst_data(read_input_data(args)); + for (const auto& it : files) { + save_file(input_filename + "-" + it.first, it.second); + } + }); + +Action a_encode_qst( + "encode-qst", "\ + encode-qst INPUT-FILENAME [OUTPUT-FILENAME] [OPTIONS...]\n\ + Encode the input quest file (in .bin/.dat format) into a .qst file. If\n\ + --download is given, generates a download .qst instead of an online .qst.\n\ + Specify the quest\'s game version with one of the --dc-nte, --dc-v1,\n\ + --dc-v2, --pc, --gc-nte, --gc, --gc-ep3, --xb, or --bb options.\n", + +[](Arguments& args) { + string input_filename = args.get(1, false); + if (input_filename.empty() || (input_filename == "-")) { + throw invalid_argument("an input filename is required"); + } + auto version = get_cli_version(args); + bool download = args.get("download"); string bin_filename = input_filename; string dat_filename = ends_with(bin_filename, ".bin") @@ -1396,117 +1011,144 @@ int main(int argc, char** argv) { pvr_data.reset(new string(load_file(pvr_filename))); } catch (const cannot_open_file&) { } - shared_ptr vq(new VersionedQuest(0, 0, cli_quest_version, 0, bin_data, dat_data, pvr_data)); + shared_ptr vq(new VersionedQuest(0, 0, version, 0, bin_data, dat_data, pvr_data)); if (download) { vq = vq->create_download_quest(); } string qst_data = vq->encode_qst(); - write_output_data(qst_data.data(), qst_data.size()); - break; - } + write_output_data(args, qst_data.data(), qst_data.size(), "qst"); + }); - case Behavior::DISASSEMBLE_QUEST_SCRIPT: { - if (!input_filename || !strcmp(input_filename, "-")) { - throw invalid_argument("an input filename is required"); - } - - string data = read_input_data(); - if (!expect_decompressed) { +Action a_disassemble_quest_script( + "disassemble-quest-script", "\ + disassemble-quest-script [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ + Disassemble the input quest script (.bin file) into a text representation\n\ + of the commands and metadata it contains. Specify the quest\'s game version\n\ + with one of the --dc-nte, --dc-v1, --dc-v2, --pc, --gc-nte, --gc, --gc-ep3,\n\ + --xb, or --bb options.\n", + +[](Arguments& args) { + string data = read_input_data(args); + auto version = get_cli_version(args); + if (!args.get("decompressed")) { data = prs_decompress(data); } - string result = disassemble_quest_script(data.data(), data.size(), cli_quest_version, 1); - write_output_data(result.data(), result.size()); - break; - } - - case Behavior::DISASSEMBLE_QUEST_MAP: { - if (!input_filename || !strcmp(input_filename, "-")) { - throw invalid_argument("an input filename is required"); - } - - string data = read_input_data(); - if (!expect_decompressed) { + string result = disassemble_quest_script(data.data(), data.size(), version, 1); + write_output_data(args, result.data(), result.size(), "txt"); + }); +Action a_disassemble_quest_map( + "disassemble-quest-map", "\ + disassemble-quest-map [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ + Disassemble the input quest map (.dat file) into a text representation of\n\ + the data it contains. Specify the quest\'s game version with one of the\n\ + --dc-nte, --dc-v1, --dc-v2, --pc, --gc-nte, --gc, --xb, or --bb options.\n", + +[](Arguments& args) { + string data = read_input_data(args); + if (!args.get("decompressed")) { data = prs_decompress(data); } string result = Map::disassemble_quest_data(data.data(), data.size()); - write_output_data(result.data(), result.size()); - break; - } + write_output_data(args, result.data(), result.size(), "txt"); + }); - case Behavior::EXTRACT_AFS: - case Behavior::EXTRACT_GSL: - case Behavior::EXTRACT_BML: { - string output_prefix; - if (!output_filename) { - output_prefix = input_filename; - output_prefix.push_back('_'); - } else if (!strcmp(output_filename, "-")) { - throw invalid_argument("output prefix cannot be stdout"); +void a_extract_archive_fn(Arguments& args) { + string output_prefix = args.get(2, false); + if (output_prefix == "-") { + throw invalid_argument("output prefix cannot be stdout"); + } else if (output_prefix.empty()) { + output_prefix = args.get(1, false); + if (output_prefix.empty() || (output_prefix == "-")) { + throw invalid_argument("an input filename must be given"); + } + output_prefix += "_"; + } + + string data = read_input_data(args); + shared_ptr data_shared(new string(std::move(data))); + + if (args.get(0) == "extract-afs") { + AFSArchive arch(data_shared); + const auto& all_entries = arch.all_entries(); + for (size_t z = 0; z < all_entries.size(); z++) { + auto e = arch.get(z); + string out_file = string_printf("%s-%zu", output_prefix.c_str(), z); + save_file(out_file.c_str(), e.first, e.second); + fprintf(stderr, "... %s\n", out_file.c_str()); + } + } else if (args.get(0) == "extract-gsl") { + GSLArchive arch(data_shared, args.get("big-endian")); + for (const auto& entry_it : arch.all_entries()) { + auto e = arch.get(entry_it.first); + string out_file = output_prefix + entry_it.first; + save_file(out_file.c_str(), e.first, e.second); + fprintf(stderr, "... %s\n", out_file.c_str()); + } + } else if (args.get(0) == "extract-bml") { + BMLArchive arch(data_shared, args.get("big-endian")); + for (const auto& entry_it : arch.all_entries()) { + { + auto e = arch.get(entry_it.first); + string data = prs_decompress(e.first, e.second); + string out_file = output_prefix + entry_it.first; + save_file(out_file, data); + fprintf(stderr, "... %s\n", out_file.c_str()); } - string data = read_input_data(); - shared_ptr data_shared(new string(std::move(data))); - - if (behavior == Behavior::EXTRACT_AFS) { - AFSArchive arch(data_shared); - const auto& all_entries = arch.all_entries(); - for (size_t z = 0; z < all_entries.size(); z++) { - auto e = arch.get(z); - string out_file = string_printf("%s-%zu", output_prefix.c_str(), z); - save_file(out_file.c_str(), e.first, e.second); - fprintf(stderr, "... %s\n", out_file.c_str()); - } - } else if (behavior == Behavior::EXTRACT_GSL) { - GSLArchive arch(data_shared, big_endian); - for (const auto& entry_it : arch.all_entries()) { - auto e = arch.get(entry_it.first); - string out_file = output_prefix + entry_it.first; - save_file(out_file.c_str(), e.first, e.second); - fprintf(stderr, "... %s\n", out_file.c_str()); - } - } else { - BMLArchive arch(data_shared, big_endian); - for (const auto& entry_it : arch.all_entries()) { - { - auto e = arch.get(entry_it.first); - string data = prs_decompress(e.first, e.second); - string out_file = output_prefix + entry_it.first; - save_file(out_file, data); - fprintf(stderr, "... %s\n", out_file.c_str()); - } - - auto gvm_e = arch.get_gvm(entry_it.first); - if (gvm_e.second) { - string data = prs_decompress(gvm_e.first, gvm_e.second); - string out_file = output_prefix + entry_it.first + ".gvm"; - save_file(out_file, data); - fprintf(stderr, "... %s\n", out_file.c_str()); - } - } + auto gvm_e = arch.get_gvm(entry_it.first); + if (gvm_e.second) { + string data = prs_decompress(gvm_e.first, gvm_e.second); + string out_file = output_prefix + entry_it.first + ".gvm"; + save_file(out_file, data); + fprintf(stderr, "... %s\n", out_file.c_str()); } - break; } + } else { + throw logic_error("unimplemented archive type"); + } +} - case Behavior::DECODE_TEXT_ARCHIVE: { - string data = read_input_data(); - TextArchive a(data, big_endian); +Action a_extract_afs("extract-afs", nullptr, a_extract_archive_fn); +Action a_extract_gsl("extract-gsl", nullptr, a_extract_archive_fn); +Action a_extract_bml("extract-bml", "\ + extract-afs [INPUT-FILENAME] [--big-endian]\n\ + extract-gsl [INPUT-FILENAME] [--big-endian]\n\ + extract-bml [INPUT-FILENAME] [--big-endian]\n\ + Extract all files from an AFS, GSL, or BML archive into the current\n\ + directory. input-filename may be specified. If output-filename is\n\ + specified, then it is treated as a prefix which is prepended to the\n\ + filename of each file contained in the archive. If --big-endian is given,\n\ + the archive header is read in GameCube format; otherwise it is read in\n\ + PC/BB format.\n", + a_extract_archive_fn); + +Action a_decode_text_archive( + "decode-text-archive", nullptr, +[](Arguments& args) { + string data = read_input_data(args); + TextArchive a(data, args.get("big-endian")); JSON j = a.json(); string out_data = j.serialize(JSON::SerializeOption::FORMAT); - write_output_data(out_data.data(), out_data.size()); - break; - } - case Behavior::ENCODE_TEXT_ARCHIVE: { - auto json = JSON::parse(read_input_data()); + write_output_data(args, out_data.data(), out_data.size(), "json"); + }); +Action a_encode_text_archive( + "encode-text-archive", "\ +decode-text-archive [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ + encode-text-archive [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ + Decode a text archive (e.g. TextEnglish.pr2) to JSON for easy editing, or\n\ + encode a JSON file to a text archive.\n", + +[](Arguments& args) { + const string& input_filename = args.get(1, false); + const string& output_filename = args.get(2, false); + + auto json = JSON::parse(read_input_data(args)); TextArchive a(json); - auto result = a.serialize(big_endian); - if (!output_filename) { - if (!input_filename || !strcmp(input_filename, "-")) { + auto result = a.serialize(args.get("big-endian")); + if (output_filename.empty()) { + if (input_filename.empty() || (input_filename == "-")) { throw runtime_error("encoded text archive cannot be written to stdout"); } - save_file(string_printf("%s.pr2", input_filename), result.first); - save_file(string_printf("%s.pr3", input_filename), result.second); - } else if (!strcmp(input_filename, "-")) { + save_file(string_printf("%s.pr2", input_filename.c_str()), result.first); + save_file(string_printf("%s.pr3", input_filename.c_str()), result.second); + } else if (output_filename == "-") { throw runtime_error("encoded text archive cannot be written to stdout"); } else { string out_filename = output_filename; @@ -1519,10 +1161,11 @@ int main(int argc, char** argv) { save_file(out_filename + ".pr3", result.second); } } - break; - } - case Behavior::DECODE_UNICODE_TEXT_SET: { - auto collections = parse_unicode_text_set(read_input_data()); + }); + +Action a_decode_unicode_text_set( + "decode-unicode-text-set", nullptr, +[](Arguments& args) { + auto collections = parse_unicode_text_set(read_input_data(args)); JSON j = JSON::list(); for (const auto& collection : collections) { JSON& coll_j = j.emplace_back(JSON::list()); @@ -1531,11 +1174,16 @@ int main(int argc, char** argv) { } } string out_data = j.serialize(JSON::SerializeOption::FORMAT); - write_output_data(out_data.data(), out_data.size()); - break; - } - case Behavior::ENCODE_UNICODE_TEXT_SET: { - auto json = JSON::parse(read_input_data()); + write_output_data(args, out_data.data(), out_data.size(), "json"); + }); +Action a_encode_unicode_text_set( + "encode-unicode-text-set", "\ + decode-unicode-text-set [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ + encode-unicode-text-set [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ + Decode a Unicode text set (e.g. unitxt_e.prs) to JSON for easy editing, or\n\ + encode a JSON file to a Unicode text set.\n", + +[](Arguments& args) { + auto json = JSON::parse(read_input_data(args)); vector> collections; for (const auto& coll_json : json.as_list()) { auto& collection = collections.emplace_back(); @@ -1544,13 +1192,24 @@ int main(int argc, char** argv) { } } string encoded = serialize_unicode_text_set(collections); - write_output_data(encoded.data(), encoded.size()); - break; - } + write_output_data(args, encoded.data(), encoded.size(), "prs"); + }); - case Behavior::CAT_CLIENT: { +Action a_cat_client( + "cat-client", "\ + cat-client ADDR:PORT\n\ + Connect to the given server and simulate a PSO client. newserv will then\n\ + print all the received commands to stdout, and forward any commands typed\n\ + into stdin to the remote server. It is assumed that the input and output\n\ + are terminals, so all commands are hex-encoded. The --patch, --dc, --pc,\n\ + --gc, and --bb options can be used to select the command format and\n\ + encryption. If --bb is used, the --key=KEY-NAME option is also required (as\n\ + in decrypt-data above).\n", + +[](Arguments& args) { + auto version = get_cli_version(args); shared_ptr key; - if (cli_version == GameVersion::BB) { + if (uses_v4_encryption(version)) { + string key_file_name = args.get("key"); if (key_file_name.empty()) { throw runtime_error("a key filename is required for BB client emulation"); } @@ -1558,83 +1217,81 @@ int main(int argc, char** argv) { load_object_file("system/blueburst/keys/" + key_file_name + ".nsk"))); } shared_ptr base(event_base_new(), event_base_free); - auto cat_client_remote = make_sockaddr_storage(parse_netloc(input_filename)).first; - CatSession session(base, cat_client_remote, cli_version, key); + auto cat_client_remote = make_sockaddr_storage(parse_netloc(args.get(1))).first; + CatSession session(base, cat_client_remote, get_cli_version(args), key); event_base_dispatch(base.get()); - break; - } + }); - case Behavior::CONVERT_RARE_ITEM_SET: { +Action a_convert_rare_item_set( + "convert-rare-item-set", "\ + convert-rare-item-set INPUT-FILENAME [OUTPUT-FILENAME]\n\ + If OUTPUT-FILENAME is not given, print the contents of a rare item table in\n\ + a human-readable format. Otherwise, convert the input rare item set to a\n\ + different format and write it to OUTPUT-FILENAME. Both filenames must end\n\ + in one of the following extensions:\n\ + .json (newserv JSON rare item table)\n\ + .gsl (PSO BB little-endian GSL archive)\n\ + .gslb (PSO GC big-endian GSL archive)\n\ + .afs (PSO V2 little-endian AFS archive)\n\ + .rel (Schtserv rare table; cannot be used in output filename)\n", + +[](Arguments& args) { auto name_index = make_shared( JSON::parse(load_file("system/item-tables/names-v2.json")), JSON::parse(load_file("system/item-tables/names-v3.json")), JSON::parse(load_file("system/item-tables/names-v4.json"))); - if (!input_filename || !strcmp(input_filename, "-")) { + string input_filename = args.get(1, false); + if (input_filename.empty() || (input_filename == "-")) { throw runtime_error("input filename must be given"); } + auto version = get_cli_version(args); - shared_ptr data(new string(read_input_data())); + shared_ptr data(new string(read_input_data(args))); shared_ptr rs; if (ends_with(input_filename, ".json")) { - rs.reset(new RareItemSet(JSON::parse(*data), cli_version, name_index)); + rs.reset(new RareItemSet(JSON::parse(*data), version, name_index)); } else if (ends_with(input_filename, ".gsl")) { rs.reset(new RareItemSet(GSLArchive(data, false), false)); } else if (ends_with(input_filename, ".gslb")) { rs.reset(new RareItemSet(GSLArchive(data, true), true)); } else if (ends_with(input_filename, ".afs")) { - rs.reset(new RareItemSet(AFSArchive(data), (cli_quest_version == QuestScriptVersion::DC_V1))); + rs.reset(new RareItemSet(AFSArchive(data), is_v1(version))); } else if (ends_with(input_filename, ".rel")) { rs.reset(new RareItemSet(*data, true)); } else { throw runtime_error("cannot determine input format; use a filename ending with .json, .gsl, .gslb, .afs, or .rel"); } - if (!output_filename || !strcmp(output_filename, "-")) { - rs->print_all_collections(stdout, cli_version, name_index); + string output_filename = args.get(2, false); + if (output_filename.empty() || (output_filename == "-")) { + rs->print_all_collections(stdout, version, name_index); } else if (ends_with(output_filename, ".json")) { - string data = rs->serialize_json(cli_version, name_index); - write_output_data(data.data(), data.size()); + string data = rs->serialize_json(version, name_index); + write_output_data(args, data.data(), data.size(), nullptr); } else if (ends_with(output_filename, ".gsl")) { - string data = rs->serialize_gsl(big_endian); - write_output_data(data.data(), data.size()); + string data = rs->serialize_gsl(args.get("big-endian")); + write_output_data(args, data.data(), data.size(), nullptr); } else if (ends_with(output_filename, ".gslb")) { string data = rs->serialize_gsl(true); - write_output_data(data.data(), data.size()); + write_output_data(args, data.data(), data.size(), nullptr); } else if (ends_with(output_filename, ".afs")) { string data = rs->serialize_afs(); - write_output_data(data.data(), data.size()); + write_output_data(args, data.data(), data.size(), nullptr); } else { throw runtime_error("cannot determine output format; use a filename ending with .json, .gsl, .gslb, or .afs"); } + }); - break; - } +Action a_describe_item( + "describe-item", "\ + describe-item DATA-OR-DESCRIPTION\n\ + Describe an item. The argument may be the item\'s raw hex code or a textual\n\ + description of the item. If the description contains spaces, it must be\n\ + quoted, such as \"L&K14 COMBAT +10 0/10/15/0/35\".\n", + +[](Arguments& args) { + string description = args.get(1); + auto version = get_cli_version(args); - case Behavior::DESCRIBE_ITEM: { - auto name_index = make_shared( - JSON::parse(load_file("system/item-tables/names-v2.json")), - JSON::parse(load_file("system/item-tables/names-v3.json")), - JSON::parse(load_file("system/item-tables/names-v4.json"))); - - string data = parse_data_string(input_filename); - - ItemData item; - if (data.size() == sizeof(ItemData)) { - item = *reinterpret_cast(data.data()); - } else { - memcpy(&item.data1[0], data.data(), min(sizeof(item.data1), data.size())); - if (data.size() > sizeof(item.data1)) { - memcpy(&item.data2[0], data.data() + sizeof(item.data1), min(sizeof(item.data2), data.size() - sizeof(item.data1))); - } - } - - string desc = name_index->describe_item(cli_version, item); - log_info("Item: %s", desc.c_str()); - break; - } - - case Behavior::ENCODE_ITEM: { auto name_index = make_shared( JSON::parse(load_file("system/item-tables/names-v2.json")), JSON::parse(load_file("system/item-tables/names-v3.json")), @@ -1646,9 +1303,9 @@ int main(int argc, char** argv) { shared_ptr pmt_data_v4(new string(prs_decompress(load_file("system/item-tables/ItemPMT-bb.prs")))); auto pmt_v4 = make_shared(pmt_data_v4, ItemParameterTable::Version::V4); - ItemData item = name_index->parse_item_description(cli_version, input_filename); + ItemData item = name_index->parse_item_description(version, description); - string desc = name_index->describe_item(cli_version, item); + string desc = name_index->describe_item(version, item); log_info("Data (decoded): %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX -------- %02hhX%02hhX%02hhX%02hhX", item.data1[0], item.data1[1], item.data1[2], item.data1[3], item.data1[4], item.data1[5], item.data1[6], item.data1[7], @@ -1656,9 +1313,9 @@ int main(int argc, char** argv) { item.data2[0], item.data2[1], item.data2[2], item.data2[3]); ItemData item_v1 = item; - item_v1.encode_for_version(GameVersion::PC, pmt_v2); + item_v1.encode_for_version(Version::PC_V2, pmt_v2); ItemData item_v1_decoded = item_v1; - item_v1_decoded.decode_for_version(GameVersion::PC); + item_v1_decoded.decode_for_version(Version::PC_V2); log_info("Data (V1-encoded): %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX -------- %02hhX%02hhX%02hhX%02hhX", item_v1.data1[0], item_v1.data1[1], item_v1.data1[2], item_v1.data1[3], @@ -1675,9 +1332,9 @@ int main(int argc, char** argv) { } ItemData item_gc = item; - item_gc.encode_for_version(GameVersion::GC, pmt_v3); + item_gc.encode_for_version(Version::GC_V3, pmt_v3); ItemData item_gc_decoded = item_gc; - item_gc_decoded.decode_for_version(GameVersion::GC); + item_gc_decoded.decode_for_version(Version::GC_V3); log_info("Data (GC-encoded): %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX -------- %02hhX%02hhX%02hhX%02hhX", item_gc.data1[0], item_gc.data1[1], item_gc.data1[2], item_gc.data1[3], @@ -1698,10 +1355,16 @@ int main(int argc, char** argv) { size_t purchase_price = pmt_v4->price_for_item(item); size_t sale_price = purchase_price >> 3; log_info("Purchase price: %zu; sale price: %zu", purchase_price, sale_price); - break; - } + }); + +Action a_show_ep3_cards( + "show-ep3-cards", "\ + show-ep3-cards\n\ + Print the Episode 3 card definitions from the system/ep3 directory in a\n\ + human-readable format.\n", + +[](Arguments& args) { + bool one_line = args.get("one-line"); - case Behavior::SHOW_EP3_CARDS: { Episode3::CardIndex card_index( "system/ep3/card-definitions.mnr", "system/ep3/card-definitions.mnrd", @@ -1743,10 +1406,16 @@ int main(int argc, char** argv) { fputc('\n', stdout); } } - break; - } + }); + +Action a_generate_ep3_cards_html( + "generate-ep3-cards-html", "\ + generate-ep3-cards-html\n\ + Generate an HTML file describing all Episode 3 card definitions from the\n\ + system/ep3 directory.\n", + +[](Arguments& args) { + size_t num_threads = args.get("threads", 0); - case Behavior::GENERATE_EP3_CARDS_HTML: { Episode3::CardIndex card_index( "system/ep3/card-definitions.mnr", "system/ep3/card-definitions.mnrd", @@ -1878,10 +1547,14 @@ int main(int argc, char** argv) { blocks.emplace_back(""); save_file("cards.html", join(blocks, "")); - break; - } + }); - case Behavior::SHOW_EP3_MAPS: { +Action a_show_ep3_maps( + "show-ep3-maps", "\ + show-ep3-maps\n\ + Print the Episode 3 maps from the system/ep3 directory in a (sort of)\n\ + human-readable format.\n", + +[](Arguments&) { config_log.info("Collecting Episode 3 data"); Episode3::MapIndex map_index("system/ep3/maps"); Episode3::CardIndex card_index("system/ep3/card-definitions.mnr", "system/ep3/card-definitions.mnrd"); @@ -1899,23 +1572,34 @@ int main(int argc, char** argv) { fprintf(stdout, "(%c) %s\n", char_for_language_code(language), s.c_str()); } } - break; - } + }); - case Behavior::PARSE_OBJECT_GRAPH: { - string data = read_input_data(); +Action a_parse_object_graph( + "parse-object-graph", nullptr, +[](Arguments& args) { + uint32_t root_object_address = args.get("root", Arguments::IntFormat::HEX); + string data = read_input_data(args); PSOGCObjectGraph g(data, root_object_address); g.print(stdout); - break; - } + }); - case Behavior::GENERATE_DC_SERIAL_NUMBER: { +Action a_generate_dc_serial_number( + "generate-dc-serial-number", "\ + generate-dc-serial-number DOMAIN SUBDOMAIN\n\ + Generate a PSO DC serial number. DOMAIN should be 0 for Japanese, 1 for\n\ + USA, or 2 for Europe. SUBDOMAIN should be 0 for v1, or 1 for v2.\n", + +[](Arguments& args) { + uint8_t domain = args.get(1); + uint8_t subdomain = args.get(2); string serial_number = generate_dc_serial_number(domain, subdomain); fprintf(stdout, "%s\n", serial_number.c_str()); - break; - } + }); +Action a_generate_all_dc_serial_numbers( + "generate-all-dc-serial-numbers", "\ + generate-all-dc-serial-numbers\n\ + Generate all possible PSO DC serial numbers.\n", + +[](Arguments& args) { + size_t num_threads = args.get("threads", 0); - case Behavior::GENERATE_ALL_DC_SERIAL_NUMBERS: { auto serial_numbers = generate_all_dc_serial_numbers(); fprintf(stdout, "%zu (0x%zX) serial numbers found\n", serial_numbers.size(), serial_numbers.size()); for (const auto& it : serial_numbers) { @@ -1948,48 +1632,63 @@ int main(int argc, char** argv) { current_value, num_found, num_found); }; parallel_range(thread_fn, 0, 0x100000000, num_threads, progress_fn); - break; - } - - case Behavior::INSPECT_DC_SERIAL_NUMBER: { - if (!input_filename) { + }); +Action a_inspect_dc_serial_number( + "inspect-dc-serial-number", "\ + inspect-dc-serial-number SERIAL-NUMBER\n\ + Show which domain and subdomain the serial number belongs to. (As with\n\ + generate-dc-serial-number, described above, this will tell you which PSO\n\ + version it is valid for.)\n", + +[](Arguments& args) { + const string& serial_number_str = args.get(1, false); + if (serial_number_str.empty()) { throw invalid_argument("no serial number given"); } size_t num_valid_subdomains = 0; for (uint8_t domain = 0; domain < 3; domain++) { for (uint8_t subdomain = 0; subdomain < 3; subdomain++) { - if (dc_serial_number_is_valid_fast(input_filename, domain, subdomain)) { - fprintf(stdout, "%s is valid in domain %hhu subdomain %hhu\n", input_filename, domain, subdomain); + if (dc_serial_number_is_valid_fast(serial_number_str, domain, subdomain)) { + fprintf(stdout, "%s is valid in domain %hhu subdomain %hhu\n", serial_number_str.c_str(), domain, subdomain); num_valid_subdomains++; } } } if (num_valid_subdomains == 0) { - fprintf(stdout, "%s is not valid in any domain\n", input_filename); + fprintf(stdout, "%s is not valid in any domain\n", serial_number_str.c_str()); } - break; - } - - case Behavior::DC_SERIAL_NUMBER_SPEED_TEST: + }); +Action a_dc_serial_number_speed_test( + "dc-serial-number-speed-test", "\ + dc-serial-number-speed-test\n\ + Run a speed test of the two DC serial number validation functions.\n", + +[](Arguments& args) { + const string& seed = args.get("seed"); if (seed.empty()) { dc_serial_number_speed_test(); } else { dc_serial_number_speed_test(stoul(seed, nullptr, 16)); } - break; + }); - case Behavior::AR_CODE_TRANSLATOR: - if (!input_filename || !strcmp(input_filename, "-")) { +Action a_ar_code_translator( + "ar-code-translator", nullptr, +[](Arguments& args) { + const string& dir = args.get(1, false); + if (dir.empty() || (dir == "-")) { throw invalid_argument("a directory name is required"); } - run_ar_code_translator(input_filename); - break; + run_ar_code_translator(dir, args.get(2, false), args.get(3, false)); + }); + +Action a_run_server_replay_log( + "", nullptr, +[](Arguments& args) { + string config_filename = args.get("config"); + const string& replay_log_filename = args.get("replay-log"); + bool is_replay = !replay_log_filename.empty(); + if (config_filename.empty()) { + config_filename = "system/config.json"; + } - case Behavior::REPLAY_LOG: - case Behavior::RUN_SERVER: { - bool is_replay = behavior == Behavior::REPLAY_LOG; signal(SIGPIPE, SIG_IGN); - if (isatty(fileno(stderr))) { use_terminal_colors = true; } @@ -2003,7 +1702,7 @@ int main(int argc, char** argv) { state->init(); shared_ptr dns_server; - if (state->dns_server_port && (behavior != Behavior::REPLAY_LOG)) { + if (state->dns_server_port && !is_replay) { config_log.info("Starting DNS server on port %hu", state->dns_server_port); dns_server.reset(new DNSServer(base, state->local_address, state->external_address)); @@ -2015,7 +1714,7 @@ int main(int argc, char** argv) { shared_ptr shell; shared_ptr replay_session; shared_ptr ip_stack_simulator; - if (behavior == Behavior::REPLAY_LOG) { + if (is_replay) { config_log.info("Starting proxy server"); state->proxy_server.reset(new ProxyServer(base, state)); config_log.info("Starting game server"); @@ -2023,14 +1722,14 @@ int main(int argc, char** argv) { auto nop_destructor = +[](FILE*) {}; shared_ptr log_f(stdin, nop_destructor); - if (input_filename && strcmp(input_filename, "-")) { - log_f = fopen_shared(input_filename, "rt"); + if (replay_log_filename != "-") { + log_f = fopen_shared(replay_log_filename, "rt"); } - replay_session.reset(new ReplaySession(base, log_f.get(), state, replay_require_basic_credentials)); + replay_session.reset(new ReplaySession(base, log_f.get(), state, args.get("require-basic-credentials"))); replay_session->start(); - } else if (behavior == Behavior::RUN_SERVER) { + } else { config_log.info("Opening sockets"); for (const auto& it : state->name_to_port_config) { const auto& pc = it.second; @@ -2045,12 +1744,12 @@ int main(int argc, char** argv) { // no way to ask the client which destination they want, so only one // destination is supported, and we have to manually specify the // destination netloc here. - if (pc->version == GameVersion::PATCH) { + if (is_patch(pc->version)) { auto [ss, size] = make_sockaddr_storage( state->proxy_destination_patch.first, state->proxy_destination_patch.second); state->proxy_server->listen(pc->port, pc->version, &ss); - } else if (pc->version == GameVersion::BB) { + } else if (is_v4(pc->version)) { auto [ss, size] = make_sockaddr_storage( state->proxy_destination_bb.first, state->proxy_destination_bb.second); @@ -2064,9 +1763,7 @@ int main(int argc, char** argv) { config_log.info("Starting game server"); state->game_server.reset(new Server(base, state)); } - string spec = string_printf("T-%hu-%s-%s-%s", - pc->port, name_for_version(pc->version), pc->name.c_str(), - name_for_server_behavior(pc->behavior)); + string spec = string_printf("T-%hu-%s-%s-%s", pc->port, name_for_enum(pc->version), pc->name.c_str(), name_for_enum(pc->behavior)); state->game_server->listen(spec, "", pc->port, pc->version, pc->behavior); } } @@ -2080,9 +1777,6 @@ int main(int argc, char** argv) { ip_stack_simulator->listen(spec, netloc.first, netloc.second); } } - - } else { - throw logic_error("invalid behavior"); } if (!state->username.empty()) { @@ -2118,12 +1812,58 @@ int main(int argc, char** argv) { config_log.info("Normal shutdown"); state->proxy_server.reset(); // Break reference cycle - break; - } + }); - default: - throw logic_error("invalid behavior"); +void print_usage() { + fputs("\ +Usage:\n\ + newserv [ACTION] [OPTIONS...]\n\ +\n\ +If ACTION is not specified, newserv runs in server mode. PSO clients can\n\ +connect normally, join lobbies, play games, and use the proxy server. See\n\ +README.md and system/config.json for more information.\n\ +\n\ +When ACTION is given, newserv will do things other than running the server.\n\ +\n\ +Some actions accept input and/or output filenames; see the descriptions below\n\ +for details. If INPUT-FILENAME is missing or is '-', newserv reads from stdin.\n\ +If OUTPUT-FILENAME is missing and the input is not from stdin, newserv writes\n\ +the output to INPUT-FILENAME.dec or a similarly-named file; if OUTPUT-FILENAME\n\ +is '-', newserv writes the output to stdout. If stdout is a terminal and the\n\ +output is not text or JSON, the data written to stdout is formatted in a\n\ +hex/ASCII view; in any other case, the raw output is written to stdout, which\n\ +(for most actions) may include arbitrary binary data.\n\ +\n\ +The actions are:\n", + stderr); + for (const auto& a : action_order) { + if (a->help_text) { + fputs(a->help_text, stderr); + } + } + fputs("\ +Most options that take data as input also accept the following option:\n\ + --parse-data\n\ + For modes that take input (from a file or from stdin), parse the input as\n\ + a hex string before encrypting/decoding/etc.\n\ +\n", + stderr); +} + +int main(int argc, char** argv) { + Arguments args(&argv[1], argc - 1); + if (args.get("help")) { + print_usage(); + return 0; } - return 0; + string action_name = args.get(0, false); + try { + const auto& a = all_actions.at(action_name); + a->run(args); + return 0; + } catch (const out_of_range&) { + log_error("Unknown or invalid action; try --help"); + return 1; + } } diff --git a/src/Map.cc b/src/Map.cc index a60d837d..0e9c2bb3 100644 --- a/src/Map.cc +++ b/src/Map.cc @@ -85,7 +85,7 @@ string Map::Object::str(shared_ptr name_index) const { string item_name; try { auto item = ItemCreator::base_item_for_specialized_box(this->param4, this->param5, this->param6); - item_name = name_index ? name_index->describe_item(GameVersion::BB, item) : item.hex(); + item_name = name_index ? name_index->describe_item(Version::BB_V4, item) : item.hex(); } catch (const exception& e) { item_name = string_printf("(failed: %s)", e.what()); } diff --git a/src/PSOProtocol.cc b/src/PSOProtocol.cc index 3b050e7d..f683e443 100644 --- a/src/PSOProtocol.cc +++ b/src/PSOProtocol.cc @@ -17,40 +17,54 @@ PSOCommandHeader::PSOCommandHeader() { this->bb.flag = 0; } -uint16_t PSOCommandHeader::command(GameVersion version) const { +uint16_t PSOCommandHeader::command(Version version) const { switch (version) { - case GameVersion::DC: - return this->dc.command; - case GameVersion::GC: - return this->gc.command; - case GameVersion::XB: - return this->xb.command; - case GameVersion::PC: - case GameVersion::PATCH: + case Version::PC_PATCH: + case Version::BB_PATCH: + case Version::PC_V2: return this->pc.command; - case GameVersion::BB: + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + return this->dc.command; + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + return this->gc.command; + case Version::XB_V3: + return this->xb.command; + case Version::BB_V4: return this->bb.command; default: throw logic_error("unknown game version"); } } -void PSOCommandHeader::set_command(GameVersion version, uint16_t command) { +void PSOCommandHeader::set_command(Version version, uint16_t command) { switch (version) { - case GameVersion::DC: - this->dc.command = command; - break; - case GameVersion::GC: - this->gc.command = command; - break; - case GameVersion::XB: - this->xb.command = command; - break; - case GameVersion::PC: - case GameVersion::PATCH: + case Version::PC_PATCH: + case Version::BB_PATCH: + case Version::PC_V2: this->pc.command = command; break; - case GameVersion::BB: + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + this->dc.command = command; + break; + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + this->gc.command = command; + break; + case Version::XB_V3: + this->xb.command = command; + break; + case Version::BB_V4: this->bb.command = command; break; default: @@ -58,40 +72,54 @@ void PSOCommandHeader::set_command(GameVersion version, uint16_t command) { } } -uint16_t PSOCommandHeader::size(GameVersion version) const { +uint16_t PSOCommandHeader::size(Version version) const { switch (version) { - case GameVersion::DC: - return this->dc.size; - case GameVersion::GC: - return this->gc.size; - case GameVersion::XB: - return this->xb.size; - case GameVersion::PC: - case GameVersion::PATCH: + case Version::PC_PATCH: + case Version::BB_PATCH: + case Version::PC_V2: return this->pc.size; - case GameVersion::BB: + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + return this->dc.size; + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + return this->gc.size; + case Version::XB_V3: + return this->xb.size; + case Version::BB_V4: return this->bb.size; default: throw logic_error("unknown game version"); } } -void PSOCommandHeader::set_size(GameVersion version, uint32_t size) { +void PSOCommandHeader::set_size(Version version, uint32_t size) { switch (version) { - case GameVersion::DC: - this->dc.size = size; - break; - case GameVersion::GC: - this->gc.size = size; - break; - case GameVersion::XB: - this->xb.size = size; - break; - case GameVersion::PC: - case GameVersion::PATCH: + case Version::PC_PATCH: + case Version::BB_PATCH: + case Version::PC_V2: this->pc.size = size; break; - case GameVersion::BB: + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + this->dc.size = size; + break; + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + this->gc.size = size; + break; + case Version::XB_V3: + this->xb.size = size; + break; + case Version::BB_V4: this->bb.size = size; break; default: @@ -99,40 +127,54 @@ void PSOCommandHeader::set_size(GameVersion version, uint32_t size) { } } -uint32_t PSOCommandHeader::flag(GameVersion version) const { +uint32_t PSOCommandHeader::flag(Version version) const { switch (version) { - case GameVersion::DC: - return this->dc.flag; - case GameVersion::GC: - return this->gc.flag; - case GameVersion::XB: - return this->xb.flag; - case GameVersion::PC: - case GameVersion::PATCH: + case Version::PC_PATCH: + case Version::BB_PATCH: + case Version::PC_V2: return this->pc.flag; - case GameVersion::BB: + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + return this->dc.flag; + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + return this->gc.flag; + case Version::XB_V3: + return this->xb.flag; + case Version::BB_V4: return this->bb.flag; default: throw logic_error("unknown game version"); } } -void PSOCommandHeader::set_flag(GameVersion version, uint32_t flag) { +void PSOCommandHeader::set_flag(Version version, uint32_t flag) { switch (version) { - case GameVersion::DC: - this->dc.flag = flag; - break; - case GameVersion::GC: - this->gc.flag = flag; - break; - case GameVersion::XB: - this->xb.flag = flag; - break; - case GameVersion::PC: - case GameVersion::PATCH: + case Version::PC_PATCH: + case Version::BB_PATCH: + case Version::PC_V2: this->pc.flag = flag; break; - case GameVersion::BB: + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + this->dc.flag = flag; + break; + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + this->gc.flag = flag; + break; + case Version::XB_V3: + this->xb.flag = flag; + break; + case Version::BB_V4: this->bb.flag = flag; break; default: @@ -157,16 +199,22 @@ void check_size_v(size_t size, size_t min_size, size_t max_size) { } std::string prepend_command_header( - GameVersion version, + Version version, bool encryption_enabled, uint16_t cmd, uint32_t flag, const std::string& data) { StringWriter ret; switch (version) { - case GameVersion::DC: - case GameVersion::GC: - case GameVersion::XB: { + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + case Version::XB_V3: { PSOCommandHeaderDCV3 header; if (encryption_enabled) { header.size = (sizeof(header) + data.size() + 3) & ~3; @@ -178,8 +226,10 @@ std::string prepend_command_header( ret.put(header); break; } - case GameVersion::PC: - case GameVersion::PATCH: { + + case Version::PC_PATCH: + case Version::BB_PATCH: + case Version::PC_V2: { PSOCommandHeaderPC header; if (encryption_enabled) { header.size = (sizeof(header) + data.size() + 3) & ~3; @@ -191,7 +241,8 @@ std::string prepend_command_header( ret.put(header); break; } - case GameVersion::BB: { + + case Version::BB_V4: { PSOCommandHeaderBB header; if (encryption_enabled) { header.size = (sizeof(header) + data.size() + 3) & ~3; @@ -203,6 +254,7 @@ std::string prepend_command_header( ret.put(header); break; } + default: throw logic_error("unimplemented game version in prepend_command_header"); } diff --git a/src/PSOProtocol.hh b/src/PSOProtocol.hh index 0de741a0..96af4569 100644 --- a/src/PSOProtocol.hh +++ b/src/PSOProtocol.hh @@ -34,14 +34,14 @@ union PSOCommandHeader { PSOCommandHeaderDCV3 xb; PSOCommandHeaderBB bb; - uint16_t command(GameVersion version) const; - void set_command(GameVersion version, uint16_t command); - uint16_t size(GameVersion version) const; - void set_size(GameVersion version, uint32_t size); - uint32_t flag(GameVersion version) const; - void set_flag(GameVersion version, uint32_t flag); - static inline size_t header_size(GameVersion version) { - return (version == GameVersion::BB) ? 8 : 4; + uint16_t command(Version version) const; + void set_command(Version version, uint16_t command); + uint16_t size(Version version) const; + void set_size(Version version, uint32_t size); + uint32_t flag(Version version) const; + void set_flag(Version version, uint32_t flag); + static inline size_t header_size(Version version) { + return (version == Version::BB_V4) ? 8 : 4; } PSOCommandHeader(); @@ -123,7 +123,7 @@ T& check_size_t(void* data, size_t size) { void check_size_v(size_t size, size_t min_size, size_t max_size = 0); std::string prepend_command_header( - GameVersion version, + Version version, bool encryption_enabled, uint16_t cmd, uint32_t flag, diff --git a/src/Player.cc b/src/Player.cc index aa736a98..0a3a7278 100644 --- a/src/Player.cc +++ b/src/Player.cc @@ -85,7 +85,7 @@ void ClientGameData::create_battle_overlay(shared_ptr rules, } } -void ClientGameData::create_challenge_overlay(GameVersion version, size_t template_index, shared_ptr level_table) { +void ClientGameData::create_challenge_overlay(Version version, size_t template_index, shared_ptr level_table) { auto p = this->character(true, false); const auto& tpl = get_challenge_template_definition(version, p->disp.visual.class_flags, template_index); diff --git a/src/Player.hh b/src/Player.hh index f0cdda64..b8b1d40e 100644 --- a/src/Player.hh +++ b/src/Player.hh @@ -56,7 +56,7 @@ public: ~ClientGameData(); void create_battle_overlay(std::shared_ptr rules, std::shared_ptr level_table); - void create_challenge_overlay(GameVersion version, size_t template_index, std::shared_ptr level_table); + void create_challenge_overlay(Version version, size_t template_index, std::shared_ptr level_table); inline void delete_overlay() { this->overlay_character_data.reset(); } diff --git a/src/PlayerSubordinates.cc b/src/PlayerSubordinates.cc index 3748906a..6658a68f 100644 --- a/src/PlayerSubordinates.cc +++ b/src/PlayerSubordinates.cc @@ -90,7 +90,7 @@ void PlayerDispDataDCPCV3::enforce_lobby_join_limits_for_client(shared_ptrversion() == GameVersion::PC) || (c->version() == GameVersion::DC)) { + if (is_v1_or_v2(c->version())) { // V1/V2 have fewer classes, so we'll substitute some here switch (this->visual.char_class) { case 0: // HUmar @@ -125,7 +125,7 @@ void PlayerDispDataDCPCV3::enforce_lobby_join_limits_for_client(shared_ptrvisual.char_class = 0; // Invalid classes -> HUmar } - this->visual.version = min(this->visual.version, c->config.check_flag(Client::Flag::IS_DC_V1) ? 0 : 2); + this->visual.version = min(this->visual.version, is_v1(c->version()) ? 0 : 2); maxes = &v1_v2_class_maxes[this->visual.char_class]; } else { @@ -152,7 +152,7 @@ void PlayerDispDataDCPCV3::enforce_lobby_join_limits_for_client(shared_ptr c) { - if (c->version() != GameVersion::BB) { + if (!is_v4(c->version())) { throw logic_error("PlayerDispDataBB being sent to non-BB client"); } this->play_time = 0; @@ -660,7 +660,7 @@ void PlayerInventory::decode_from_client(shared_ptr c) { } void PlayerInventory::encode_for_client(shared_ptr c) { - if (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) { + if (c->version() == Version::DC_NTE) { // DC NTE has the item count as a 32-bit value here, whereas every other // version uses a single byte. To stop DC NTE from crashing by trying to // construct far more than 30 TItem objects, we clear the fields DC NTE @@ -919,7 +919,7 @@ static PlayerInventoryItem make_template_item(bool equipped, uint64_t first_data static PlayerInventoryItem v2_item(bool equipped, uint64_t first_data, uint64_t second_data) { auto ret = make_template_item(equipped, first_data, second_data); - ret.data.decode_for_version(GameVersion::PC); + ret.data.decode_for_version(Version::PC_V2); return ret; } @@ -927,7 +927,7 @@ static PlayerInventoryItem v3_item(bool equipped, uint64_t first_data, uint64_t return make_template_item(equipped, first_data, second_data); } -const ChallengeTemplateDefinition& get_challenge_template_definition(GameVersion version, uint32_t class_flags, size_t index) { +const ChallengeTemplateDefinition& get_challenge_template_definition(Version version, uint32_t class_flags, size_t index) { // clang-format off static const vector v2_hunter_templates({ {0, {v2_item(true, 0x0001000000000000, 0x0000000000000000), v2_item(true, 0x0101000000000000, 0x0000000000000000), v2_item(true, 0x02000500F4010100, 0x0100010000002800), v2_item(false, 0x0300000000030000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}}, @@ -1040,14 +1040,12 @@ const ChallengeTemplateDefinition& get_challenge_template_definition(GameVersion }); // clang-format on - bool is_v2 = (version == GameVersion::DC) || (version == GameVersion::PC); - if ((class_flags & 0xE0) == 0x20) { - return is_v2 ? v2_hunter_templates.at(index) : v3_hunter_templates.at(index); + return is_v1_or_v2(version) ? v2_hunter_templates.at(index) : v3_hunter_templates.at(index); } else if ((class_flags & 0xE0) == 0x40) { - return is_v2 ? v2_ranger_templates.at(index) : v3_ranger_templates.at(index); + return is_v1_or_v2(version) ? v2_ranger_templates.at(index) : v3_ranger_templates.at(index); } else if ((class_flags & 0xE0) == 0x80) { - return is_v2 ? v2_force_templates.at(index) : v3_force_templates.at(index); + return is_v1_or_v2(version) ? v2_force_templates.at(index) : v3_force_templates.at(index); } else { throw runtime_error("invalid class flags on original player"); } diff --git a/src/PlayerSubordinates.hh b/src/PlayerSubordinates.hh index 23af8ec2..51031fa9 100644 --- a/src/PlayerSubordinates.hh +++ b/src/PlayerSubordinates.hh @@ -621,7 +621,7 @@ struct ChallengeTemplateDefinition { std::vector tech_levels; }; -const ChallengeTemplateDefinition& get_challenge_template_definition(GameVersion version, uint32_t class_flags, size_t index); +const ChallengeTemplateDefinition& get_challenge_template_definition(Version version, uint32_t class_flags, size_t index); struct SymbolChat { // Bits: ----------------------DMSSSCCCFF diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index aa278340..79c97b40 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -104,7 +104,7 @@ static HandlerResult default_handler(shared_ptr, uin static HandlerResult S_invalid(shared_ptr ses, uint16_t command, uint32_t flag, string&) { ses->log.error("Server sent invalid command"); - string error_str = (ses->version() == GameVersion::BB) + string error_str = is_v4(ses->version()) ? string_printf("Server sent invalid\ncommand: %04hX %08" PRIX32, command, flag) : string_printf("Server sent invalid\ncommand: %02hX %02" PRIX32, command, flag); ses->send_to_game_server(error_str.c_str()); @@ -112,7 +112,7 @@ static HandlerResult S_invalid(shared_ptr ses, uint1 } static HandlerResult C_05(shared_ptr ses, uint16_t, uint32_t, string&) { - ses->disconnect_action = ses->version() == GameVersion::BB + ses->disconnect_action = is_v4(ses->version()) ? ProxyServer::LinkedSession::DisconnectAction::MEDIUM_TIMEOUT : ProxyServer::LinkedSession::DisconnectAction::SHORT_TIMEOUT; return HandlerResult::Type::FORWARD; @@ -213,7 +213,7 @@ static HandlerResult S_V123P_02_17( uint16_t command, uint32_t flag, string& data) { - if (ses->version() == GameVersion::PATCH && command == 0x17) { + if (is_patch(ses->version()) && command == 0x17) { throw invalid_argument("patch server sent 17 server init"); } @@ -228,7 +228,7 @@ static HandlerResult S_V123P_02_17( // client will be able to understand what we sent. forward_command(ses, false, command, flag, data); - if ((ses->version() == GameVersion::GC) || (ses->version() == GameVersion::XB)) { + if (uses_v3_encryption(ses->version())) { ses->server_channel.crypt_in.reset(new PSOV3Encryption(cmd.server_key)); ses->server_channel.crypt_out.reset(new PSOV3Encryption(cmd.client_key)); ses->client_channel.crypt_in.reset(new PSOV3Encryption(cmd.client_key)); @@ -246,20 +246,12 @@ static HandlerResult S_V123P_02_17( ses->log.info("Existing license in linked session"); // This isn't forwarded to the client, so don't recreate the client's crypts - switch (ses->version()) { - case GameVersion::DC: - case GameVersion::PC: - case GameVersion::PATCH: - ses->server_channel.crypt_in.reset(new PSOV2Encryption(cmd.server_key)); - ses->server_channel.crypt_out.reset(new PSOV2Encryption(cmd.client_key)); - break; - case GameVersion::GC: - case GameVersion::XB: - ses->server_channel.crypt_in.reset(new PSOV3Encryption(cmd.server_key)); - ses->server_channel.crypt_out.reset(new PSOV3Encryption(cmd.client_key)); - break; - default: - throw logic_error("unsupported version"); + if (uses_v3_encryption(ses->version())) { + ses->server_channel.crypt_in.reset(new PSOV3Encryption(cmd.server_key)); + ses->server_channel.crypt_out.reset(new PSOV3Encryption(cmd.client_key)); + } else { + ses->server_channel.crypt_in.reset(new PSOV2Encryption(cmd.server_key)); + ses->server_channel.crypt_out.reset(new PSOV2Encryption(cmd.client_key)); } // Respond with an appropriate login command. We don't let the client do this @@ -267,94 +259,103 @@ static HandlerResult S_V123P_02_17( // in the patch server case, during the current session due to a hidden // redirect). switch (ses->version()) { - case GameVersion::PATCH: + case Version::PC_PATCH: + case Version::BB_PATCH: ses->server_channel.send(0x02); return HandlerResult::Type::SUPPRESS; - case GameVersion::DC: - case GameVersion::PC: - if (ses->config.check_flag(Client::Flag::IS_DC_V1)) { - if (command == 0x17) { - C_LoginV1_DC_PC_V3_90 cmd; - cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number)); - cmd.access_key.encode(ses->license->access_key); - cmd.access_key.clear_after(8); - ses->server_channel.send(0x90, 0x00, &cmd, sizeof(cmd)); - return HandlerResult::Type::SUPPRESS; - } else { - C_LoginV1_DC_93 cmd; - if (ses->remote_guild_card_number < 0) { - cmd.player_tag = 0xFFFF0000; - cmd.guild_card_number = 0xFFFFFFFF; - } else { - cmd.player_tag = 0x00010000; - cmd.guild_card_number = ses->remote_guild_card_number; - } - cmd.unknown_a1 = 0; - cmd.unknown_a2 = 0; - cmd.sub_version = ses->sub_version; - cmd.is_extended = 0; - cmd.language = ses->language(); - cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number)); - cmd.access_key.encode(ses->license->access_key); - cmd.access_key.clear_after(8); - cmd.hardware_id.encode(ses->hardware_id); - cmd.name.encode(ses->character_name); - ses->server_channel.send(0x93, 0x00, &cmd, sizeof(cmd)); - return HandlerResult::Type::SUPPRESS; - } - } else { // DCv2 or PC - if (command == 0x17) { - C_Login_DC_PC_V3_9A cmd; - if (ses->remote_guild_card_number < 0) { - cmd.player_tag = 0xFFFF0000; - cmd.guild_card_number = 0xFFFFFFFF; - } else { - cmd.player_tag = 0x00010000; - cmd.guild_card_number = ses->remote_guild_card_number; - } - cmd.sub_version = ses->sub_version; - cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number)); - cmd.access_key.encode(ses->license->access_key); - cmd.access_key.clear_after(8); - cmd.serial_number2 = cmd.serial_number; - cmd.access_key2 = cmd.access_key; - // TODO: We probably should set email_address, but we currently don't - // keep that value anywhere in the session object, nor is it saved in - // the License object. - ses->server_channel.send(0x9A, 0x00, &cmd, sizeof(cmd)); - return HandlerResult::Type::SUPPRESS; - } else { - C_Login_DC_PC_GC_9D cmd; - if (ses->remote_guild_card_number < 0) { - cmd.player_tag = 0xFFFF0000; - cmd.guild_card_number = 0xFFFFFFFF; - } else { - cmd.player_tag = 0x00010000; - cmd.guild_card_number = ses->remote_guild_card_number; - } - cmd.unused1 = 0; - cmd.unused2 = 0; - cmd.sub_version = ses->sub_version; - cmd.is_extended = 0; - cmd.language = ses->language(); - cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number)); - cmd.access_key.encode(ses->license->access_key); - cmd.access_key.clear_after(8); - cmd.serial_number2 = cmd.serial_number; - cmd.access_key2 = cmd.access_key; - if (ses->config.check_flag(Client::Flag::PROXY_BLANK_NAME_ENABLED)) { - cmd.name.encode(" ", ses->language()); - } else { - cmd.name.encode(ses->character_name); - } - ses->server_channel.send(0x9D, 0x00, &cmd, sizeof(cmd)); - return HandlerResult::Type::SUPPRESS; - } - } - throw logic_error("DC/PC init command not handled"); + case Version::DC_NTE: + // TODO + throw runtime_error("DC NTE proxy is not implemented"); - case GameVersion::GC: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + if (command == 0x17) { + C_LoginV1_DC_PC_V3_90 cmd; + cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number)); + cmd.access_key.encode(ses->license->access_key); + cmd.access_key.clear_after(8); + ses->server_channel.send(0x90, 0x00, &cmd, sizeof(cmd)); + return HandlerResult::Type::SUPPRESS; + } else { + C_LoginV1_DC_93 cmd; + if (ses->remote_guild_card_number < 0) { + cmd.player_tag = 0xFFFF0000; + cmd.guild_card_number = 0xFFFFFFFF; + } else { + cmd.player_tag = 0x00010000; + cmd.guild_card_number = ses->remote_guild_card_number; + } + cmd.unknown_a1 = 0; + cmd.unknown_a2 = 0; + cmd.sub_version = ses->sub_version; + cmd.is_extended = 0; + cmd.language = ses->language(); + cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number)); + cmd.access_key.encode(ses->license->access_key); + cmd.access_key.clear_after(8); + cmd.hardware_id.encode(ses->hardware_id); + cmd.name.encode(ses->character_name); + ses->server_channel.send(0x93, 0x00, &cmd, sizeof(cmd)); + return HandlerResult::Type::SUPPRESS; + } + break; + + case Version::DC_V2: + case Version::PC_V2: + case Version::GC_NTE: + if (command == 0x17) { + C_Login_DC_PC_V3_9A cmd; + if (ses->remote_guild_card_number < 0) { + cmd.player_tag = 0xFFFF0000; + cmd.guild_card_number = 0xFFFFFFFF; + } else { + cmd.player_tag = 0x00010000; + cmd.guild_card_number = ses->remote_guild_card_number; + } + cmd.sub_version = ses->sub_version; + cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number)); + cmd.access_key.encode(ses->license->access_key); + cmd.access_key.clear_after(8); + cmd.serial_number2 = cmd.serial_number; + cmd.access_key2 = cmd.access_key; + // TODO: We probably should set email_address, but we currently don't + // keep that value anywhere in the session object, nor is it saved in + // the License object. + ses->server_channel.send(0x9A, 0x00, &cmd, sizeof(cmd)); + return HandlerResult::Type::SUPPRESS; + } else { + C_Login_DC_PC_GC_9D cmd; + if (ses->remote_guild_card_number < 0) { + cmd.player_tag = 0xFFFF0000; + cmd.guild_card_number = 0xFFFFFFFF; + } else { + cmd.player_tag = 0x00010000; + cmd.guild_card_number = ses->remote_guild_card_number; + } + cmd.unused1 = 0; + cmd.unused2 = 0; + cmd.sub_version = ses->sub_version; + cmd.is_extended = 0; + cmd.language = ses->language(); + cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number)); + cmd.access_key.encode(ses->license->access_key); + cmd.access_key.clear_after(8); + cmd.serial_number2 = cmd.serial_number; + cmd.access_key2 = cmd.access_key; + if (ses->config.check_flag(Client::Flag::PROXY_BLANK_NAME_ENABLED)) { + cmd.name.encode(" ", ses->language()); + } else { + cmd.name.encode(ses->character_name); + } + ses->server_channel.send(0x9D, 0x00, &cmd, sizeof(cmd)); + return HandlerResult::Type::SUPPRESS; + } + break; + + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: if (command == 0x17) { C_VerifyLicense_V3_DB cmd; cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number)); @@ -410,7 +411,7 @@ static HandlerResult S_V123P_02_17( } throw logic_error("GC init command not handled"); - case GameVersion::XB: { + case Version::XB_V3: { C_LoginExtended_XB_9E cmd; if (ses->remote_guild_card_number < 0) { cmd.player_tag = 0xFFFF0000; @@ -441,6 +442,8 @@ static HandlerResult S_V123P_02_17( return HandlerResult::Type::SUPPRESS; } + case Version::BB_V4: + throw logic_error("v1/v2/v3 server init handler should not be called on BB"); default: throw logic_error("invalid game version in server init handler"); } @@ -646,7 +649,7 @@ static HandlerResult S_B2(shared_ptr ses, uint16_t, if (ses->config.check_flag(Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL)) { StringReader r(code); - bool is_big_endian = (ses->version() == GameVersion::GC || ses->version() == GameVersion::DC); + bool is_big_endian = ::is_big_endian(ses->version()); uint32_t decompressed_size = is_big_endian ? r.get_u32b() : r.get_u32l(); uint32_t key = is_big_endian ? r.get_u32b() : r.get_u32l(); @@ -682,7 +685,7 @@ static HandlerResult S_B2(shared_ptr ses, uint16_t, ses->log.info("Wrote code from server to file %s", output_filename.c_str()); #ifdef HAVE_RESOURCE_FILE - if (ses->version() == GameVersion::GC) { + if (is_gc(ses->version())) { try { if (code.size() < sizeof(S_ExecuteCode_Footer_GC_B2)) { throw runtime_error("code section is too small"); @@ -842,7 +845,7 @@ static HandlerResult S_19_P_14(shared_ptr ses, uint1 struct sockaddr_in* sin = reinterpret_cast( &ses->next_destination); sin->sin_family = AF_INET; - if (ses->version() == GameVersion::PATCH) { + if (is_patch(ses->version())) { auto& cmd = check_size_t(data); sin->sin_addr.s_addr = cmd.address.load_raw(); // Already big-endian sin->sin_port = htons(cmd.port); @@ -858,7 +861,7 @@ static HandlerResult S_19_P_14(shared_ptr ses, uint1 ses->log.warning("Received reconnect command with no destination present"); return HandlerResult::Type::SUPPRESS; - } else if (ses->version() != GameVersion::BB) { + } else if (!is_v4(ses->version())) { // Hide redirects from the client completely. The new destination server // will presumably send a new encryption init command, which the handlers // will appropriately respond to. @@ -886,16 +889,15 @@ static HandlerResult S_V3_1A_D5(shared_ptr ses, uint // If the client is a version that sends close confirmations and the client // has the no-close-confirmation flag set in its newserv client config, send a // fake confirmation to the remote server immediately. - if (((ses->version() == GameVersion::GC) || (ses->version() == GameVersion::XB)) && - ses->config.check_flag(Client::Flag::NO_D6)) { + if (is_v3(ses->version()) && ses->config.check_flag(Client::Flag::NO_D6)) { ses->server_channel.send(0xD6); } return HandlerResult::Type::FORWARD; } static HandlerResult S_V3_BB_DA(shared_ptr ses, uint16_t, uint32_t flag, string&) { - // This command is supported on all V3 versions except Ep1&2 Trial - if ((ses->version() == GameVersion::GC) && ses->config.check_flag(Client::Flag::IS_GC_TRIAL_EDITION)) { + // This command is supported on all V3 and V4 versions except Ep1&2 Trial + if (ses->version() == Version::GC_NTE) { return HandlerResult::Type::SUPPRESS; } else if ((ses->config.override_lobby_event != 0xFF) && (flag != ses->config.override_lobby_event)) { return HandlerResult(HandlerResult::Type::MODIFIED, 0xDA, ses->config.override_lobby_event); @@ -908,7 +910,7 @@ static HandlerResult S_6x(shared_ptr ses, uint16_t, auto s = ses->require_server_state(); if (ses->config.check_flag(Client::Flag::PROXY_SAVE_FILES)) { - if ((ses->version() == GameVersion::GC) && (data.size() >= 0x14)) { + if (is_ep3(ses->version()) && (data.size() >= 0x14)) { if (static_cast(data[0]) == 0xB6) { const auto& header = check_size_t(data, 0xFFFF); if (header.subsubcommand == 0x00000041) { @@ -932,7 +934,7 @@ static HandlerResult S_6x(shared_ptr ses, uint16_t, bool modified = false; if (!data.empty()) { // Unmask any masked Episode 3 commands from the server - if ((ses->version() == GameVersion::GC) && (data.size() > 8) && + if (is_ep3(ses->version()) && (data.size() > 8) && ((static_cast(data[0]) == 0xB3) || (static_cast(data[0]) == 0xB4) || (static_cast(data[0]) == 0xB5))) { @@ -989,7 +991,7 @@ static HandlerResult S_6x(shared_ptr ses, uint16_t, return HandlerResult::Type::SUPPRESS; } - } else if ((data[0] == 0x60) && ses->next_drop_item.data1d[0] && (ses->version() != GameVersion::BB)) { + } else if ((data[0] == 0x60) && ses->next_drop_item.data1d[0] && !is_v4(ses->version())) { const auto& cmd = check_size_t( data, sizeof(G_StandardDropItemRequest_PC_V3_BB_6x60)); ses->next_drop_item.id = ses->next_item_id++; @@ -1002,7 +1004,7 @@ static HandlerResult S_6x(shared_ptr ses, uint16_t, // the comparison is always false (which even happens in some environments // if we use -0x5E... apparently char is unsigned on some systems, or // std::string's char_type isn't char??) - } else if ((static_cast(data[0]) == 0xA2) && ses->next_drop_item.data1d[0] && (ses->version() != GameVersion::BB)) { + } else if ((static_cast(data[0]) == 0xA2) && ses->next_drop_item.data1d[0] && !is_v4(ses->version())) { const auto& cmd = check_size_t(data); ses->next_drop_item.id = ses->next_item_id++; send_drop_item(s, ses->server_channel, ses->next_drop_item, false, cmd.floor, cmd.x, cmd.z, cmd.entity_id); @@ -1010,9 +1012,7 @@ static HandlerResult S_6x(shared_ptr ses, uint16_t, ses->next_drop_item.clear(); return HandlerResult::Type::SUPPRESS; - } else if ((static_cast(data[0]) == 0xB5) && - (ses->version() == GameVersion::GC) && - (data.size() > 4)) { + } else if ((static_cast(data[0]) == 0xB5) && is_ep3(ses->version()) && (data.size() > 4)) { if (data[4] == 0x1A) { return HandlerResult::Type::SUPPRESS; } else if (data[4] == 0x36) { @@ -1032,7 +1032,7 @@ static HandlerResult C_GXB_61(shared_ptr ses, uint16 // TODO: We should check if the info board text was actually modified and // return MODIFIED if so. - if (ses->version() == GameVersion::BB) { + if (is_v4(ses->version())) { auto& pd = check_size_t(data, 0xFFFF); if (ses->config.check_flag(Client::Flag::PROXY_CHAT_FILTER_ENABLED)) { pd.info_board.encode(add_color(pd.info_board.decode(ses->language())), ses->language()); @@ -1069,6 +1069,9 @@ static HandlerResult C_GXB_61(shared_ptr ses, uint16 } pd = reinterpret_cast(&ep3_pd); } else { + if (is_ep3(ses->version())) { + ses->set_version(Version::GC_EP3_TRIAL_EDITION); + } pd = &check_size_t(data, 0xFFFF); } if (ses->config.check_flag(Client::Flag::PROXY_CHAT_FILTER_ENABLED)) { @@ -1132,7 +1135,7 @@ static HandlerResult S_44_A6(shared_ptr ses, uint16_ if (extension_offset != string::npos) { basename = filename.substr(0, extension_offset); extension = filename.substr(extension_offset); - if (extension == ".bin" && ses->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (extension == ".bin" && is_ep3(ses->version())) { extension += ".mnm"; } } else { @@ -1154,8 +1157,8 @@ static HandlerResult S_44_A6(shared_ptr ses, uint16_ } } - // Episode 3 download quests aren't DLQ-encoded - bool decode_dlq = is_download && !ses->config.check_flag(Client::Flag::IS_EPISODE_3); + // Episode 3 download quests aren't DLQ-encoded (but they are on Trial Edition) + bool decode_dlq = is_download && (ses->version() != Version::GC_EP3); ProxyServer::LinkedSession::SavingFile sf(filename, output_filename, cmd.file_size, decode_dlq); ses->saving_files.emplace(filename, std::move(sf)); if (ses->config.check_flag(Client::Flag::PROXY_SAVE_FILES)) { @@ -1218,7 +1221,7 @@ static HandlerResult S_13_A7(shared_ptr ses, uint16_ } static HandlerResult S_G_B7(shared_ptr ses, uint16_t, uint32_t, string& data) { - if (ses->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (is_ep3(ses->version())) { if (ses->config.check_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED)) { auto& cmd = check_size_t(data); if (cmd.current_meseta != 1000000) { @@ -1256,9 +1259,7 @@ static HandlerResult S_G_B8(shared_ptr ses, uint16_t // so the file sill be sent again if the client returns to newserv. ses->config.clear_flag(Client::Flag::HAS_EP3_CARD_DEFS); - return ses->config.check_flag(Client::Flag::IS_EPISODE_3) - ? HandlerResult::Type::FORWARD - : HandlerResult::Type::SUPPRESS; + return is_ep3(ses->version()) ? HandlerResult::Type::FORWARD : HandlerResult::Type::SUPPRESS; } static HandlerResult S_G_B9(shared_ptr ses, uint16_t, uint32_t, string& data) { @@ -1289,13 +1290,15 @@ static HandlerResult S_G_B9(shared_ptr ses, uint16_t } } - return ses->config.check_flag(Client::Flag::IS_EPISODE_3) + // This command exists only in final Episode 3 and not in Trial Edition + // (hence not using is_ep3() here) + return (ses->version() == Version::GC_EP3) ? HandlerResult::Type::FORWARD : HandlerResult::Type::SUPPRESS; } static HandlerResult S_G_EF(shared_ptr ses, uint16_t, uint32_t, string& data) { - if (ses->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (is_ep3(ses->version())) { if (ses->config.check_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED)) { auto& cmd = check_size_t(data, offsetof(S_StartCardAuction_GC_Ep3_EF, unused), 0xFFFF); @@ -1415,7 +1418,7 @@ static HandlerResult S_64(shared_ptr ses, uint16_t, if (ses->sub_version >= 0x40) { cmd = &check_size_t(data, sizeof(S_JoinGame_GC_Ep3_64)); cmd_ep3 = &check_size_t(data); - } else if (ses->version() == GameVersion::XB) { + } else if (ses->version() == Version::XB_V3) { // Schtserv doesn't send the unknown_a1 field in this command, and we don't // use it here, so we allow it to be omitted. cmd = &check_size_t(data.data(), data.size(), sizeof(CmdT) - 0x18, sizeof(CmdT)); @@ -1558,9 +1561,7 @@ static HandlerResult C_98(shared_ptr ses, uint16_t c ses->floor = 0x0F; ses->is_in_game = false; ses->is_in_quest = false; - if (ses->version() == GameVersion::GC || - ses->version() == GameVersion::XB || - ses->version() == GameVersion::BB) { + if (is_v3(ses->version()) || is_v4(ses->version())) { return C_GXB_61(ses, command, flag, data); } else { return HandlerResult::Type::FORWARD; @@ -1575,12 +1576,12 @@ static HandlerResult C_06(shared_ptr ses, uint16_t, strip_trailing_zeroes(text); uint8_t private_flags = 0; - if (ses->version() == GameVersion::PC || ses->version() == GameVersion::BB) { + if (uses_utf16(ses->version())) { if (text.size() & 1) { text.push_back(0); } text = tt_decode_marked(text, ses->language(), true); - } else if (!text.empty() && (text[0] != '\t') && ses->config.check_flag(Client::Flag::IS_EPISODE_3)) { + } else if (!text.empty() && (text[0] != '\t') && is_ep3(ses->version())) { private_flags = text[0]; text = tt_decode_marked(text.substr(1), ses->language(), false); } else { @@ -1670,7 +1671,7 @@ static HandlerResult C_6x(shared_ptr ses, uint16_t c if (ses->license && !data.empty()) { // On BB, the 6x06 command is blank - the server generates the actual Guild // Card contents and sends it to the target client. - if (data[0] == 0x06 && ses->version() != GameVersion::BB) { + if ((data[0] == 0x06) && !is_v4(ses->version())) { auto& cmd = check_size_t(data); if (cmd.guild_card.guild_card_number == ses->license->serial_number) { cmd.guild_card.guild_card_number = ses->remote_guild_card_number; @@ -1749,278 +1750,286 @@ static HandlerResult C_V123_A0_A1(shared_ptr ses, ui return HandlerResult::Type::SUPPRESS; } -// [command][version][is_client] -static on_command_t handlers[0x100][6][2] = { +// Indexed as [command][version][is_from_client] +static on_command_t handlers[0x100][13][2] = { // clang-format off -// CMD S-PATCH C-PATCH S-DC C-DC S-PC C-PC S-GC C-GC S-XB C-XB S-BB C-BB -/* 00 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 01 */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}}, -/* 02 */ {{S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {nullptr, nullptr}}, -/* 03 */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_B_03, nullptr}}, -/* 04 */ {{nullptr, nullptr}, {S_V123_04, nullptr}, {S_V123_04, nullptr}, {S_V123_04, nullptr}, {S_V123_04, nullptr}, {nullptr, nullptr}}, -/* 05 */ {{nullptr, C_05}, {nullptr, C_05}, {nullptr, C_05}, {nullptr, C_05}, {nullptr, C_05}, {nullptr, C_05}}, -/* 06 */ {{nullptr, nullptr}, {S_V123_06, C_06}, {S_V123_06, C_06}, {S_V123_06, C_06}, {S_V123_06, C_06}, {nullptr, C_06}}, -/* 07 */ {{nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* 08 */ {{nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* 09 */ {{nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 0A */ {{nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 0B */ {{nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 0C */ {{nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 0D */ {{nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 0E */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* 0F */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 10 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 11 */ {{nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* 12 */ {{nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* 13 */ {{nullptr, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}}, -/* 14 */ {{S_19_P_14, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* 15 */ {{nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 16 */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* 17 */ {{S_invalid, nullptr}, {S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {S_invalid, nullptr}}, -/* 18 */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}}, -/* 19 */ {{S_invalid, nullptr}, {S_19_P_14, nullptr}, {S_19_P_14, nullptr}, {S_19_P_14, nullptr}, {S_19_P_14, nullptr}, {S_19_P_14, nullptr}}, -/* 1A */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_V3_1A_D5, nullptr}, {S_V3_1A_D5, nullptr}, {nullptr, nullptr}}, -/* 1B */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}}, -/* 1C */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}}, -/* 1D */ {{S_invalid, nullptr}, {S_1D, C_1D}, {S_1D, C_1D}, {S_1D, C_1D}, {S_1D, C_1D}, {S_1D, C_1D}}, -/* 1E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 1F */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -// CMD S-PATCH C-PATCH S-DC C-DC S-PC C-PC S-GC C-GC S-XB C-XB S-BB C-BB -/* 20 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 21 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 22 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_B_22, nullptr}}, -/* 23 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, -/* 24 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, -/* 25 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, -/* 26 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 27 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 28 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 29 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 2A */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 2B */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 2C */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 2D */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 2E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 2F */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 30 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 31 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 32 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 33 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 34 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 35 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 36 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 37 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 38 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 39 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 3A */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 3B */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 3C */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 3D */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 3E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 3F */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -// CMD S-PATCH C-PATCH S-DC C-DC S-PC C-PC S-GC C-GC S-XB C-XB S-BB C-BB -/* 40 */ {{S_invalid, nullptr}, {S_invalid, C_40}, {S_invalid, C_40}, {S_invalid, C_40}, {S_invalid, C_40}, {S_invalid, C_40}}, -/* 41 */ {{S_invalid, nullptr}, {S_DGX_41, nullptr}, {S_P_41, nullptr}, {S_DGX_41, nullptr}, {S_DGX_41, nullptr}, {S_B_41, nullptr}}, -/* 42 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 43 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 44 */ {{S_invalid, nullptr}, {S_D_44_A6, nullptr}, {S_PG_44_A6, nullptr}, {S_PG_44_A6, nullptr}, {S_X_44_A6, nullptr}, {S_B_44_A6, nullptr}}, -/* 45 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 46 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 47 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 48 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 49 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 4A */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 4B */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 4C */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 4D */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 4E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 4F */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 50 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 51 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 52 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 53 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 54 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 55 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 56 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 57 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 58 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 59 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 5A */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 5B */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 5C */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 5D */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 5E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 5F */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -// CMD S-PATCH C-PATCH S-DC C-DC S-PC C-PC S-GC C-GC S-XB C-XB S-BB C-BB -/* 60 */ {{S_invalid, nullptr}, {S_6x, C_D_6x}, {S_6x, C_P_6x}, {S_6x, C_G_6x}, {S_6x, C_X_6x}, {S_6x, C_B_6x}}, -/* 61 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, C_GXB_61}, {S_invalid, C_GXB_61}, {S_invalid, C_GXB_61}}, -/* 62 */ {{S_invalid, nullptr}, {S_6x, C_D_6x}, {S_6x, C_P_6x}, {S_6x, C_G_6x}, {S_6x, C_X_6x}, {S_6x, C_B_6x}}, -/* 63 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 64 */ {{S_invalid, nullptr}, {S_D_64, nullptr}, {S_P_64, nullptr}, {S_G_64, nullptr}, {S_X_64, nullptr}, {S_B_64, nullptr}}, -/* 65 */ {{S_invalid, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_P_65_67_68, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_X_65_67_68, nullptr}, {S_B_65_67_68, nullptr}}, -/* 66 */ {{S_invalid, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}}, -/* 67 */ {{S_invalid, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_P_65_67_68, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_X_65_67_68, nullptr}, {S_B_65_67_68, nullptr}}, -/* 68 */ {{S_invalid, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_P_65_67_68, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_X_65_67_68, nullptr}, {S_B_65_67_68, nullptr}}, -/* 69 */ {{S_invalid, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}}, -/* 6A */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 6B */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 6C */ {{S_invalid, nullptr}, {S_6x, C_D_6x}, {S_6x, C_P_6x}, {S_6x, C_G_6x}, {S_6x, C_X_6x}, {S_6x, C_B_6x}}, -/* 6D */ {{S_invalid, nullptr}, {S_6x, C_D_6x}, {S_6x, C_P_6x}, {S_6x, C_G_6x}, {S_6x, C_X_6x}, {S_6x, C_B_6x}}, -/* 6E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 6F */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 70 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 71 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 72 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 73 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 74 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 75 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 76 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 77 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 78 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 79 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 7A */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 7B */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 7C */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 7D */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 7E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 7F */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -// CMD S-PATCH C-PATCH S-DC C-DC S-PC C-PC S-GC C-GC S-XB C-XB S-BB C-BB -/* 80 */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* 81 */ {{S_invalid, nullptr}, {S_DGX_81, C_DGX_81}, {S_P_81, C_P_81}, {S_DGX_81, C_DGX_81}, {S_DGX_81, C_DGX_81}, {S_B_81, C_B_81}}, -/* 82 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 83 */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* 84 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 85 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 86 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 87 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 88 */ {{S_invalid, nullptr}, {S_88, nullptr}, {S_88, nullptr}, {S_88, nullptr}, {S_88, nullptr}, {S_88, nullptr}}, -/* 89 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 8A */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* 8B */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 8C */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 8D */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 8E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 8F */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 90 */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* 91 */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* 92 */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* 93 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 94 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 95 */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* 96 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 97 */ {{S_invalid, nullptr}, {S_97, nullptr}, {S_97, nullptr}, {S_97, nullptr}, {S_97, nullptr}, {nullptr, nullptr}}, -/* 98 */ {{S_invalid, nullptr}, {S_invalid, C_98}, {S_invalid, C_98}, {S_invalid, C_98}, {S_invalid, C_98}, {S_invalid, C_98}}, -/* 99 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 9A */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_G_9A, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* 9B */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* 9C */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* 9D */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 9E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, C_G_9E}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 9F */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -// CMD S-PATCH C-PATCH S-DC C-DC S-PC C-PC S-GC C-GC S-XB C-XB S-BB C-BB -/* A0 */ {{S_invalid, nullptr}, {nullptr, C_V123_A0_A1}, {nullptr, C_V123_A0_A1}, {nullptr, C_V123_A0_A1}, {nullptr, C_V123_A0_A1}, {nullptr, nullptr}}, -/* A1 */ {{S_invalid, nullptr}, {nullptr, C_V123_A0_A1}, {nullptr, C_V123_A0_A1}, {nullptr, C_V123_A0_A1}, {nullptr, C_V123_A0_A1}, {nullptr, nullptr}}, -/* A2 */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* A3 */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* A4 */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* A5 */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* A6 */ {{S_invalid, nullptr}, {S_D_44_A6, nullptr}, {S_PG_44_A6, nullptr}, {S_PG_44_A6, nullptr}, {S_X_44_A6, nullptr}, {S_B_44_A6, nullptr}}, -/* A7 */ {{S_invalid, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}}, -/* A8 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* A9 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* AA */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* AB */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* AC */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_AC, nullptr}, {S_AC, nullptr}, {S_AC, nullptr}}, -/* AD */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* AE */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* AF */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* B0 */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* B1 */ {{S_invalid, nullptr}, {S_B1, nullptr}, {S_B1, nullptr}, {S_B1, nullptr}, {S_B1, nullptr}, {S_B1, nullptr}}, -/* B2 */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_B2, nullptr}, {S_B2, nullptr}, {S_B2, nullptr}}, -/* B3 */ {{S_invalid, C_B3}, {S_invalid, C_B3}, {S_invalid, C_B3}, {S_invalid, C_B3}, {S_invalid, C_B3}, {S_invalid, C_B3}}, -/* B4 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* B5 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* B6 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* B7 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_G_B7, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* B8 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_G_B8, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, -/* B9 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_G_B9, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* BA */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_G_BA, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* BB */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* BC */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* BD */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* BE */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* BF */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -// CMD S-PATCH C-PATCH S-DC C-DC S-PC C-PC S-GC C-GC S-XB C-XB S-BB C-BB -/* C0 */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* C1 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* C2 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* C3 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* C4 */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_V3_C4, nullptr}, {S_V3_C4, nullptr}, {nullptr, nullptr}}, -/* C5 */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* C6 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* C7 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* C8 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* C9 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_6x, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* CA */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* CB */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_6x, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* CC */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* CD */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* CE */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* CF */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* D0 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* D1 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* D2 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* D3 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* D4 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* D5 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_V3_1A_D5, nullptr}, {S_V3_1A_D5, nullptr}, {nullptr, nullptr}}, -/* D6 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* D7 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* D8 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, -/* D9 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, C_GX_D9}, {S_invalid, C_GX_D9}, {S_invalid, C_B_D9}}, -/* DA */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_V3_BB_DA, nullptr}, {S_V3_BB_DA, nullptr}, {S_V3_BB_DA, nullptr}}, -/* DB */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* DC */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, -/* DD */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, -/* DE */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, -/* DF */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -// CMD S-PATCH C-PATCH S-DC C-DC S-PC C-PC S-GC C-GC S-XB C-XB S-BB C-BB -/* E0 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, -/* E1 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, -/* E2 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, -/* E3 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, -/* E4 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_G_E4, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, -/* E5 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, -/* E6 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, -/* E7 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_B_E7, nullptr}}, -/* E8 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_E8, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, -/* E9 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_66_69_E9, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, -/* EA */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, -/* EB */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, -/* EC */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* ED */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, -/* EE */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, -/* EF */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_G_EF, nullptr}, {S_invalid, nullptr}, {S_B_EF, nullptr}}, -/* F0 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, -/* F1 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* F2 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* F3 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* F4 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* F5 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* F6 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* F7 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* F8 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* F9 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* FA */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* FB */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* FC */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* FD */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* FE */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* FF */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -// CMD S-PATCH C-PATCH S-DC C-DC S-PC C-PC S-GC C-GC S-XB C-XB S-BB C-BB +// CMD S_PC_PATCH C S_BB_PATCH C S_DC_NTE C S_DC_V1_12_2000_PROTO C S_DC_V1 C S_DC_V2 C S_PC_V2 C S_GC_NTE C S_GC_V3 C S_GC_EP3_TRIAL_EDITION C S_GC_EP3 C S_XB_V3 C S_BB_V4 C +/* 00 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 01 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}}, +/* 02 */ {{S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {nullptr, nullptr}, {S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {nullptr, nullptr}}, +/* 03 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_B_03, nullptr}}, +/* 04 */ {{nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_V123_04, nullptr}, {S_V123_04, nullptr}, {S_V123_04, nullptr}, {S_V123_04, nullptr}, {S_V123_04, nullptr}, {S_V123_04, nullptr}, {S_V123_04, nullptr}, {S_V123_04, nullptr}, {S_V123_04, nullptr}, {nullptr, nullptr}}, +/* 05 */ {{nullptr, C_05}, {nullptr, C_05}, {nullptr, nullptr}, {nullptr, C_05}, {nullptr, C_05}, {nullptr, C_05}, {nullptr, C_05}, {nullptr, C_05}, {nullptr, C_05}, {nullptr, C_05}, {nullptr, C_05}, {nullptr, C_05}, {nullptr, C_05}}, +/* 06 */ {{nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_V123_06, C_06}, {S_V123_06, C_06}, {S_V123_06, C_06}, {S_V123_06, C_06}, {S_V123_06, C_06}, {S_V123_06, C_06}, {S_V123_06, C_06}, {S_V123_06, C_06}, {S_V123_06, C_06}, {nullptr, C_06}}, +/* 07 */ {{nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* 08 */ {{nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* 09 */ {{nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 0A */ {{nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 0B */ {{nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 0C */ {{nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 0D */ {{nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 0E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* 0F */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +// CMD S_PC_PATCH C S_BB_PATCH C S_DC_NTE C S_DC_V1_12_2000_PROTO C S_DC_V1 C S_DC_V2 C S_PC_V2 C S_GC_NTE C S_GC_V3 C S_GC_EP3_TRIAL_EDITION C S_GC_EP3 C S_XB_V3 C S_BB_V4 C +/* 10 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 11 */ {{nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* 12 */ {{nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* 13 */ {{nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}}, +/* 14 */ {{S_19_P_14, nullptr}, {S_19_P_14, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* 15 */ {{nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 16 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* 17 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {S_V123P_02_17, nullptr}, {S_invalid, nullptr}}, +/* 18 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}}, +/* 19 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_19_P_14, nullptr}, {S_19_P_14, nullptr}, {S_19_P_14, nullptr}, {S_19_P_14, nullptr}, {S_19_P_14, nullptr}, {S_19_P_14, nullptr}, {S_19_P_14, nullptr}, {S_19_P_14, nullptr}, {S_19_P_14, nullptr}, {S_19_P_14, nullptr}}, +/* 1A */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_V3_1A_D5, nullptr}, {S_V3_1A_D5, nullptr}, {S_V3_1A_D5, nullptr}, {S_V3_1A_D5, nullptr}, {nullptr, nullptr}}, +/* 1B */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}}, +/* 1C */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}}, +/* 1D */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_1D, C_1D}, {S_1D, C_1D}, {S_1D, C_1D}, {S_1D, C_1D}, {S_1D, C_1D}, {S_1D, C_1D}, {S_1D, C_1D}, {S_1D, C_1D}, {S_1D, C_1D}, {S_1D, C_1D}}, +/* 1E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 1F */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +// CMD S_PC_PATCH C S_BB_PATCH C S_DC_NTE C S_DC_V1_12_2000_PROTO C S_DC_V1 C S_DC_V2 C S_PC_V2 C S_GC_NTE C S_GC_V3 C S_GC_EP3_TRIAL_EDITION C S_GC_EP3 C S_XB_V3 C S_BB_V4 C +/* 20 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 21 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 22 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_B_22, nullptr}}, +/* 23 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, +/* 24 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, +/* 25 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, +/* 26 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 27 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 28 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 29 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 2A */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 2B */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 2C */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 2D */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 2E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 2F */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +// CMD S_PC_PATCH C S_BB_PATCH C S_DC_NTE C S_DC_V1_12_2000_PROTO C S_DC_V1 C S_DC_V2 C S_PC_V2 C S_GC_NTE C S_GC_V3 C S_GC_EP3_TRIAL_EDITION C S_GC_EP3 C S_XB_V3 C S_BB_V4 C +/* 30 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 31 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 32 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 33 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 34 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 35 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 36 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 37 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 38 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 39 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 3A */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 3B */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 3C */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 3D */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 3E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 3F */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +// CMD S_PC_PATCH C S_BB_PATCH C S_DC_NTE C S_DC_V1_12_2000_PROTO C S_DC_V1 C S_DC_V2 C S_PC_V2 C S_GC_NTE C S_GC_V3 C S_GC_EP3_TRIAL_EDITION C S_GC_EP3 C S_XB_V3 C S_BB_V4 C +/* 40 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, C_40}, {S_invalid, C_40}, {S_invalid, C_40}, {S_invalid, C_40}, {S_invalid, C_40}, {S_invalid, C_40}, {S_invalid, C_40}, {S_invalid, C_40}, {S_invalid, C_40}, {S_invalid, C_40}}, +/* 41 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_DGX_41, nullptr}, {S_DGX_41, nullptr}, {S_DGX_41, nullptr}, {S_P_41, nullptr}, {S_DGX_41, nullptr}, {S_DGX_41, nullptr}, {S_DGX_41, nullptr}, {S_DGX_41, nullptr}, {S_DGX_41, nullptr}, {S_B_41, nullptr}}, +/* 42 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 43 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 44 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_D_44_A6, nullptr}, {S_D_44_A6, nullptr}, {S_D_44_A6, nullptr}, {S_PG_44_A6, nullptr}, {S_D_44_A6, nullptr}, {S_PG_44_A6, nullptr}, {S_PG_44_A6, nullptr}, {S_PG_44_A6, nullptr}, {S_X_44_A6, nullptr}, {S_B_44_A6, nullptr}}, +/* 45 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 46 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 47 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 48 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 49 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 4A */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 4B */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 4C */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 4D */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 4E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 4F */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +// CMD S_PC_PATCH C S_BB_PATCH C S_DC_NTE C S_DC_V1_12_2000_PROTO C S_DC_V1 C S_DC_V2 C S_PC_V2 C S_GC_NTE C S_GC_V3 C S_GC_EP3_TRIAL_EDITION C S_GC_EP3 C S_XB_V3 C S_BB_V4 C +/* 50 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 51 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 52 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 53 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 54 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 55 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 56 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 57 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 58 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 59 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 5A */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 5B */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 5C */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 5D */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 5E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 5F */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +// CMD S_PC_PATCH C S_BB_PATCH C S_DC_NTE C S_DC_V1_12_2000_PROTO C S_DC_V1 C S_DC_V2 C S_PC_V2 C S_GC_NTE C S_GC_V3 C S_GC_EP3_TRIAL_EDITION C S_GC_EP3 C S_XB_V3 C S_BB_V4 C +/* 60 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_6x, C_D_6x}, {S_6x, C_D_6x}, {S_6x, C_D_6x}, {S_6x, C_P_6x}, {S_6x, C_D_6x}, {S_6x, C_G_6x}, {S_6x, C_G_6x}, {S_6x, C_G_6x}, {S_6x, C_X_6x}, {S_6x, C_B_6x}}, +/* 61 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, C_GXB_61}, {S_invalid, C_GXB_61}, {S_invalid, C_GXB_61}, {S_invalid, C_GXB_61}, {S_invalid, C_GXB_61}}, +/* 62 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_6x, C_D_6x}, {S_6x, C_D_6x}, {S_6x, C_D_6x}, {S_6x, C_P_6x}, {S_6x, C_D_6x}, {S_6x, C_G_6x}, {S_6x, C_G_6x}, {S_6x, C_G_6x}, {S_6x, C_X_6x}, {S_6x, C_B_6x}}, +/* 63 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 64 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_D_64, nullptr}, {S_D_64, nullptr}, {S_D_64, nullptr}, {S_P_64, nullptr}, {S_D_64, nullptr}, {S_G_64, nullptr}, {S_G_64, nullptr}, {S_G_64, nullptr}, {S_X_64, nullptr}, {S_B_64, nullptr}}, +/* 65 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_P_65_67_68, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_X_65_67_68, nullptr}, {S_B_65_67_68, nullptr}}, +/* 66 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}}, +/* 67 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_P_65_67_68, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_X_65_67_68, nullptr}, {S_B_65_67_68, nullptr}}, +/* 68 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_P_65_67_68, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_X_65_67_68, nullptr}, {S_B_65_67_68, nullptr}}, +/* 69 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}}, +/* 6A */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 6B */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 6C */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_6x, C_D_6x}, {S_6x, C_D_6x}, {S_6x, C_D_6x}, {S_6x, C_P_6x}, {S_6x, C_D_6x}, {S_6x, C_G_6x}, {S_6x, C_G_6x}, {S_6x, C_G_6x}, {S_6x, C_X_6x}, {S_6x, C_B_6x}}, +/* 6D */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_6x, C_D_6x}, {S_6x, C_D_6x}, {S_6x, C_D_6x}, {S_6x, C_P_6x}, {S_6x, C_D_6x}, {S_6x, C_G_6x}, {S_6x, C_G_6x}, {S_6x, C_G_6x}, {S_6x, C_X_6x}, {S_6x, C_B_6x}}, +/* 6E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 6F */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +// CMD S_PC_PATCH C S_BB_PATCH C S_DC_NTE C S_DC_V1_12_2000_PROTO C S_DC_V1 C S_DC_V2 C S_PC_V2 C S_GC_NTE C S_GC_V3 C S_GC_EP3_TRIAL_EDITION C S_GC_EP3 C S_XB_V3 C S_BB_V4 C +/* 70 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 71 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 72 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 73 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 74 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 75 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 76 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 77 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 78 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 79 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 7A */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 7B */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 7C */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 7D */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 7E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 7F */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +// CMD S_PC_PATCH C S_BB_PATCH C S_DC_NTE C S_DC_V1_12_2000_PROTO C S_DC_V1 C S_DC_V2 C S_PC_V2 C S_GC_NTE C S_GC_V3 C S_GC_EP3_TRIAL_EDITION C S_GC_EP3 C S_XB_V3 C S_BB_V4 C +/* 80 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* 81 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_DGX_81, C_DGX_81}, {S_DGX_81, C_DGX_81}, {S_DGX_81, C_DGX_81}, {S_P_81, C_P_81}, {S_DGX_81, C_DGX_81}, {S_DGX_81, C_DGX_81}, {S_DGX_81, C_DGX_81}, {S_DGX_81, C_DGX_81}, {S_DGX_81, C_DGX_81}, {S_B_81, C_B_81}}, +/* 82 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 83 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* 84 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 85 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 86 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 87 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 88 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_88, nullptr}, {S_88, nullptr}, {S_88, nullptr}, {S_88, nullptr}, {S_88, nullptr}, {S_88, nullptr}, {S_88, nullptr}, {S_88, nullptr}, {S_88, nullptr}, {S_88, nullptr}}, +/* 89 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 8A */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* 8B */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 8C */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 8D */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 8E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 8F */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +// CMD S_PC_PATCH C S_BB_PATCH C S_DC_NTE C S_DC_V1_12_2000_PROTO C S_DC_V1 C S_DC_V2 C S_PC_V2 C S_GC_NTE C S_GC_V3 C S_GC_EP3_TRIAL_EDITION C S_GC_EP3 C S_XB_V3 C S_BB_V4 C +/* 90 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* 91 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* 92 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* 93 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 94 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 95 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* 96 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 97 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_97, nullptr}, {S_97, nullptr}, {S_97, nullptr}, {S_97, nullptr}, {S_97, nullptr}, {S_97, nullptr}, {S_97, nullptr}, {S_97, nullptr}, {S_97, nullptr}, {nullptr, nullptr}}, +/* 98 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, C_98}, {S_invalid, C_98}, {S_invalid, C_98}, {S_invalid, C_98}, {S_invalid, C_98}, {S_invalid, C_98}, {S_invalid, C_98}, {S_invalid, C_98}, {S_invalid, C_98}, {S_invalid, C_98}}, +/* 99 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 9A */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_G_9A, nullptr}, {S_G_9A, nullptr}, {S_G_9A, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* 9B */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* 9C */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* 9D */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 9E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, C_G_9E}, {S_invalid, C_G_9E}, {S_invalid, C_G_9E}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* 9F */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +// CMD S_PC_PATCH C S_BB_PATCH C S_DC_NTE C S_DC_V1_12_2000_PROTO C S_DC_V1 C S_DC_V2 C S_PC_V2 C S_GC_NTE C S_GC_V3 C S_GC_EP3_TRIAL_EDITION C S_GC_EP3 C S_XB_V3 C S_BB_V4 C +/* A0 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, C_V123_A0_A1},{nullptr, C_V123_A0_A1},{nullptr, C_V123_A0_A1}, {nullptr, C_V123_A0_A1}, {nullptr, C_V123_A0_A1},{nullptr, C_V123_A0_A1}, {nullptr, C_V123_A0_A1}, {nullptr, C_V123_A0_A1}, {nullptr, C_V123_A0_A1}, {nullptr, nullptr}}, +/* A1 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, C_V123_A0_A1},{nullptr, C_V123_A0_A1},{nullptr, C_V123_A0_A1}, {nullptr, C_V123_A0_A1}, {nullptr, C_V123_A0_A1},{nullptr, C_V123_A0_A1}, {nullptr, C_V123_A0_A1}, {nullptr, C_V123_A0_A1}, {nullptr, C_V123_A0_A1}, {nullptr, nullptr}}, +/* A2 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* A3 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* A4 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* A5 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* A6 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_D_44_A6, nullptr}, {S_D_44_A6, nullptr}, {S_D_44_A6, nullptr}, {S_PG_44_A6, nullptr}, {S_D_44_A6, nullptr}, {S_PG_44_A6, nullptr}, {S_PG_44_A6, nullptr}, {S_PG_44_A6, nullptr}, {S_X_44_A6, nullptr}, {S_B_44_A6, nullptr}}, +/* A7 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}, {S_13_A7, nullptr}}, +/* A8 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* A9 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* AA */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* AB */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* AC */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_AC, nullptr}, {S_AC, nullptr}, {S_AC, nullptr}, {S_AC, nullptr}, {S_AC, nullptr}}, +/* AD */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* AE */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* AF */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +// CMD S_PC_PATCH C S_BB_PATCH C S_DC_NTE C S_DC_V1_12_2000_PROTO C S_DC_V1 C S_DC_V2 C S_PC_V2 C S_GC_NTE C S_GC_V3 C S_GC_EP3_TRIAL_EDITION C S_GC_EP3 C S_XB_V3 C S_BB_V4 C +/* B0 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* B1 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_B1, nullptr}, {S_B1, nullptr}, {S_B1, nullptr}, {S_B1, nullptr}, {S_B1, nullptr}, {S_B1, nullptr}, {S_B1, nullptr}, {S_B1, nullptr}, {S_B1, nullptr}, {S_B1, nullptr}}, +/* B2 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_B2, nullptr}, {S_B2, nullptr}, {S_B2, nullptr}, {S_B2, nullptr}, {S_B2, nullptr}}, +/* B3 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, C_B3}, {S_invalid, C_B3}, {S_invalid, C_B3}, {S_invalid, C_B3}, {S_invalid, C_B3}, {S_invalid, C_B3}, {S_invalid, C_B3}, {S_invalid, C_B3}, {S_invalid, C_B3}, {S_invalid, C_B3}}, +/* B4 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* B5 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* B6 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* B7 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_G_B7, nullptr}, {S_G_B7, nullptr}, {S_G_B7, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* B8 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_G_B8, nullptr}, {S_G_B8, nullptr}, {S_G_B8, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, +/* B9 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_G_B9, nullptr}, {S_G_B9, nullptr}, {S_G_B9, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* BA */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_G_BA, nullptr}, {S_G_BA, nullptr}, {S_G_BA, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* BB */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* BC */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* BD */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* BE */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* BF */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +// CMD S_PC_PATCH C S_BB_PATCH C S_DC_NTE C S_DC_V1_12_2000_PROTO C S_DC_V1 C S_DC_V2 C S_PC_V2 C S_GC_NTE C S_GC_V3 C S_GC_EP3_TRIAL_EDITION C S_GC_EP3 C S_XB_V3 C S_BB_V4 C +/* C0 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* C1 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* C2 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* C3 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* C4 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_V3_C4, nullptr}, {S_V3_C4, nullptr}, {S_V3_C4, nullptr}, {S_V3_C4, nullptr}, {nullptr, nullptr}}, +/* C5 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* C6 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* C7 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* C8 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* C9 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_6x, nullptr}, {S_6x, nullptr}, {S_6x, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* CA */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* CB */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_6x, nullptr}, {S_6x, nullptr}, {S_6x, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* CC */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* CD */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* CE */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* CF */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +// CMD S_PC_PATCH C S_BB_PATCH C S_DC_NTE C S_DC_V1_12_2000_PROTO C S_DC_V1 C S_DC_V2 C S_PC_V2 C S_GC_NTE C S_GC_V3 C S_GC_EP3_TRIAL_EDITION C S_GC_EP3 C S_XB_V3 C S_BB_V4 C +/* D0 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* D1 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* D2 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* D3 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* D4 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* D5 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_V3_1A_D5, nullptr}, {S_V3_1A_D5, nullptr}, {S_V3_1A_D5, nullptr}, {S_V3_1A_D5, nullptr}, {nullptr, nullptr}}, +/* D6 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* D7 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* D8 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* D9 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, C_GX_D9}, {S_invalid, C_GX_D9}, {S_invalid, C_GX_D9}, {S_invalid, C_GX_D9}, {S_invalid, C_B_D9}}, +/* DA */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_V3_BB_DA, nullptr}, {S_V3_BB_DA, nullptr}, {S_V3_BB_DA, nullptr}, {S_V3_BB_DA, nullptr}, {S_V3_BB_DA, nullptr}}, +/* DB */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* DC */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, +/* DD */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, +/* DE */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, +/* DF */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +// CMD S_PC_PATCH C S_BB_PATCH C S_DC_NTE C S_DC_V1_12_2000_PROTO C S_DC_V1 C S_DC_V2 C S_PC_V2 C S_GC_NTE C S_GC_V3 C S_GC_EP3_TRIAL_EDITION C S_GC_EP3 C S_XB_V3 C S_BB_V4 C +/* E0 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, +/* E1 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, +/* E2 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, +/* E3 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, +/* E4 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_G_E4, nullptr}, {S_G_E4, nullptr}, {S_G_E4, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, +/* E5 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, +/* E6 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, +/* E7 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_B_E7, nullptr}}, +/* E8 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_E8, nullptr}, {S_E8, nullptr}, {S_E8, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, +/* E9 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, +/* EA */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, +/* EB */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, +/* EC */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* ED */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, +/* EE */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, +/* EF */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_G_EF, nullptr}, {S_G_EF, nullptr}, {S_G_EF, nullptr}, {S_invalid, nullptr}, {S_B_EF, nullptr}}, +// CMD S_PC_PATCH C S_BB_PATCH C S_DC_NTE C S_DC_V1_12_2000_PROTO C S_DC_V1 C S_DC_V2 C S_PC_V2 C S_GC_NTE C S_GC_V3 C S_GC_EP3_TRIAL_EDITION C S_GC_EP3 C S_XB_V3 C S_BB_V4 C +/* F0 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}}, +/* F1 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* F2 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* F3 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* F4 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* F5 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* F6 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* F7 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* F8 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* F9 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* FA */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* FB */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* FC */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* FD */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* FE */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +/* FF */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, +// CMD S_PC_PATCH C S_BB_PATCH C S_DC_NTE C S_DC_V1_12_2000_PROTO C S_DC_V1 C S_DC_V2 C S_PC_V2 C S_GC_NTE C S_GC_V3 C S_GC_EP3_TRIAL_EDITION C S_GC_EP3 C S_XB_V3 C S_BB_V4 C // clang-format on }; -static on_command_t get_handler(GameVersion version, bool from_server, uint8_t command) { +static on_command_t get_handler(Version version, bool from_server, uint8_t command) { size_t version_index = static_cast(version); if (version_index >= sizeof(handlers[0]) / sizeof(handlers[0][0])) { throw logic_error("invalid game version on proxy server"); diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index 8115f707..e96fe572 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -43,8 +43,7 @@ ProxyServer::ProxyServer( state(state), next_unlicensed_session_id(0xFF00000000000001) {} -void ProxyServer::listen(uint16_t port, GameVersion version, - const struct sockaddr_storage* default_destination) { +void ProxyServer::listen(uint16_t port, Version version, const struct sockaddr_storage* default_destination) { shared_ptr socket_obj(new ListeningSocket( this, port, version, default_destination)); auto l = this->listeners.emplace(port, socket_obj).first->second; @@ -53,7 +52,7 @@ void ProxyServer::listen(uint16_t port, GameVersion version, ProxyServer::ListeningSocket::ListeningSocket( ProxyServer* server, uint16_t port, - GameVersion version, + Version version, const struct sockaddr_storage* default_destination) : server(server), log(string_printf("[ProxyServer:ListeningSocket:%hu] ", port), proxy_server_log.min_level), @@ -83,8 +82,7 @@ ProxyServer::ListeningSocket::ListeningSocket( this->default_destination.ss_family = 0; } - this->log.info("Listening on TCP port %hu (%s) on fd %d", - this->port, name_for_version(this->version), static_cast(this->fd)); + this->log.info("Listening on TCP port %hu (%s) on fd %d", this->port, name_for_enum(this->version), static_cast(this->fd)); } void ProxyServer::ListeningSocket::dispatch_on_listen_accept( @@ -98,10 +96,8 @@ void ProxyServer::ListeningSocket::dispatch_on_listen_error( } void ProxyServer::ListeningSocket::on_listen_accept(int fd) { - this->log.info("Client connected on fd %d (port %hu, version %s)", - fd, this->port, name_for_version(this->version)); - auto* bev = bufferevent_socket_new(this->server->base.get(), fd, - BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); + this->log.info("Client connected on fd %d (port %hu, version %s)", fd, this->port, name_for_enum(this->version)); + auto* bev = bufferevent_socket_new(this->server->base.get(), fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); this->server->on_client_connect(bev, this->port, this->version, (this->default_destination.ss_family == AF_INET) ? &this->default_destination : nullptr); } @@ -117,7 +113,7 @@ void ProxyServer::ListeningSocket::on_listen_error() { void ProxyServer::connect_client(struct bufferevent* bev, uint16_t server_port) { // Look up the listening socket for the given port, and use that game version. // We don't support default-destination proxying for virtual connections (yet) - GameVersion version; + Version version; try { version = this->listeners.at(server_port)->version; } catch (const out_of_range&) { @@ -136,12 +132,12 @@ void ProxyServer::connect_client(struct bufferevent* bev, uint16_t server_port) void ProxyServer::on_client_connect( struct bufferevent* bev, uint16_t listen_port, - GameVersion version, + Version version, const struct sockaddr_storage* default_destination) { // If a default destination exists for this client and the client is a patch // client, create a linked session immediately and connect to the remote // server. This creates a direct session. - if (default_destination && (version == GameVersion::PATCH)) { + if (default_destination && is_patch(version)) { uint64_t session_id = this->next_unlicensed_session_id++; if (this->next_unlicensed_session_id == 0) { this->next_unlicensed_session_id = 0xFF00000000000001; @@ -175,18 +171,24 @@ void ProxyServer::on_client_connect( } switch (version) { - case GameVersion::PATCH: + case Version::PC_PATCH: + case Version::BB_PATCH: throw logic_error("cannot create unlinked patch session"); - case GameVersion::DC: - case GameVersion::PC: - case GameVersion::GC: - case GameVersion::XB: { + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::PC_V2: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + case Version::XB_V3: { uint32_t server_key = random_object(); uint32_t client_key = random_object(); - auto cmd = prepare_server_init_contents_console( - server_key, client_key, 0); + auto cmd = prepare_server_init_contents_console(server_key, client_key, 0); ses->channel.send(0x02, 0x00, &cmd, sizeof(cmd)); - if ((version == GameVersion::DC) || (version == GameVersion::PC)) { + if (uses_v2_encryption(version)) { ses->channel.crypt_out.reset(new PSOV2Encryption(server_key)); ses->channel.crypt_in.reset(new PSOV2Encryption(client_key)); } else { @@ -195,7 +197,7 @@ void ProxyServer::on_client_connect( } break; } - case GameVersion::BB: { + case Version::BB_V4: { parray server_key; parray client_key; random_data(server_key.data(), server_key.bytes()); @@ -225,7 +227,7 @@ ProxyServer::UnlinkedSession::UnlinkedSession( shared_ptr server, struct bufferevent* bev, uint16_t local_port, - GameVersion version) + Version version) : server(server), log(string_printf("[ProxyServer:UnlinkedSession:%p] ", bev), proxy_server_log.min_level), channel( @@ -263,108 +265,139 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 bool should_close_unlinked_session = false; try { - if (ses->version == GameVersion::DC) { - // We should only get a 93 or 9D while the session is unlinked; if we get - // anything else, disconnect - if (command == 0x93) { - const auto& cmd = check_size_t(data); - ses->license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode()); - ses->sub_version = cmd.sub_version; - ses->channel.language = cmd.language; - ses->character_name = cmd.name.decode(ses->channel.language); - ses->hardware_id = cmd.hardware_id.decode(); - ses->config.set_flag(Client::Flag::IS_DC_V1); - } else if (command == 0x9D) { - const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_DC_GC_9D)); - ses->license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode()); - ses->sub_version = cmd.sub_version; - ses->channel.language = cmd.language; - ses->character_name = cmd.name.decode(ses->channel.language); - } else { - throw runtime_error("command is not 93 or 9D"); - } - - } else if (ses->version == GameVersion::PC) { - // We should only get a 9D while the session is unlinked; if we get - // anything else, disconnect - if (command != 0x9D) { - throw runtime_error("command is not 9D"); - } - const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_PC_9D)); - ses->license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode()); - ses->sub_version = cmd.sub_version; - ses->channel.language = cmd.language; - ses->character_name = cmd.name.decode(ses->channel.language); - - } else if (ses->version == GameVersion::GC) { - // We should only get a 9E while the session is unlinked; if we get - // anything else, disconnect - // TODO: GCTE will send 9D; we should presumably handle that too, sigh - if (command != 0x9E) { - throw runtime_error("command is not 9E"); - } - const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_GC_9E)); - ses->license = s->license_index->verify_gc(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode()); - ses->sub_version = cmd.sub_version; - ses->channel.language = cmd.language; - ses->character_name = cmd.name.decode(ses->channel.language); - ses->config.parse_from(cmd.client_config); - - } else if (ses->version == GameVersion::XB) { - // We should only get a 9E or 9F while the session is unlinked; if we get - // anything else, disconnect - if (command == 0x9E) { - const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_XB_9E)); - string xb_gamertag = cmd.serial_number.decode(); - uint64_t xb_user_id = stoull(cmd.access_key.decode(), nullptr, 16); - uint64_t xb_account_id = cmd.netloc.account_id; - ses->license = s->license_index->verify_xb(xb_gamertag, xb_user_id, xb_account_id); - ses->sub_version = cmd.sub_version; - ses->channel.language = cmd.language; - ses->character_name = cmd.name.decode(ses->channel.language); - ses->xb_netloc = cmd.netloc; - ses->xb_9E_unknown_a1a = cmd.unknown_a1a; - ses->channel.send(0x9F, 0x00); - return; - } else if (command == 0x9F) { - const auto& cmd = check_size_t(data); - ses->config.parse_from(cmd.data); - } else { - throw runtime_error("command is not 9E or 9F"); - } - - } else if (ses->version == GameVersion::XB) { - throw runtime_error("xbox licenses are not implemented"); - - } else if (ses->version == GameVersion::BB) { - // We should only get a 93 while the session is unlinked; if we get - // anything else, disconnect - if (command != 0x93) { - throw runtime_error("command is not 93"); - } - const auto& cmd = check_size_t(data); - try { - ses->license = s->license_index->verify_bb(cmd.username.decode(), cmd.password.decode()); - } catch (const LicenseIndex::missing_license&) { - if (!s->allow_unregistered_users) { - throw; + switch (ses->version) { + case Version::DC_NTE: { + // We should only get an 8B while the session is unlinked + if (command != 0x8B) { + throw runtime_error("command is not 8B"); } - shared_ptr l(new License()); - l->serial_number = fnv1a32(cmd.username.decode()) & 0x7FFFFFFF; - l->bb_username = cmd.username.decode(); - l->bb_password = cmd.password.decode(); - s->license_index->add(l); - l->save(); - ses->license = l; - string l_str = l->str(); - ses->log.info("Created license %s", l_str.c_str()); + const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_DCNTE_8B)); + ses->license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode()); + ses->sub_version = cmd.sub_version; + ses->channel.language = cmd.language; + ses->character_name = cmd.name.decode(ses->channel.language); + // TODO: Parse cmd.hardware_id + ses->version = Version::DC_NTE; + break; } - ses->login_command_bb = std::move(data); - } else { - throw logic_error("unsupported unlinked session version"); + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + // We should only get a 93 or 9D while the session is unlinked + if (command == 0x93) { + const auto& cmd = check_size_t(data); + ses->license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode()); + ses->sub_version = cmd.sub_version; + ses->channel.language = cmd.language; + ses->character_name = cmd.name.decode(ses->channel.language); + ses->hardware_id = cmd.hardware_id.decode(); + ses->version = Version::DC_V1; + } else if (command == 0x9D) { + const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_DC_GC_9D)); + ses->license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode()); + ses->sub_version = cmd.sub_version; + ses->channel.language = cmd.language; + ses->character_name = cmd.name.decode(ses->channel.language); + } else { + throw runtime_error("command is not 93 or 9D"); + } + break; + + case Version::PC_V2: { + // We should only get a 9D while the session is unlinked + if (command != 0x9D) { + throw runtime_error("command is not 9D"); + } + const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_PC_9D)); + ses->license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode()); + ses->sub_version = cmd.sub_version; + ses->channel.language = cmd.language; + ses->character_name = cmd.name.decode(ses->channel.language); + break; + } + + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + // We should only get a 9D or 9E while the session is unlinked + if (command == 0x9D) { + const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_DC_GC_9D)); + ses->license = s->license_index->verify_gc(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode()); + ses->sub_version = cmd.sub_version; + ses->channel.language = cmd.language; + ses->character_name = cmd.name.decode(ses->channel.language); + ses->version = Version::GC_NTE; + ses->config.set_flags_for_version(ses->version, cmd.sub_version); + } else if (command == 0x9E) { + const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_GC_9E)); + ses->license = s->license_index->verify_gc(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode()); + ses->sub_version = cmd.sub_version; + ses->channel.language = cmd.language; + ses->character_name = cmd.name.decode(ses->channel.language); + ses->config.parse_from(cmd.client_config); + if (cmd.sub_version >= 0x40) { + ses->version = Version::GC_EP3; + } + } else { + throw runtime_error("command is not 9D or 9E"); + } + break; + + case Version::XB_V3: + // We should only get a 9E or 9F while the session is unlinked + if (command == 0x9E) { + const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_XB_9E)); + string xb_gamertag = cmd.serial_number.decode(); + uint64_t xb_user_id = stoull(cmd.access_key.decode(), nullptr, 16); + uint64_t xb_account_id = cmd.netloc.account_id; + ses->license = s->license_index->verify_xb(xb_gamertag, xb_user_id, xb_account_id); + ses->sub_version = cmd.sub_version; + ses->channel.language = cmd.language; + ses->character_name = cmd.name.decode(ses->channel.language); + ses->xb_netloc = cmd.netloc; + ses->xb_9E_unknown_a1a = cmd.unknown_a1a; + ses->channel.send(0x9F, 0x00); + return; + } else if (command == 0x9F) { + const auto& cmd = check_size_t(data); + ses->config.parse_from(cmd.data); + } else { + throw runtime_error("command is not 9E or 9F"); + } + break; + + case Version::BB_V4: { + // We should only get a 93 while the session is unlinked; if we get + // anything else, disconnect + if (command != 0x93) { + throw runtime_error("command is not 93"); + } + const auto& cmd = check_size_t(data); + try { + ses->license = s->license_index->verify_bb(cmd.username.decode(), cmd.password.decode()); + } catch (const LicenseIndex::missing_license&) { + if (!s->allow_unregistered_users) { + throw; + } + shared_ptr l(new License()); + l->serial_number = fnv1a32(cmd.username.decode()) & 0x7FFFFFFF; + l->bb_username = cmd.username.decode(); + l->bb_password = cmd.password.decode(); + s->license_index->add(l); + l->save(); + ses->license = l; + string l_str = l->str(); + ses->log.info("Created license %s", l_str.c_str()); + } + ses->login_command_bb = std::move(data); + break; + } + + default: + throw runtime_error("unvalid unlinked session version"); } - } catch (const exception& e) { ses->log.error("Failed to process command from unlinked client: %s", e.what()); should_close_unlinked_session = true; @@ -410,7 +443,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 } else { // Resume the linked session using the unlinked session try { - if (ses->version == GameVersion::BB) { + if (ses->version == Version::BB_V4) { linked_ses->resume( std::move(ses->channel), ses->detector_crypt, @@ -455,7 +488,7 @@ ProxyServer::LinkedSession::LinkedSession( shared_ptr server, uint64_t id, uint16_t local_port, - GameVersion version) + Version version) : server(server), id(id), log(string_printf("[ProxyServer:LinkedSession:%08" PRIX64 "] ", this->id), proxy_server_log.min_level), @@ -501,7 +534,7 @@ ProxyServer::LinkedSession::LinkedSession( ProxyServer::LinkedSession::LinkedSession( shared_ptr server, uint16_t local_port, - GameVersion version, + Version version, shared_ptr license, const Client::Config& config) : LinkedSession(server, license->serial_number, local_port, version) { @@ -517,7 +550,7 @@ ProxyServer::LinkedSession::LinkedSession( ProxyServer::LinkedSession::LinkedSession( shared_ptr server, uint16_t local_port, - GameVersion version, + Version version, std::shared_ptr license, const struct sockaddr_storage& next_destination) : LinkedSession(server, license->serial_number, local_port, version) { @@ -529,7 +562,7 @@ ProxyServer::LinkedSession::LinkedSession( shared_ptr server, uint64_t id, uint16_t local_port, - GameVersion version, + Version version, const struct sockaddr_storage& destination) : LinkedSession(server, id, local_port, version) { this->next_destination = destination; @@ -547,6 +580,11 @@ std::shared_ptr ProxyServer::LinkedSession::require_server_state() return this->require_server()->state; } +void ProxyServer::LinkedSession::set_version(Version v) { + this->client_channel.version = v; + this->server_channel.version = v; +} + void ProxyServer::LinkedSession::resume( Channel&& client_channel, shared_ptr detector_crypt, @@ -594,6 +632,7 @@ void ProxyServer::LinkedSession::resume_inner( this, string_printf("LinkedSession:%08" PRIX64 ":client", this->id)); this->server_channel.language = this->client_channel.language; + this->server_channel.version = this->client_channel.version; this->detector_crypt = detector_crypt; this->server_channel.disconnect(); @@ -663,10 +702,7 @@ void ProxyServer::LinkedSession::on_error(Channel& ch, short events) { if (events & BEV_EVENT_CONNECTED) { ses->log.info("%s channel connected", is_server_stream ? "Server" : "Client"); - if (is_server_stream && (ses->config.override_lobby_event != 0xFF) && - (((ses->version() == GameVersion::GC) && !(ses->config.check_flag(Client::Flag::IS_GC_TRIAL_EDITION))) || - (ses->version() == GameVersion::XB) || - (ses->version() == GameVersion::BB))) { + if (is_server_stream && (ses->config.override_lobby_event != 0xFF) && (is_v3(ses->version()) || is_v4(ses->version()))) { ses->client_channel.send(0xDA, ses->config.override_lobby_event); } } @@ -703,7 +739,7 @@ void ProxyServer::LinkedSession::send_to_game_server(const char* error_message) } // On BB, do nothing - we can't return to the game server since the remote // server likely sent different game data than what newserv would have sent - if (this->version() == GameVersion::BB) { + if (this->version() == Version::BB_V4) { this->disconnect(); return; } @@ -728,7 +764,7 @@ void ProxyServer::LinkedSession::send_to_game_server(const char* error_message) send_ship_info(this->client_channel, string_printf("You\'ve returned to\n\tC6%s$C7\n\n%s", s->name.c_str(), error_message ? error_message : "")); // Restore newserv_client_config, so the login server gets the client flags - if (this->version() == GameVersion::GC || this->version() == GameVersion::XB) { + if (is_v3(this->version())) { S_UpdateClientConfig_V3_04 update_client_config_cmd; update_client_config_cmd.player_tag = 0x00010000; update_client_config_cmd.guild_card_number = this->license->serial_number; @@ -736,8 +772,7 @@ void ProxyServer::LinkedSession::send_to_game_server(const char* error_message) this->client_channel.send(0x04, 0x00, &update_client_config_cmd, sizeof(update_client_config_cmd)); } - const auto& port_name = version_to_login_port_name.at(static_cast(this->version())); - + string port_name = login_port_name_for_version(this->version()); S_Reconnect_19 reconnect_cmd = {{0, s->name_to_port_config.at(port_name)->port, 0}}; // If the client is on a virtual connection, we can use any address @@ -839,7 +874,7 @@ shared_ptr ProxyServer::get_session_by_name( shared_ptr ProxyServer::create_licensed_session( shared_ptr l, uint16_t local_port, - GameVersion version, + Version version, const Client::Config& config) { shared_ptr session(new LinkedSession(this->shared_from_this(), local_port, version, l, config)); auto emplace_ret = this->id_to_session.emplace(session->id, session); diff --git a/src/ProxyServer.hh b/src/ProxyServer.hh index a428fd32..0039b687 100644 --- a/src/ProxyServer.hh +++ b/src/ProxyServer.hh @@ -26,8 +26,7 @@ public: std::shared_ptr state); virtual ~ProxyServer() = default; - void listen(uint16_t port, GameVersion version, - const struct sockaddr_storage* default_destination = nullptr); + void listen(uint16_t port, Version version, const struct sockaddr_storage* default_destination = nullptr); void connect_client(struct bufferevent* bev, uint16_t server_port); @@ -116,35 +115,36 @@ public: std::shared_ptr server, uint64_t id, uint16_t local_port, - GameVersion version); + Version version); LinkedSession( std::shared_ptr server, uint16_t local_port, - GameVersion version, + Version version, std::shared_ptr license, const Client::Config& config); LinkedSession( std::shared_ptr server, uint16_t local_port, - GameVersion version, + Version version, std::shared_ptr license, const struct sockaddr_storage& next_destination); LinkedSession( std::shared_ptr server, uint64_t id, uint16_t local_port, - GameVersion version, + Version version, const struct sockaddr_storage& next_destination); std::shared_ptr require_server() const; std::shared_ptr require_server_state() const; - inline GameVersion version() const { + inline Version version() const { return this->client_channel.version; } inline uint8_t language() const { return this->client_channel.language; } + void set_version(Version v); void resume( Channel&& client_channel, @@ -182,7 +182,7 @@ public: std::shared_ptr create_licensed_session( std::shared_ptr l, uint16_t local_port, - GameVersion version, + Version version, const Client::Config& config); void delete_session(uint64_t id); void delete_session(struct bufferevent* bev); @@ -197,13 +197,13 @@ private: uint16_t port; scoped_fd fd; std::unique_ptr listener; - GameVersion version; + Version version; struct sockaddr_storage default_destination; ListeningSocket( ProxyServer* server, uint16_t port, - GameVersion version, + Version version, const struct sockaddr_storage* default_destination); static void dispatch_on_listen_accept(struct evconnlistener* listener, @@ -219,7 +219,7 @@ private: PrefixedLogger log; Channel channel; uint16_t local_port; - GameVersion version; + Version version; struct sockaddr_storage next_destination; std::shared_ptr detector_crypt; @@ -237,7 +237,7 @@ private: XBNetworkLocation xb_netloc; parray xb_9E_unknown_a1a; - UnlinkedSession(std::shared_ptr server, struct bufferevent* bev, uint16_t port, GameVersion version); + UnlinkedSession(std::shared_ptr server, struct bufferevent* bev, uint16_t port, Version version); std::shared_ptr require_server() const; std::shared_ptr require_server_state() const; @@ -263,6 +263,6 @@ private: void on_client_connect( struct bufferevent* bev, uint16_t listen_port, - GameVersion version, + Version version, const struct sockaddr_storage* default_destination); }; diff --git a/src/Quest.cc b/src/Quest.cc index dd68a59c..9d9a4c15 100644 --- a/src/Quest.cc +++ b/src/Quest.cc @@ -207,7 +207,7 @@ struct PSODownloadQuestHeader { VersionedQuest::VersionedQuest( uint32_t quest_number, uint32_t category_id, - QuestScriptVersion version, + Version version, uint8_t language, std::shared_ptr bin_contents, std::shared_ptr dat_contents, @@ -230,9 +230,23 @@ VersionedQuest::VersionedQuest( auto bin_decompressed = prs_decompress(*this->bin_contents); switch (this->version) { - case QuestScriptVersion::DC_NTE: - case QuestScriptVersion::DC_V1: - case QuestScriptVersion::DC_V2: { + case Version::DC_NTE: { + if (bin_decompressed.size() < sizeof(PSOQuestHeaderDCNTE)) { + throw invalid_argument("file is too small for header"); + } + auto* header = reinterpret_cast(bin_decompressed.data()); + this->joinable = false; + this->episode = Episode::EP1; + if (this->quest_number == 0xFFFFFFFF) { + this->quest_number = fnv1a32(header, sizeof(header)) & 0xFFFF; + } + this->name = header->name.decode(this->language); + break; + } + + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: { if (bin_decompressed.size() < sizeof(PSOQuestHeaderDC)) { throw invalid_argument("file is too small for header"); } @@ -248,7 +262,7 @@ VersionedQuest::VersionedQuest( break; } - case QuestScriptVersion::PC_V2: { + case Version::PC_V2: { if (bin_decompressed.size() < sizeof(PSOQuestHeaderPC)) { throw invalid_argument("file is too small for header"); } @@ -264,14 +278,14 @@ VersionedQuest::VersionedQuest( break; } - case QuestScriptVersion::GC_EP3: { + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: { // Note: This codepath handles Episode 3 download quests, which are not // the same as Episode 3 quest scripts. The latter are only used offline // in story mode, but can be disassembled with disassemble_quest_script. - // It's unfortunate that the QuestScriptVersion::GC_EP3 value is used - // here for Episode 3 download quests (maps) and there for offline story - // mode scripts, but it's probably not worth refactoring this logic, at - // least right now. + // It's unfortunate that Version::GC_EP3 is used here for Episode 3 + // download quests (maps) and there for offline story mode scripts, but + // it's probably not worth refactoring this logic, at least right now. if (bin_decompressed.size() != sizeof(Episode3::MapDefinition)) { throw invalid_argument("file is incorrect size"); } @@ -287,9 +301,9 @@ VersionedQuest::VersionedQuest( break; } - case QuestScriptVersion::XB_V3: - case QuestScriptVersion::GC_NTE: - case QuestScriptVersion::GC_V3: { + case Version::XB_V3: + case Version::GC_NTE: + case Version::GC_V3: { if (bin_decompressed.size() < sizeof(PSOQuestHeaderGC)) { throw invalid_argument("file is too small for header"); } @@ -305,7 +319,7 @@ VersionedQuest::VersionedQuest( break; } - case QuestScriptVersion::BB_V4: { + case Version::BB_V4: { if (bin_decompressed.size() < sizeof(PSOQuestHeaderBB)) { throw invalid_argument("file is too small for header"); } @@ -372,8 +386,8 @@ Quest::Quest(shared_ptr initial_version) this->versions.emplace(this->versions_key(initial_version->version, initial_version->language), initial_version); } -uint16_t Quest::versions_key(QuestScriptVersion v, uint8_t language) { - return (static_cast(v) << 8) | language; +uint32_t Quest::versions_key(Version v, uint8_t language) { + return (static_cast(v) << 8) | language; } void Quest::add_version(shared_ptr vq) { @@ -402,17 +416,17 @@ void Quest::add_version(shared_ptr vq) { this->versions.emplace(this->versions_key(vq->version, vq->language), vq); } -bool Quest::has_version(QuestScriptVersion v, uint8_t language) const { +bool Quest::has_version(Version v, uint8_t language) const { return this->versions.count(this->versions_key(v, language)); } -bool Quest::has_version_any_language(QuestScriptVersion v) const { - uint16_t k = this->versions_key(v, 0); +bool Quest::has_version_any_language(Version v) const { + uint32_t k = this->versions_key(v, 0); auto it = this->versions.lower_bound(k); return ((it != this->versions.end()) && ((it->first & 0xFF00) == k)); } -shared_ptr Quest::version(QuestScriptVersion v, uint8_t language) const { +shared_ptr Quest::version(Version v, uint8_t language) const { // Return the requested version, if it exists try { return this->versions.at(this->versions_key(v, language)); @@ -563,16 +577,18 @@ QuestIndex::QuestIndex( uint32_t quest_number = strtoull(quest_number_token.c_str() + 1, nullptr, 10); // Get the version from the second token - static const unordered_map name_to_version({ - {"dn", QuestScriptVersion::DC_NTE}, - {"d1", QuestScriptVersion::DC_V1}, - {"dc", QuestScriptVersion::DC_V2}, - {"pc", QuestScriptVersion::PC_V2}, - {"gcn", QuestScriptVersion::GC_NTE}, - {"gc", QuestScriptVersion::GC_V3}, - {"gc3", QuestScriptVersion::GC_EP3}, - {"xb", QuestScriptVersion::XB_V3}, - {"bb", QuestScriptVersion::BB_V4}, + static const unordered_map name_to_version({ + {"dn", Version::DC_NTE}, + {"dp", Version::DC_V1_12_2000_PROTOTYPE}, + {"d1", Version::DC_V1}, + {"dc", Version::DC_V2}, + {"pc", Version::PC_V2}, + {"gcn", Version::GC_NTE}, + {"gc", Version::GC_V3}, + {"gc3t", Version::GC_EP3_TRIAL_EDITION}, + {"gc3", Version::GC_EP3}, + {"xb", Version::XB_V3}, + {"bb", Version::BB_V4}, }); auto version = name_to_version.at(version_token); @@ -587,7 +603,7 @@ QuestIndex::QuestIndex( string pvr_filename; shared_ptr dat_contents; shared_ptr pvr_contents; - if (version != QuestScriptVersion::GC_EP3) { + if (!::is_ep3(version)) { // Look for dat and pvr files with the same basename as the bin file; if // not found, look for them without the language suffix try { @@ -703,7 +719,7 @@ shared_ptr QuestIndex::get(uint32_t quest_number) const { } } -vector> QuestIndex::filter(Episode episode, uint32_t category_id, QuestScriptVersion version) const { +vector> QuestIndex::filter(Episode episode, uint32_t category_id, Version version) const { vector> ret; for (auto it : this->quests_by_number) { if (((episode == Episode::NONE) || (it.second->episode == episode)) && @@ -754,7 +770,7 @@ shared_ptr VersionedQuest::create_download_quest(uint8_t overrid // This function should not be used for Episode 3 quests (they should be sent // to the client as-is, without any encryption or other preprocessing) - if (this->episode == Episode::EP3 || this->version == QuestScriptVersion::GC_EP3) { + if (this->episode == Episode::EP3 || is_ep3(this->version)) { throw logic_error("Episode 3 quests cannot be converted to download quests"); } @@ -762,9 +778,16 @@ shared_ptr VersionedQuest::create_download_quest(uint8_t overrid void* data_ptr = decompressed_bin.data(); switch (this->version) { - case QuestScriptVersion::DC_NTE: - case QuestScriptVersion::DC_V1: - case QuestScriptVersion::DC_V2: + case Version::DC_NTE: + if (decompressed_bin.size() < sizeof(PSOQuestHeaderDCNTE)) { + throw runtime_error("bin file is too small for header"); + } + // There's no known language field in this version, so we don't write + // anything here + break; + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: if (decompressed_bin.size() < sizeof(PSOQuestHeaderDC)) { throw runtime_error("bin file is too small for header"); } @@ -772,7 +795,7 @@ shared_ptr VersionedQuest::create_download_quest(uint8_t overrid reinterpret_cast(data_ptr)->language = override_language; } break; - case QuestScriptVersion::PC_V2: + case Version::PC_V2: if (decompressed_bin.size() < sizeof(PSOQuestHeaderPC)) { throw runtime_error("bin file is too small for header"); } @@ -780,9 +803,9 @@ shared_ptr VersionedQuest::create_download_quest(uint8_t overrid reinterpret_cast(data_ptr)->language = override_language; } break; - case QuestScriptVersion::GC_NTE: - case QuestScriptVersion::GC_V3: - case QuestScriptVersion::XB_V3: + case Version::GC_NTE: + case Version::GC_V3: + case Version::XB_V3: if (decompressed_bin.size() < sizeof(PSOQuestHeaderGC)) { throw runtime_error("bin file is too small for header"); } @@ -790,10 +813,8 @@ shared_ptr VersionedQuest::create_download_quest(uint8_t overrid reinterpret_cast(data_ptr)->language = override_language; } break; - case QuestScriptVersion::BB_V4: + case Version::BB_V4: throw invalid_argument("PSOBB does not support download quests"); - case QuestScriptVersion::GC_EP3: - throw logic_error("Episode 3 quests cannot be converted to download quests"); default: throw invalid_argument("unknown game version"); } @@ -1094,8 +1115,7 @@ unordered_map decode_qst_data(const string& data) { } template -void add_command_header( - StringWriter& w, uint8_t command, uint8_t flag, uint16_t size) { +void add_command_header(StringWriter& w, uint8_t command, uint8_t flag, uint16_t size) { HeaderT header; header.command = command; header.flag = flag; @@ -1172,16 +1192,17 @@ string encode_qst_file( const string& name, uint32_t quest_number, const string& xb_filename, - QuestScriptVersion version, + Version version, bool is_dlq_encoded) { StringWriter w; // Some tools expect both open file commands at the beginning, hence this // unfortunate abstraction-breaking. switch (version) { - case QuestScriptVersion::DC_NTE: - case QuestScriptVersion::DC_V1: - case QuestScriptVersion::DC_V2: + case Version::DC_NTE: // DC NTE doesn't support quests, but we support encoding QST files anyway + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: for (const auto& it : files) { add_open_file_command_t(w, name, it.first, xb_filename, quest_number, it.second->size(), is_dlq_encoded); } @@ -1189,7 +1210,7 @@ string encode_qst_file( add_write_file_commands_t(w, it.first, *it.second, is_dlq_encoded, false); } break; - case QuestScriptVersion::PC_V2: + case Version::PC_V2: for (const auto& it : files) { add_open_file_command_t(w, name, it.first, xb_filename, quest_number, it.second->size(), is_dlq_encoded); } @@ -1197,9 +1218,10 @@ string encode_qst_file( add_write_file_commands_t(w, it.first, *it.second, is_dlq_encoded, false); } break; - case QuestScriptVersion::GC_NTE: - case QuestScriptVersion::GC_V3: - case QuestScriptVersion::GC_EP3: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: for (const auto& it : files) { add_open_file_command_t(w, name, it.first, xb_filename, quest_number, it.second->size(), is_dlq_encoded); } @@ -1207,7 +1229,7 @@ string encode_qst_file( add_write_file_commands_t(w, it.first, *it.second, is_dlq_encoded, false); } break; - case QuestScriptVersion::XB_V3: + case Version::XB_V3: for (const auto& it : files) { add_open_file_command_t(w, name, it.first, xb_filename, quest_number, it.second->size(), is_dlq_encoded); } @@ -1215,7 +1237,7 @@ string encode_qst_file( add_write_file_commands_t(w, it.first, *it.second, is_dlq_encoded, false); } break; - case QuestScriptVersion::BB_V4: + case Version::BB_V4: for (const auto& it : files) { add_open_file_command_t(w, name, it.first, xb_filename, quest_number, it.second->size(), is_dlq_encoded); } diff --git a/src/Quest.hh b/src/Quest.hh index 54abf227..40b5364b 100644 --- a/src/Quest.hh +++ b/src/Quest.hh @@ -58,7 +58,7 @@ struct VersionedQuest { Episode episode; bool joinable; std::string name; - QuestScriptVersion version; + Version version; uint8_t language; bool is_dlq_encoded; std::string short_description; @@ -72,7 +72,7 @@ struct VersionedQuest { VersionedQuest( uint32_t quest_number, uint32_t category_id, - QuestScriptVersion version, + Version version, uint8_t language, std::shared_ptr bin_contents, std::shared_ptr dat_contents, @@ -98,11 +98,11 @@ public: Quest& operator=(Quest&&) = default; void add_version(std::shared_ptr vq); - bool has_version(QuestScriptVersion v, uint8_t language) const; - bool has_version_any_language(QuestScriptVersion v) const; - std::shared_ptr version(QuestScriptVersion v, uint8_t language) const; + bool has_version(Version v, uint8_t language) const; + bool has_version_any_language(Version v) const; + std::shared_ptr version(Version v, uint8_t language) const; - static uint16_t versions_key(QuestScriptVersion v, uint8_t language); + static uint32_t versions_key(Version v, uint8_t language); uint32_t quest_number; uint32_t category_id; @@ -111,7 +111,7 @@ public: std::string name; std::shared_ptr battle_rules; ssize_t challenge_template_index; - std::map> versions; + std::map> versions; }; struct QuestIndex { @@ -123,7 +123,7 @@ struct QuestIndex { QuestIndex(const std::string& directory, std::shared_ptr category_index, bool is_ep3); std::shared_ptr get(uint32_t quest_number) const; - std::vector> filter(Episode episode, uint32_t category_id, QuestScriptVersion version) const; + std::vector> filter(Episode episode, uint32_t category_id, Version version) const; }; std::string encode_download_quest_data( @@ -149,5 +149,5 @@ std::string encode_qst_file( const std::string& name, uint32_t quest_number, const std::string& xb_filename, - QuestScriptVersion version, + Version version, bool is_dlq_encoded); diff --git a/src/QuestScript.cc b/src/QuestScript.cc index 04f20c65..8e4076a8 100644 --- a/src/QuestScript.cc +++ b/src/QuestScript.cc @@ -23,32 +23,6 @@ using AttackData = BattleParamsIndex::AttackData; using ResistData = BattleParamsIndex::ResistData; using MovementData = BattleParamsIndex::MovementData; -template <> -const char* name_for_enum(QuestScriptVersion v) { - switch (v) { - case QuestScriptVersion::DC_NTE: - return "DC_NTE"; - case QuestScriptVersion::DC_V1: - return "DC_V1"; - case QuestScriptVersion::DC_V2: - return "DC_V2"; - case QuestScriptVersion::PC_V2: - return "PC_V2"; - case QuestScriptVersion::GC_NTE: - return "GC_NTE"; - case QuestScriptVersion::GC_V3: - return "GC_V3"; - case QuestScriptVersion::XB_V3: - return "XB_V3"; - case QuestScriptVersion::GC_EP3: - return "GC_EP3"; - case QuestScriptVersion::BB_V4: - return "BB_V4"; - default: - return "__UNKNOWN__"; - } -} - // bit_cast isn't in the standard place on macOS (it is apparently implicitly // included by resource_dasm, but newserv can be built without resource_dasm) // and I'm too lazy to go find the right header to include @@ -152,12 +126,12 @@ struct QuestScriptOpcodeDefinition { flags(flags) {} }; -constexpr uint16_t v_flag(QuestScriptVersion v) { +constexpr uint16_t v_flag(Version v) { return (1 << static_cast(v)); } using Arg = QuestScriptOpcodeDefinition::Argument; -using V = QuestScriptVersion; +using V = Version; static constexpr uint16_t F_DC_NTE = v_flag(V::DC_NTE); static constexpr uint16_t F_DC_V1 = v_flag(V::DC_V1); @@ -801,10 +775,10 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = { }; static const unordered_map& -opcodes_for_version(QuestScriptVersion v) { +opcodes_for_version(Version v) { static array< unordered_map, - static_cast(QuestScriptVersion::BB_V4) + 1> + static_cast(Version::BB_V4) + 1> indexes; auto& index = indexes.at(static_cast(v)); @@ -823,7 +797,7 @@ opcodes_for_version(QuestScriptVersion v) { return index; } -std::string disassemble_quest_script(const void* data, size_t size, QuestScriptVersion version, uint8_t language) { +std::string disassemble_quest_script(const void* data, size_t size, Version version, uint8_t language) { StringReader r(data, size); deque lines; @@ -831,9 +805,16 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV size_t code_offset = 0; size_t function_table_offset = 0; switch (version) { - case QuestScriptVersion::DC_NTE: - case QuestScriptVersion::DC_V1: - case QuestScriptVersion::DC_V2: { + case Version::DC_NTE: { + const auto& header = r.get(); + code_offset = header.code_offset; + function_table_offset = header.function_table_offset; + lines.emplace_back(".name " + JSON(header.name.decode(0)).serialize()); + break; + } + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: { const auto& header = r.get(); code_offset = header.code_offset; function_table_offset = header.function_table_offset; @@ -844,7 +825,7 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV lines.emplace_back(".long_desc " + JSON(header.long_description.decode(language)).serialize()); break; } - case QuestScriptVersion::PC_V2: { + case Version::PC_V2: { use_wstrs = true; const auto& header = r.get(); code_offset = header.code_offset; @@ -856,10 +837,11 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV lines.emplace_back(".long_desc " + JSON(header.long_description.decode(language)).serialize()); break; } - case QuestScriptVersion::GC_NTE: - case QuestScriptVersion::GC_V3: - case QuestScriptVersion::GC_EP3: - case QuestScriptVersion::XB_V3: { + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + case Version::XB_V3: { const auto& header = r.get(); code_offset = header.code_offset; function_table_offset = header.function_table_offset; @@ -871,7 +853,7 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV lines.emplace_back(".long_desc " + JSON(header.long_description.decode(language)).serialize()); break; } - case QuestScriptVersion::BB_V4: { + case Version::BB_V4: { use_wstrs = true; const auto& header = r.get(); code_offset = header.code_offset; @@ -1484,7 +1466,7 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV return join(lines, "\n"); } -Episode find_quest_episode_from_script(const void* data, size_t size, QuestScriptVersion version) { +Episode find_quest_episode_from_script(const void* data, size_t size, Version version) { StringReader r(data, size); bool use_wstrs = false; @@ -1492,22 +1474,24 @@ Episode find_quest_episode_from_script(const void* data, size_t size, QuestScrip size_t function_table_offset = 0; Episode header_episode = Episode::NONE; switch (version) { - case QuestScriptVersion::DC_NTE: - case QuestScriptVersion::DC_V1: - case QuestScriptVersion::DC_V2: - case QuestScriptVersion::PC_V2: + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::PC_V2: return Episode::EP1; - case QuestScriptVersion::GC_NTE: - case QuestScriptVersion::GC_V3: - case QuestScriptVersion::GC_EP3: - case QuestScriptVersion::XB_V3: { + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + case Version::XB_V3: { const auto& header = r.get(); code_offset = header.code_offset; function_table_offset = header.function_table_offset; header_episode = episode_for_quest_episode_number(header.episode); break; } - case QuestScriptVersion::BB_V4: { + case Version::BB_V4: { use_wstrs = true; const auto& header = r.get(); code_offset = header.code_offset; diff --git a/src/QuestScript.hh b/src/QuestScript.hh index 7e581297..071ed85c 100644 --- a/src/QuestScript.hh +++ b/src/QuestScript.hh @@ -9,21 +9,14 @@ #include "Text.hh" #include "Version.hh" -enum class QuestScriptVersion { - DC_NTE = 0, - DC_V1 = 1, - DC_V2 = 2, - PC_V2 = 3, - GC_NTE = 4, - GC_V3 = 5, - XB_V3 = 6, - GC_EP3 = 7, - BB_V4 = 8, - UNKNOWN = 15, -}; - -template <> -const char* name_for_enum(QuestScriptVersion v); +struct PSOQuestHeaderDCNTE { + /* 0000 */ le_uint32_t code_offset; + /* 0004 */ le_uint32_t function_table_offset; + /* 0008 */ le_uint32_t size; + /* 000C */ le_uint32_t unused; + /* 0010 */ pstring name; + /* 0020 */ +} __attribute__((packed)); struct PSOQuestHeaderDC { // Same format for DC v1 and v2 /* 0000 */ le_uint32_t code_offset; @@ -89,7 +82,6 @@ struct PSOQuestHeaderBB { Episode episode_for_quest_episode_number(uint8_t episode_number); -std::string disassemble_quest_script( - const void* data, size_t size, QuestScriptVersion version, uint8_t language); +std::string disassemble_quest_script(const void* data, size_t size, Version version, uint8_t language); -Episode find_quest_episode_from_script(const void* data, size_t size, QuestScriptVersion version); +Episode find_quest_episode_from_script(const void* data, size_t size, Version version); diff --git a/src/RareItemSet.cc b/src/RareItemSet.cc index e77c3a08..60b67d2e 100644 --- a/src/RareItemSet.cc +++ b/src/RareItemSet.cc @@ -17,7 +17,7 @@ string RareItemSet::ExpandedDrop::str() const { this->probability, frac.first, frac.second, this->item_code[0], this->item_code[1], this->item_code[2]); } -string RareItemSet::ExpandedDrop::str(GameVersion version, shared_ptr name_index) const { +string RareItemSet::ExpandedDrop::str(Version version, shared_ptr name_index) const { ItemData item; item.data1[0] = this->item_code[0]; item.data1[1] = this->item_code[1]; @@ -296,7 +296,7 @@ RareItemSet::RareItemSet(const string& rel_data, bool is_big_endian) { } } -RareItemSet::RareItemSet(const JSON& json, GameVersion version, shared_ptr name_index) { +RareItemSet::RareItemSet(const JSON& json, Version version, shared_ptr name_index) { for (const auto& mode_it : json.as_dict()) { static const unordered_map mode_keys( {{"Normal", GameMode::NORMAL}, {"Battle", GameMode::BATTLE}, {"Challenge", GameMode::CHALLENGE}, {"Solo", GameMode::SOLO}}); @@ -420,7 +420,7 @@ std::string RareItemSet::serialize_gsl(bool big_endian) const { return GSLArchive::generate(files, big_endian); } -std::string RareItemSet::serialize_json(GameVersion version, shared_ptr name_index) const { +std::string RareItemSet::serialize_json(Version version, 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) { @@ -505,7 +505,7 @@ std::string RareItemSet::serialize_json(GameVersion version, shared_ptr name_index) const { + FILE* stream, Version version, std::shared_ptr name_index) const { static const array modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO}; static const array episodes = {Episode::EP1, Episode::EP2, Episode::EP4}; for (GameMode mode : modes) { diff --git a/src/RareItemSet.hh b/src/RareItemSet.hh index 741372e4..8570516d 100644 --- a/src/RareItemSet.hh +++ b/src/RareItemSet.hh @@ -22,14 +22,14 @@ public: parray item_code; std::string str() const; - std::string str(GameVersion version, std::shared_ptr name_index) const; + std::string str(Version version, std::shared_ptr name_index) const; }; RareItemSet(); RareItemSet(const AFSArchive& afs, bool is_v1); RareItemSet(const GSLArchive& gsl, bool is_big_endian); RareItemSet(const std::string& rel, bool is_big_endian); - RareItemSet(const JSON& json, GameVersion version, std::shared_ptr name_index = nullptr); + RareItemSet(const JSON& json, Version version, std::shared_ptr name_index = nullptr); ~RareItemSet() = default; std::vector get_enemy_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t rt_index) const; @@ -37,17 +37,17 @@ public: std::string serialize_afs() const; std::string serialize_gsl(bool big_endian) const; - std::string serialize_json(GameVersion version, std::shared_ptr name_index = nullptr) const; + std::string serialize_json(Version version, std::shared_ptr name_index = nullptr) const; void print_collection( FILE* stream, - GameVersion version, + Version version, GameMode mode, Episode episode, uint8_t difficulty, uint8_t section_id, std::shared_ptr name_index = nullptr) const; - void print_all_collections(FILE* stream, GameVersion version, std::shared_ptr name_index = nullptr) const; + void print_all_collections(FILE* stream, Version version, std::shared_ptr name_index = nullptr) const; protected: struct SpecCollection { diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index c4731cb1..5112834d 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -56,7 +56,7 @@ static shared_ptr proxy_options_menu_for_client(shared_ptrcheat_mode_behavior != ServerState::BehaviorSwitch::OFF) { - if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (!is_ep3(c->version())) { add_option(ProxyOptionsMenuItemID::INFINITE_HP, Client::Flag::INFINITE_HP_ENABLED, "Infinite HP", "Enable automatic HP\nrestoration when\nyou are hit by an\nenemy or trap\n\nCannot revive you\nfrom one-hit kills"); add_option(ProxyOptionsMenuItemID::INFINITE_TP, Client::Flag::INFINITE_TP_ENABLED, @@ -84,7 +84,7 @@ static shared_ptr proxy_options_menu_for_client(shared_ptrversion() != GameVersion::XB) { + if (c->version() != Version::XB_V3) { add_option(ProxyOptionsMenuItemID::SUPPRESS_LOGIN, Client::Flag::PROXY_SUPPRESS_REMOTE_LOGIN, "Skip login", "Use an alternate\nlogin sequence"); add_option(ProxyOptionsMenuItemID::SKIP_CARD, Client::Flag::PROXY_ZERO_REMOTE_GUILD_CARD, @@ -96,14 +96,14 @@ static shared_ptr proxy_options_menu_for_client(shared_ptr c) { - const auto& port_name = version_to_login_port_name.at(static_cast(c->version())); + string port_name = login_port_name_for_version(c->version()); auto s = c->require_server_state(); send_reconnect(c, s->connect_address_for_client(c), s->name_to_port_config.at(port_name)->port); } void send_client_to_lobby_server(shared_ptr c) { auto s = c->require_server_state(); - const auto& port_name = version_to_lobby_port_name.at(static_cast(c->version())); + string port_name = lobby_port_name_for_version(c->version()); send_reconnect(c, s->connect_address_for_client(c), s->name_to_port_config.at(port_name)->port); } @@ -111,7 +111,7 @@ void send_client_to_lobby_server(shared_ptr c) { void send_client_to_proxy_server(shared_ptr c) { auto s = c->require_server_state(); - const auto& port_name = version_to_proxy_port_name.at(static_cast(c->version())); + string port_name = proxy_port_name_for_version(c->version()); uint16_t local_port = s->name_to_port_config.at(port_name)->port; s->proxy_server->delete_session(c->license->serial_number); @@ -166,12 +166,15 @@ void on_connect(std::shared_ptr c) { send_server_init(c, SendServerInitFlag::IS_INITIAL_CONNECTION); break; + case ServerBehavior::PATCH_SERVER_PC: + c->channel.version = Version::PC_PATCH; + send_server_init(c, 0); + break; case ServerBehavior::PATCH_SERVER_BB: - c->config.set_flag(Client::Flag::IS_BB_PATCH); + c->channel.version = Version::BB_PATCH; send_server_init(c, 0); break; - case ServerBehavior::PATCH_SERVER_PC: case ServerBehavior::DATA_SERVER_BB: case ServerBehavior::LOBBY_SERVER: send_server_init(c, 0); @@ -202,8 +205,7 @@ static void send_main_menu(shared_ptr c) { const auto& l = it.second; if (l->is_game()) { num_games++; - if (l->version_is_allowed(c->quest_version()) && - (l->is_ep3() == c->config.check_flag(Client::Flag::IS_EPISODE_3))) { + if (l->version_is_allowed(c->version()) && (l->is_ep3() == is_ep3(c->version()))) { num_compatible_games++; } } @@ -263,7 +265,7 @@ void on_login_complete(shared_ptr c) { // On the login server, send the events/songs, ep3 updates, and the main // menu or welcome message - if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (is_ep3(c->version())) { if (s->ep3_menu_song >= 0) { send_ep3_change_music(c->channel, s->ep3_menu_song); } else if (s->pre_lobby_event) { @@ -291,13 +293,13 @@ void on_login_complete(shared_ptr c) { } else if (c->server_behavior == ServerBehavior::LOBBY_SERVER) { - if (c->version() == GameVersion::BB) { + if (c->version() == Version::BB_V4) { // This implicitly loads the client's account and player data send_complete_player_bb(c); c->game_data.should_update_play_time = true; } - if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (is_ep3(c->version())) { send_ep3_rank_update(c); } @@ -353,12 +355,20 @@ static void on_05_XB(shared_ptr c, uint16_t, uint32_t, string&) { static void set_console_client_flags(shared_ptr c, uint32_t sub_version) { if (c->channel.crypt_in->type() == PSOEncryption::Type::V2) { - if (sub_version <= 0x28) { - c->channel.version = GameVersion::DC; - c->log.info("Game version changed to DC"); - } else if (c->version() == GameVersion::GC) { - c->config.set_flag(Client::Flag::IS_GC_TRIAL_EDITION); - c->log.info("GC Trial Edition flag set"); + if (sub_version <= 0x24) { + c->channel.version = Version::DC_V1; + c->log.info("Game version changed to DC_V1"); + } else if (sub_version <= 0x28) { + c->channel.version = Version::DC_V2; + c->log.info("Game version changed to DC_V2"); + } else if (is_v3(c->version())) { + c->channel.version = Version::GC_NTE; + c->log.info("Game version changed to GC_NTE"); + } + } else { + if (sub_version >= 0x40 && !is_ep3(c->version())) { + c->channel.version = Version::GC_EP3; + c->log.info("Game version changed to GC_EP3"); } } c->config.set_flags_for_version(c->version(), sub_version); @@ -423,10 +433,9 @@ static void on_88_DCNTE(shared_ptr c, uint16_t, uint32_t, string& data) const auto& cmd = check_size_t(data); auto s = c->require_server_state(); - c->channel.version = GameVersion::DC; - c->config.set_flag(Client::Flag::IS_DC_V1); - c->config.set_flag(Client::Flag::IS_DC_TRIAL_EDITION); + c->channel.version = Version::DC_NTE; c->config.set_flags_for_version(c->version(), -1); + c->log.info("Game version changed to DC_NTE"); uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); try { @@ -467,11 +476,10 @@ static void on_8B_DCNTE(shared_ptr c, uint16_t, uint32_t, string& data) const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_DCNTE_8B)); auto s = c->require_server_state(); - c->channel.version = GameVersion::DC; + c->channel.version = Version::DC_NTE; c->channel.language = cmd.language; - c->config.set_flag(Client::Flag::IS_DC_V1); - c->config.set_flag(Client::Flag::IS_DC_TRIAL_EDITION); c->config.set_flags_for_version(c->version(), -1); + c->log.info("Game version changed to DC_NTE"); uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); try { @@ -522,9 +530,9 @@ static void on_90_DC(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data, 0xFFFF); auto s = c->require_server_state(); - c->channel.version = GameVersion::DC; - c->config.set_flag(Client::Flag::IS_DC_V1); + c->channel.version = Version::DC_V1; c->config.set_flags_for_version(c->version(), -1); + c->log.info("Game version changed to DC_V1"); uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); try { @@ -568,7 +576,7 @@ static void on_92_DC(shared_ptr c, uint16_t, uint32_t, string& data) { // than 92, so we use the presence of a 92 command to determine that the // client is actually DCv1 and not the prototype. c->config.set_flag(Client::Flag::CHECKED_FOR_DC_V1_PROTOTYPE); - c->config.clear_flag(Client::Flag::IS_DC_V1_PROTOTYPE); + c->log.info("Game version changed to DC_V1"); send_command(c, 0x92, 0x01); } @@ -631,7 +639,7 @@ static void on_93_DC(shared_ptr c, uint16_t, uint32_t, string& data) { if (!c->config.check_flag(Client::Flag::CHECKED_FOR_DC_V1_PROTOTYPE)) { send_command(c, 0x90, 0x01); c->config.set_flag(Client::Flag::CHECKED_FOR_DC_V1_PROTOTYPE); - c->config.set_flag(Client::Flag::IS_DC_V1_PROTOTYPE); + c->log.info("Game version changed to DC_V1_12_2000_PROTOTYPE (will be changed to V1 if 92 is received)"); } else { on_login_complete(c); } @@ -647,18 +655,18 @@ static void on_9A(shared_ptr c, uint16_t, uint32_t, string& data) { try { shared_ptr l; switch (c->version()) { - case GameVersion::DC: - case GameVersion::PC: + case Version::DC_V2: + case Version::PC_V2: l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode()); break; - case GameVersion::GC: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: l = s->license_index->verify_gc(serial_number, cmd.access_key.decode()); break; - case GameVersion::XB: - throw runtime_error("xbox licenses are not implemented"); - break; default: - throw logic_error("unsupported versioned command"); + throw runtime_error("unsupported versioned command"); } c->set_license(l); send_command(c, 0x9A, 0x02); @@ -683,11 +691,11 @@ static void on_9A(shared_ptr c, uint16_t, uint32_t, string& data) { // no license exists at this point, disconnect the client even if // unregistered clients are allowed. shared_ptr l; - if ((c->version() == GameVersion::GC) || (c->version() == GameVersion::XB)) { + if (is_v3(c->version())) { send_command(c, 0x9A, 0x04); c->should_disconnect = true; return; - } else if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::PC)) { + } else if (is_v1_or_v2(c->version())) { shared_ptr l(new License()); l->serial_number = serial_number; l->access_key = cmd.access_key.decode(); @@ -716,16 +724,16 @@ static void on_9C(shared_ptr c, uint16_t, uint32_t, string& data) { try { shared_ptr l; switch (c->version()) { - case GameVersion::DC: - case GameVersion::PC: + case Version::DC_V2: + case Version::PC_V2: l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode()); break; - case GameVersion::GC: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: l = s->license_index->verify_gc(serial_number, cmd.access_key.decode(), cmd.password.decode()); break; - case GameVersion::XB: - throw runtime_error("xbox licenses are not implemented"); - break; default: throw logic_error("unsupported versioned command"); } @@ -751,7 +759,7 @@ static void on_9C(shared_ptr c, uint16_t, uint32_t, string& data) { shared_ptr l(new License()); l->serial_number = serial_number; l->access_key = cmd.access_key.decode(); - if (c->version() == GameVersion::GC) { + if (is_gc(c->version())) { l->gc_password = cmd.password.decode(); } s->license_index->add(l); @@ -773,7 +781,7 @@ static void on_9D_9E(shared_ptr c, uint16_t command, uint32_t, string& d if (command == 0x9D) { base_cmd = &check_size_t(data, sizeof(C_LoginExtended_PC_9D)); if (base_cmd->is_extended) { - if (c->version() == GameVersion::PC) { + if (c->version() == Version::PC_V2) { const auto& cmd = check_size_t(data); if (cmd.extension.lobby_refs[0].menu_id == MenuID::LOBBY) { c->preferred_lobby_id = cmd.extension.lobby_refs[0].item_id; @@ -827,16 +835,16 @@ static void on_9D_9E(shared_ptr c, uint16_t command, uint32_t, string& d try { shared_ptr l; switch (c->version()) { - case GameVersion::DC: - case GameVersion::PC: + case Version::DC_V2: + case Version::PC_V2: l = s->license_index->verify_v1_v2(serial_number, base_cmd->access_key.decode()); break; - case GameVersion::GC: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: l = s->license_index->verify_gc(serial_number, base_cmd->access_key.decode()); break; - case GameVersion::XB: - throw runtime_error("xbox licenses are not implemented"); - break; default: throw logic_error("unsupported versioned command"); } @@ -862,11 +870,11 @@ static void on_9D_9E(shared_ptr c, uint16_t command, uint32_t, string& d // password already, which should have created and added a license. So, if // no license exists at this point, disconnect the client even if // unregistered clients are allowed. - if (c->version() == GameVersion::GC) { + if (is_v3(c->version())) { send_command(c, 0x04, 0x04); c->should_disconnect = true; return; - } else if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::PC)) { + } else if (is_v1_or_v2(c->version())) { shared_ptr l(new License()); l->serial_number = serial_number; l->access_key = base_cmd->access_key.decode(); @@ -1051,12 +1059,14 @@ static void on_93_BB(shared_ptr c, uint16_t, uint32_t, string& data) { static void on_9F(shared_ptr c, uint16_t, uint32_t, string& data) { switch (c->version()) { - case GameVersion::GC: { + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: { const auto& cmd = check_size_t(data); c->config.parse_from(cmd.data); break; } - case GameVersion::XB: { + case Version::XB_V3: { const auto& cmd = check_size_t(data); // On XB, this command is part of the login sequence, so we may not be // able to import the config the first time the client connects. If we @@ -1071,7 +1081,7 @@ static void on_9F(shared_ptr c, uint16_t, uint32_t, string& data) { on_login_complete(c); break; } - case GameVersion::BB: { + case Version::BB_V4: { const auto& cmd = check_size_t(data); c->config.parse_from(cmd.data); break; @@ -1658,7 +1668,7 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, string& data) { if (!q) { send_quest_info(c, "$C4Quest does not\nexist.", is_download_quest); } else { - auto vq = q->version(c->quest_version(), c->language()); + auto vq = q->version(c->version(), c->language()); if (!vq) { send_quest_info(c, "$C4Quest does not\nexist for this game\nversion.", is_download_quest); } else { @@ -1679,7 +1689,7 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, string& data) { if (!game->is_game()) { send_ship_info(c, "$C4Incorrect game ID"); - } else if (c->config.check_flag(Client::Flag::IS_EPISODE_3) && game->is_ep3()) { + } else if (is_ep3(c->version()) && game->is_ep3()) { send_ep3_game_details(c, game); } else { @@ -1742,7 +1752,7 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, string& data) { case MenuID::TOURNAMENTS_FOR_SPEC: case MenuID::TOURNAMENTS: { - if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (!is_ep3(c->version())) { send_ship_info(c, "Incorrect menu ID"); break; } @@ -1754,7 +1764,7 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, string& data) { } case MenuID::TOURNAMENT_ENTRIES: { - if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (!is_ep3(c->version())) { send_ship_info(c, "Incorrect menu ID"); break; } @@ -1828,13 +1838,13 @@ static void on_quest_loaded(shared_ptr l) { } auto s = l->require_server_state(); - if ((l->base_version == GameVersion::BB) && l->map) { + if ((l->base_version == Version::BB_PATCH) && l->map) { auto leader_c = l->clients.at(l->leader_id); if (!leader_c) { throw logic_error("lobby leader is missing"); } - auto vq = l->quest->version(QuestScriptVersion::BB_V4, leader_c->language()); + auto vq = l->quest->version(Version::BB_V4, leader_c->language()); auto dat_contents = prs_decompress(*vq->dat_contents); l->map->clear(); l->map->add_enemies_and_objects_from_quest_data( @@ -1864,7 +1874,7 @@ static void on_quest_loaded(shared_ptr l) { continue; } - if ((lc->version() == GameVersion::BB) && l->map) { + if ((lc->version() == Version::BB_V4) && l->map) { send_rare_enemy_index_list(lc, l->map->rare_enemy_indexes); } @@ -1872,7 +1882,7 @@ static void on_quest_loaded(shared_ptr l) { // the server is not informed when the clients have replaced their player // data. On BB, this is instead done in the 6xCF handler (for battle) or // the 02DF handler (for challenge). - if (l->base_version != GameVersion::BB) { + if (l->base_version != Version::BB_V4) { lc->game_data.delete_overlay(); if (l->quest->battle_rules) { lc->game_data.create_battle_overlay(l->quest->battle_rules, s->level_table); @@ -1919,9 +1929,7 @@ void set_lobby_quest(shared_ptr l, shared_ptr q) { if (!lc) { continue; } - if ((lc->version() != GameVersion::DC) && - (lc->version() != GameVersion::PC) && - !lc->config.check_flag(Client::Flag::IS_GC_TRIAL_EDITION)) { + if (is_v3(lc->version()) || is_v4(lc->version())) { num_clients_need_loading_flag++; } else { num_clients_skip_loading_flag++; @@ -1938,7 +1946,7 @@ void set_lobby_quest(shared_ptr l, shared_ptr q) { continue; } - auto vq = q->version(lc->quest_version(), lc->language()); + auto vq = q->version(lc->version(), lc->language()); if (!vq) { send_lobby_message_box(lc, "$C6Quest does not exist\nfor this game version."); lc->should_disconnect = true; @@ -1965,7 +1973,7 @@ void set_lobby_quest(shared_ptr l, shared_ptr q) { } static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { - bool uses_unicode = ((c->version() == GameVersion::PC) || (c->version() == GameVersion::BB)); + bool uses_utf16 = ::uses_utf16(c->version()); uint32_t menu_id; uint32_t item_id; @@ -1973,7 +1981,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { string password; if (data.size() > sizeof(C_MenuSelection_10_Flag00)) { - if (uses_unicode) { + if (uses_utf16) { // TODO: We can support the Flag03 variant here, but PC/BB probably never // actually use it. const auto& cmd = check_size_t(data); @@ -2007,8 +2015,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { c->config.set_flag(Client::Flag::SAVE_ENABLED); // DC NTE and the v1 prototype crash if they receive a 97 command, // so we instead do the redirect immediately - if ((c->version() == GameVersion::DC) && - (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION) || c->config.check_flag(Client::Flag::IS_DC_V1_PROTOTYPE))) { + if ((c->version() == Version::DC_NTE) || (c->version() == Version::DC_V1_12_2000_PROTOTYPE)) { send_client_to_lobby_server(c); } else { send_command(c, 0x97, 0x01); @@ -2036,7 +2043,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { case MainMenuItemID::DOWNLOAD_QUESTS: { auto s = c->require_server_state(); - if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (is_ep3(c->version())) { // Episode 3 has only download quests, not online quests, so this is // always the download quest menu. (Episode 3 does actually have // online quests, but they're served via a server data request @@ -2052,7 +2059,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { if (num_ep3_categories == 1) { auto quest_index = s->quest_index_for_client(c); if (quest_index) { - auto quests = quest_index->filter(Episode::EP3, ep3_category_id, c->quest_version()); + auto quests = quest_index->filter(Episode::EP3, ep3_category_id, c->version()); send_quest_menu(c, MenuID::QUEST, quests, true); } else { send_lobby_message_box(c, "$C6Quests are not available."); @@ -2063,10 +2070,10 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { // Not Episode 3, or there are multiple Episode 3 download categories; // send the categories menu instead - uint8_t flags = c->config.check_flag(Client::Flag::IS_EPISODE_3) + uint8_t flags = is_ep3(c->version()) ? QuestCategoryIndex::Category::Flag::EP3_DOWNLOAD : QuestCategoryIndex::Category::Flag::DOWNLOAD; - if (c->version() == GameVersion::DC || c->version() == GameVersion::PC) { + if (is_v1_or_v2(c->version())) { flags |= QuestCategoryIndex::Category::Flag::HIDE_ON_PRE_V3; } send_quest_menu(c, MenuID::QUEST_FILTER, s->quest_category_index, flags); @@ -2098,7 +2105,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { break; case MainMenuItemID::DISCONNECT: - if (c->version() == GameVersion::XB) { + if (c->version() == Version::XB_V3) { // On XB (at least via Insignia) the server has to explicitly tell // the client to disconnect by sending this command. send_command(c, 0x05, 0x00); @@ -2216,8 +2223,9 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { c->should_disconnect = true; } else { // Clear Check Tactics menu so client won't see newserv tournament - // state while logically on another server - if (c->config.check_flag(Client::Flag::IS_EPISODE_3) && !c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION)) { + // state while logically on another server. There is no such command + // on Trial Edition though, so only do this on Ep3 final. + if (c->version() == Version::GC_EP3) { send_ep3_confirm_tournament_entry(c, nullptr); } @@ -2252,9 +2260,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { send_lobby_message_box(c, "$C6You cannot join this\ngame because it is\nfull."); break; } - if (!game->version_is_allowed(c->quest_version()) || - (game->is_ep3() != c->config.check_flag(Client::Flag::IS_EPISODE_3)) || - (game->check_flag(Lobby::Flag::IS_EP3_TRIAL) != c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION))) { + if (!game->version_is_allowed(c->version())) { send_lobby_message_box(c, "$C6You cannot join this\ngame because it is\nfor a different\nversion of PSO."); break; } @@ -2308,7 +2314,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& category = s->quest_category_index->at(item_id); shared_ptr l = c->lobby.lock(); bool filter_by_episode = l && !(category.flags & QuestCategoryIndex::Category::Flag::GOVERNMENT); - auto quests = quest_index->filter(filter_by_episode ? l->episode : Episode::NONE, item_id, c->quest_version()); + auto quests = quest_index->filter(filter_by_episode ? l->episode : Episode::NONE, item_id, c->version()); // Hack: Assume the menu to be sent is the download quest menu if the // client is not in any lobby @@ -2349,7 +2355,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { set_lobby_quest(l, q); } else { - auto vq = q->version(c->quest_version(), c->language()); + auto vq = q->version(c->version(), c->language()); if (!vq) { send_lobby_message_box(c, "$C6Quest does not exist\nfor this game version."); break; @@ -2359,7 +2365,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { // other versions. // TODO: This is not true for Episode 3 Trial Edition. We also would // have to convert the map to a MapDefinitionTrial, though. - if (vq->version == QuestScriptVersion::GC_EP3) { + if (is_ep3(vq->version)) { send_open_quest_file(c, q->name, vq->bin_filename(), "", vq->quest_number, QuestFileType::EPISODE_3, vq->bin_contents); } else { vq = vq->create_download_quest(c->language()); @@ -2417,7 +2423,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { case MenuID::TOURNAMENTS_FOR_SPEC: case MenuID::TOURNAMENTS: { - if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (!is_ep3(c->version())) { throw runtime_error("non-Episode 3 client attempted to join tournament"); } auto s = c->require_server_state(); @@ -2428,7 +2434,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { break; } case MenuID::TOURNAMENT_ENTRIES: { - if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (!is_ep3(c->version())) { throw runtime_error("non-Episode 3 client attempted to join tournament"); } if (c->ep3_tournament_team.lock()) { @@ -2508,7 +2514,7 @@ static void on_84(shared_ptr c, uint16_t, uint32_t, string& data) { return; } - if (new_lobby->is_ep3() && !c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (new_lobby->is_ep3() && !is_ep3(c->version())) { send_lobby_message_box(c, "$C6Can't change lobby\n\n$C7The lobby is for\nEpisode 3 only."); return; } @@ -2557,7 +2563,7 @@ static void on_A1(shared_ptr c, uint16_t command, uint32_t flag, string& } static void on_8E_DCNTE(shared_ptr c, uint16_t command, uint32_t flag, string& data) { - if (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) { + if (c->version() == Version::DC_NTE) { on_A0(c, command, flag, data); } else { throw runtime_error("non-DCNTE client sent 8E"); @@ -2565,7 +2571,7 @@ static void on_8E_DCNTE(shared_ptr c, uint16_t command, uint32_t flag, s } static void on_8F_DCNTE(shared_ptr c, uint16_t command, uint32_t flag, string& data) { - if (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) { + if (c->version() == Version::DC_NTE) { on_A1(c, command, flag, data); } else { throw runtime_error("non-DCNTE client sent 8F"); @@ -2639,15 +2645,15 @@ static void on_A2(shared_ptr c, uint16_t, uint32_t flag, string& data) { // In Episode 3, there are no quest categories, so skip directly to the quest // filter menu. - if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (is_ep3(c->version())) { send_lobby_message_box(c, "$C6Episode 3 does not\nprovide online quests\nvia this interface."); } else { - uint8_t flags = (c->version() == GameVersion::DC || c->version() == GameVersion::PC) + uint8_t flags = is_v1_or_v2(c->version()) ? QuestCategoryIndex::Category::Flag::HIDE_ON_PRE_V3 : 0; - if ((c->version() == GameVersion::BB) && flag) { + if ((c->version() == Version::BB_V4) && flag) { flags |= QuestCategoryIndex::Category::Flag::GOVERNMENT; } else { switch (l->mode) { @@ -2679,7 +2685,7 @@ static void on_AC_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) if (c->config.check_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST)) { c->config.clear_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST); - if (l->base_version != GameVersion::BB) { + if (l->base_version != Version::BB_V4) { throw logic_error("joinable quest started on non-BB version"); } @@ -2702,12 +2708,9 @@ static void on_AC_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) static void on_AA(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); - if (c->version() == GameVersion::DC || c->version() == GameVersion::PC) { + if (is_v1_or_v2(c->version())) { throw runtime_error("pre-V3 client sent update quest stats command"); } - if (c->config.check_flag(Client::Flag::IS_GC_TRIAL_EDITION)) { - throw runtime_error("trial edition client sent update quest stats command"); - } auto l = c->require_lobby(); if (!l->is_game() || !l->quest.get()) { @@ -2774,22 +2777,24 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri auto player = c->game_data.character(); switch (c->version()) { - case GameVersion::DC: { - if (c->config.check_flag(Client::Flag::IS_DC_V1)) { - const auto& cmd = check_size_t(data); - player->inventory = cmd.inventory; - player->disp = cmd.disp.to_bb(player->inventory.language, player->inventory.language); - } else { - const auto& cmd = check_size_t(data, 0xFFFF); - player->inventory = cmd.inventory; - player->disp = cmd.disp.to_bb(player->inventory.language, player->inventory.language); - player->battle_records = cmd.records.battle; - player->challenge_records = cmd.records.challenge; - player->choice_search_config = cmd.choice_search_config; - } + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: { + const auto& cmd = check_size_t(data); + player->inventory = cmd.inventory; + player->disp = cmd.disp.to_bb(player->inventory.language, player->inventory.language); break; } - case GameVersion::PC: { + case Version::DC_V2: { + const auto& cmd = check_size_t(data, 0xFFFF); + player->inventory = cmd.inventory; + player->disp = cmd.disp.to_bb(player->inventory.language, player->inventory.language); + player->battle_records = cmd.records.battle; + player->challenge_records = cmd.records.challenge; + player->choice_search_config = cmd.choice_search_config; + break; + } + case Version::PC_V2: { const auto& cmd = check_size_t(data, 0xFFFF); player->inventory = cmd.inventory; player->disp = cmd.disp.to_bb(player->inventory.language, player->inventory.language); @@ -2811,20 +2816,30 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri } break; } - case GameVersion::GC: - case GameVersion::XB: { + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + case Version::XB_V3: { const C_CharacterData_V3_61_98* cmd; if (flag == 4) { // Episode 3 - if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (!is_ep3(c->version())) { throw runtime_error("non-Episode 3 client sent Episode 3 player data"); } const auto* cmd3 = &check_size_t(data); c->game_data.ep3_config.reset(new Episode3::PlayerConfig(cmd3->ep3_config)); cmd = reinterpret_cast(cmd3); + if (c->config.specific_version == 0x33000000) { + c->config.specific_version = 0x33534A30; // 3SJ0 + } } else { - if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) { - c->config.set_flag(Client::Flag::IS_EP3_TRIAL_EDITION); + if (is_ep3(c->version())) { + c->channel.version = Version::GC_EP3_TRIAL_EDITION; + c->log.info("Game version changed to GC_EP3_TRIAL_EDITION"); c->config.clear_flag(Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL); + if (c->config.specific_version == 0x33000000) { + c->config.specific_version = 0x33534A54; // 3SJT + } } cmd = &check_size_t(data, 0xFFFF); } @@ -2837,7 +2852,7 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri // update and the current tournament entry depend on this flag, we have // to delay sending them until now, instead of sending them during the // login sequence. - if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (is_ep3(c->version())) { bool flags_changed = false; if (!c->config.check_flag(Client::Flag::HAS_EP3_CARD_DEFS)) { send_ep3_card_list_update(c); @@ -2876,7 +2891,7 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri } break; } - case GameVersion::BB: { + case Version::BB_V4: { const auto& cmd = check_size_t(data, 0xFFFF); // Note: we don't copy the inventory and disp here because we already have // them (we sent the player data to the client in the first place) @@ -2981,14 +2996,14 @@ static void on_06(shared_ptr c, uint16_t, uint32_t, string& data) { if (text.empty()) { return; } - bool is_w = (c->version() == GameVersion::PC || c->version() == GameVersion::BB); + bool is_w = uses_utf16(c->version()); if (is_w && (text.size() & 1)) { text.push_back(0); } auto l = c->lobby.lock(); char private_flags = 0; - if ((c->version() == GameVersion::GC) && c->config.check_flag(Client::Flag::IS_EPISODE_3) && l && l->is_ep3() && (text[0] != '\t')) { + if (is_ep3(c->version()) && l && l->is_ep3() && (text[0] != '\t')) { private_flags = text[0]; text = text.substr(1); } @@ -3032,7 +3047,6 @@ static void on_06(shared_ptr c, uint16_t, uint32_t, string& data) { if (l->battle_record && l->battle_record->battle_in_progress()) { auto prepared_message = prepare_chat_data( c->version(), - c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION), c->language(), c->lobby_client_id, p->disp.name.decode(c->language()), @@ -3431,7 +3445,7 @@ static void on_DF_BB(shared_ptr c, uint16_t command, uint32_t, string& d award_state.rank_award_flags |= cmd.rank_bitmask; p->add_item(cmd.item); l->on_item_id_generated_externally(cmd.item.id); - string desc = s->describe_item(GameVersion::BB, cmd.item, false); + string desc = s->describe_item(Version::BB_V4, cmd.item, false); l->log.info("(Challenge mode) Item awarded to player %hhu: %s", c->lobby_client_id, desc.c_str()); break; } @@ -3470,21 +3484,27 @@ static void on_81(shared_ptr c, uint16_t, uint32_t, string& data) { string message; uint32_t to_guild_card_number; switch (c->version()) { - case GameVersion::DC: - case GameVersion::GC: - case GameVersion::XB: { + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + case Version::XB_V3: { const auto& cmd = check_size_t(data); to_guild_card_number = cmd.to_guild_card_number; message = cmd.text.decode(c->language()); break; } - case GameVersion::PC: { + case Version::PC_V2: { const auto& cmd = check_size_t(data); to_guild_card_number = cmd.to_guild_card_number; message = cmd.text.decode(c->language()); break; } - case GameVersion::BB: { + case Version::BB_V4: { const auto& cmd = check_size_t(data); to_guild_card_number = cmd.to_guild_card_number; message = cmd.text.decode(c->language()); @@ -3541,7 +3561,7 @@ static void on_D8(shared_ptr c, uint16_t, uint32_t, string& data) { void on_D9(shared_ptr c, uint16_t, uint32_t, string& data) { strip_trailing_zeroes(data); - bool is_w = (c->version() == GameVersion::PC || c->version() == GameVersion::BB); + bool is_w = uses_utf16(c->version()); if (is_w && (data.size() & 1)) { data.push_back(0); } @@ -3550,7 +3570,7 @@ void on_D9(shared_ptr c, uint16_t, uint32_t, string& data) { void on_C7(shared_ptr c, uint16_t, uint32_t, string& data) { strip_trailing_zeroes(data); - bool is_w = (c->version() == GameVersion::PC || c->version() == GameVersion::BB); + bool is_w = uses_utf16(c->version()); if (is_w && (data.size() & 1)) { data.push_back(0); } @@ -3564,7 +3584,7 @@ static void on_C8(shared_ptr c, uint16_t, uint32_t, string& data) { static void on_C6(shared_ptr c, uint16_t, uint32_t, string& data) { c->game_data.blocked_senders.fill(0); - if (c->version() == GameVersion::BB) { + if (c->version() == Version::BB_V4) { const auto& cmd = check_size_t(data); for (size_t z = 0; z < cmd.blocked_senders.size(); z++) { c->game_data.blocked_senders[z] = cmd.blocked_senders[z]; @@ -3645,15 +3665,70 @@ shared_ptr create_game_generic( shared_ptr game = s->create_lobby(); game->name = name; game->set_flag(Lobby::Flag::GAME); - if (c->config.check_flag(Client::Flag::IS_DC_V1)) { - game->set_flag(Lobby::Flag::USE_DCV1_RARE_TABLE); + + game->base_version = c->version(); + game->allowed_versions = 0; + switch (game->base_version) { + case Version::DC_NTE: + game->allow_version(Version::DC_NTE); + break; + case Version::DC_V1_12_2000_PROTOTYPE: + game->allow_version(Version::DC_V1_12_2000_PROTOTYPE); + break; + case Version::DC_V1: + game->allow_version(Version::DC_V1); + game->allow_version(Version::DC_V2); + if (s->allow_dc_pc_games) { + game->allow_version(Version::PC_V2); + } + break; + case Version::DC_V2: + if (allow_v1 && (difficulty <= 2)) { + game->allow_version(Version::DC_V1); + } + game->allow_version(Version::DC_V2); + if (s->allow_dc_pc_games) { + game->allow_version(Version::PC_V2); + } + break; + case Version::PC_V2: + game->allow_version(Version::PC_V2); + if (s->allow_dc_pc_games) { + game->allow_version(Version::DC_V2); + if (allow_v1 && (difficulty <= 2)) { + game->allow_version(Version::DC_V1); + } + } + break; + case Version::GC_NTE: + game->allow_version(Version::GC_NTE); + break; + case Version::GC_V3: + game->allow_version(Version::GC_V3); + if (s->allow_gc_xb_games) { + game->allow_version(Version::XB_V3); + } + break; + case Version::GC_EP3_TRIAL_EDITION: + game->allow_version(Version::GC_EP3_TRIAL_EDITION); + break; + case Version::GC_EP3: + game->allow_version(Version::GC_EP3); + break; + case Version::XB_V3: + game->allow_version(Version::XB_V3); + if (s->allow_gc_xb_games) { + game->allow_version(Version::GC_V3); + } + break; + case Version::BB_V4: + game->allow_version(Version::BB_V4); + break; + default: + throw logic_error("invalid quest script version"); } - if ((c->version() == GameVersion::GC) && - c->config.check_flag(Client::Flag::IS_EPISODE_3) && - c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION)) { - game->set_flag(Lobby::Flag::IS_EP3_TRIAL); - } - if ((c->version() == GameVersion::BB) || s->item_tracking_enabled) { + + if ((game->base_version == Version::BB_V4) || s->item_tracking_enabled) { game->set_flag(Lobby::Flag::ITEM_TRACKING_ENABLED); } // Only disable drops if the config flag is set and for regular multi-mode; @@ -3678,54 +3753,6 @@ shared_ptr create_game_generic( } game->password = password; - game->base_version = c->version(); - game->allowed_versions = 0; - switch (c->quest_version()) { - case QuestScriptVersion::DC_NTE: - game->allow_version(QuestScriptVersion::DC_NTE); - break; - case QuestScriptVersion::DC_V1: - game->allow_version(QuestScriptVersion::DC_V1); - game->allow_version(QuestScriptVersion::DC_V2); - if (s->allow_dc_pc_games) { - game->allow_version(QuestScriptVersion::PC_V2); - } - break; - case QuestScriptVersion::DC_V2: - case QuestScriptVersion::PC_V2: - if (allow_v1 && (difficulty <= 2)) { - game->allow_version(QuestScriptVersion::DC_V1); - } - game->allow_version(QuestScriptVersion::DC_V2); - if (s->allow_dc_pc_games) { - game->allow_version(QuestScriptVersion::PC_V2); - } - break; - case QuestScriptVersion::GC_NTE: - game->allow_version(QuestScriptVersion::GC_NTE); - break; - case QuestScriptVersion::GC_V3: - game->allow_version(QuestScriptVersion::GC_V3); - if (s->allow_gc_xb_games) { - game->allow_version(QuestScriptVersion::XB_V3); - } - break; - case QuestScriptVersion::XB_V3: - game->allow_version(QuestScriptVersion::XB_V3); - if (s->allow_gc_xb_games) { - game->allow_version(QuestScriptVersion::GC_V3); - } - break; - case QuestScriptVersion::GC_EP3: - game->allow_version(QuestScriptVersion::GC_EP3); - break; - case QuestScriptVersion::BB_V4: - game->allow_version(QuestScriptVersion::BB_V4); - break; - default: - throw logic_error("invalid quest script version"); - } - game->section_id = (c->config.override_section_id != 0xFF) ? c->config.override_section_id : p->disp.visual.section_id; @@ -3743,7 +3770,7 @@ shared_ptr create_game_generic( game->battle_player = battle_player; battle_player->set_lobby(game); } - if (game->base_version == GameVersion::BB) { + if (game->base_version == Version::BB_V4) { game->base_exp_multiplier = s->bb_global_exp_multiplier; } @@ -3751,7 +3778,7 @@ shared_ptr create_game_generic( game->next_item_id[x] = (0x00200000 * x) + 0x00010000; } game->next_game_item_id = 0x00810000; - if ((game->base_version == GameVersion::BB) || + if ((game->base_version == Version::BB_V4) || (game->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED) && s->behavior_enabled(s->use_server_item_tables_behavior))) { game->create_item_creator(); } @@ -3770,14 +3797,14 @@ shared_ptr create_game_generic( // Generate the map variations if (game->is_ep3() || - (c->version() == GameVersion::DC && - (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION) || c->config.check_flag(Client::Flag::IS_DC_V1_PROTOTYPE)))) { + (c->version() == Version::DC_NTE) || + (c->version() == Version::DC_V1_12_2000_PROTOTYPE)) { game->variations.clear(0); } else { generate_variations(game->variations, game->random_crypt, game->episode, is_solo); } - if (game->base_version == GameVersion::BB) { + if (game->base_version == Version::BB_V4) { game->map.reset(new Map()); for (size_t floor = 0; floor < 0x10; floor++) { c->log.info("[Map/%zu] Using variations %" PRIX32 ", %" PRIX32, @@ -3878,8 +3905,7 @@ static void on_0C_C1_E7_EC(shared_ptr c, uint16_t command, uint32_t, str auto s = c->require_server_state(); shared_ptr game; - if ((c->version() == GameVersion::DC) && - (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION) || c->config.check_flag(Client::Flag::IS_DC_V1_PROTOTYPE))) { + if ((c->version() == Version::DC_NTE) || (c->version() == Version::DC_V1_12_2000_PROTOTYPE)) { const auto& cmd = check_size_t>(data); game = create_game_generic(s, c, cmd.name.decode(c->language()), cmd.password.decode(c->language()), Episode::EP1, GameMode::NORMAL, 0, true); @@ -3887,14 +3913,14 @@ static void on_0C_C1_E7_EC(shared_ptr c, uint16_t command, uint32_t, str const auto& cmd = check_size_t(data); // Only allow E7/EC from Ep3 clients - bool client_is_ep3 = c->config.check_flag(Client::Flag::IS_EPISODE_3); + bool client_is_ep3 = is_ep3(c->version()); if (((command & 0xF0) == 0xE0) != client_is_ep3) { throw runtime_error("invalid command"); } Episode episode = Episode::NONE; bool allow_v1 = false; - if (c->version() == GameVersion::DC) { + if (is_v1_or_v2(c->version())) { allow_v1 = (cmd.episode == 0); episode = Episode::EP1; } else if (client_is_ep3) { @@ -3985,7 +4011,7 @@ static void on_C1_BB(shared_ptr c, uint16_t, uint32_t, string& data) { } static void on_8A(shared_ptr c, uint16_t, uint32_t, string& data) { - if ((c->version() == GameVersion::DC) && c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) { + if (c->version() == Version::DC_NTE) { const auto& cmd = check_size_t(data); set_console_client_flags(c, cmd.sub_version); send_command(c, 0x8A, 0x01); @@ -4007,7 +4033,7 @@ static void on_6F(shared_ptr c, uint16_t command, uint32_t, string& data c->config.clear_flag(Client::Flag::LOADING); send_resume_game(l, c); - if (l->base_version == GameVersion::BB) { + if (l->base_version == Version::BB_V4) { send_set_exp_multiplier(l); } send_server_time(c); @@ -4022,12 +4048,12 @@ static void on_6F(shared_ptr c, uint16_t command, uint32_t, string& data // BB sends 016F when the client is done loading a quest. In that case, we // shouldn't send the quest to them again! - if (command == 0x006F && c->version() == GameVersion::BB) { + if (command == 0x006F && c->version() == Version::BB_V4) { if (l->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS)) { if (!l->quest) { throw runtime_error("JOINABLE_QUEST_IN_PROGRESS is set, but lobby has no quest"); } - auto vq = l->quest->version(c->quest_version(), c->language()); + auto vq = l->quest->version(c->version(), c->language()); if (!vq) { throw runtime_error("JOINABLE_QUEST_IN_PROGRESS is set, but lobby has no quest for client version"); } @@ -4049,8 +4075,7 @@ static void on_6F(shared_ptr c, uint16_t command, uint32_t, string& data l->battle_player->start(); } else if (watched_lobby && watched_lobby->ep3_server) { if (!watched_lobby->ep3_server->battle_finished) { - watched_lobby->ep3_server->send_commands_for_joining_spectator( - c->channel, c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION)); + watched_lobby->ep3_server->send_commands_for_joining_spectator(c->channel); } send_ep3_update_game_metadata(watched_lobby); } @@ -4156,7 +4181,7 @@ static void on_D2_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) } auto complete_trade_for_side = [&](shared_ptr to_c, shared_ptr from_c) { - if (c->version() == GameVersion::BB) { + if (c->version() == Version::BB_V4) { // On BB, the server is expected to generate the delete item and create // item commands auto to_p = to_c->game_data.character(); @@ -4230,7 +4255,7 @@ static void on_D4_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) } static void on_EE_Ep3(shared_ptr c, uint16_t, uint32_t flag, string& data) { - if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (!is_ep3(c->version())) { throw runtime_error("non-Ep3 client sent card trade command"); } auto l = c->require_lobby(); @@ -4252,7 +4277,7 @@ static void on_EE_Ep3(shared_ptr c, uint16_t, uint32_t flag, string& dat if (!target_c) { throw runtime_error("card trade command sent to missing player"); } - if (!target_c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (!is_ep3(target_c->version())) { throw runtime_error("card trade target is not Episode 3"); } @@ -4334,7 +4359,7 @@ static void on_EE_Ep3(shared_ptr c, uint16_t, uint32_t flag, string& dat static void on_EF_Ep3(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_v(data.size(), 0); - if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (!is_ep3(c->version())) { throw runtime_error("non-Ep3 client sent card auction request"); } auto l = c->require_lobby(); @@ -4737,14 +4762,15 @@ static void on_04_P(shared_ptr c, uint16_t, uint32_t, string& data) { // On BB we can use colors and newlines should be \n; on PC we can't use // colors, the text is auto-word-wrapped, and newlines should be \r\n. - const string& message = c->config.check_flag(Client::Flag::IS_BB_PATCH) + bool is_bb_patch = (c->version() == Version::BB_PATCH); + const string& message = is_bb_patch ? s->bb_patch_server_message : s->pc_patch_server_message; if (!message.empty()) { send_message_box(c, message.c_str()); } - auto index = c->config.check_flag(Client::Flag::IS_BB_PATCH) ? s->bb_patch_file_index : s->pc_patch_file_index; + auto index = is_bb_patch ? s->bb_patch_file_index : s->pc_patch_file_index; if (index.get()) { send_command(c, 0x0B, 0x00); // Start patch session; go to root directory @@ -4827,289 +4853,309 @@ typedef void (*on_command_t)(shared_ptr c, uint16_t command, uint32_t fl // Command handler table, indexed by command number and game version. Null // entries in this table cause on_unimplemented_command to be called, which // disconnects the client. -static on_command_t handlers[0x100][6] = { +static on_command_t handlers[0x100][13] = { // clang-format off - // PATCH DC PC GC XB BB - /* 00 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 01 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 02 */ {on_02_P, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 03 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 04 */ {on_04_P, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 05 */ {nullptr, on_ignored, on_ignored, on_ignored, on_05_XB, on_ignored}, - /* 06 */ {nullptr, on_06, on_06, on_06, on_06, on_06}, - /* 07 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 08 */ {nullptr, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6}, - /* 09 */ {nullptr, on_09, on_09, on_09, on_09, on_09}, - /* 0A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 0B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 0C */ {nullptr, on_0C_C1_E7_EC, nullptr, nullptr, nullptr, nullptr}, - /* 0D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 0E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 0F */ {on_0F_P, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 10 */ {on_10_P, on_10, on_10, on_10, on_10, on_10}, - /* 11 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 12 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 13 */ {nullptr, on_ignored, on_ignored, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB}, - /* 14 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 15 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 16 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 17 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 18 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 19 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 1A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 1B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 1C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 1D */ {nullptr, on_1D, on_1D, on_1D, on_1D, on_1D}, - /* 1E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 1F */ {nullptr, on_1F, on_1F, nullptr, nullptr, nullptr}, - // PATCH DC PC GC XB BB - /* 20 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 21 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 22 */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_ignored}, - /* 23 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 24 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 25 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 26 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 27 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 28 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 29 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 2A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 2B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 2C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 2D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 2E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 2F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 30 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 31 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 32 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 33 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 34 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 35 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 36 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 37 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 38 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 39 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 3A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 3B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 3C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 3D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 3E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 3F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - // PATCH DC PC GC XB BB - /* 40 */ {nullptr, on_40, on_40, on_40, on_40, on_40}, - /* 41 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 42 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 43 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 44 */ {nullptr, on_ignored, on_ignored, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB}, - /* 45 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 46 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 47 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 48 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 49 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 4A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 4B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 4C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 4D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 4E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 4F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 50 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 51 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 52 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 53 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 54 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 55 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 56 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 57 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 58 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 59 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 5A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 5B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 5C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 5D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 5E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 5F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - // PATCH DC PC GC XB BB - /* 60 */ {nullptr, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB}, - /* 61 */ {nullptr, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98}, - /* 62 */ {nullptr, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB}, - /* 63 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 64 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 65 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 66 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 67 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 68 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 69 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 6A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 6B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 6C */ {nullptr, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB}, - /* 6D */ {nullptr, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB}, - /* 6E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 6F */ {nullptr, on_6F, on_6F, on_6F, on_6F, on_6F}, - /* 70 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 71 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 72 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 73 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 74 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 75 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 76 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 77 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 78 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 79 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 7A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 7B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 7C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 7D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 7E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 7F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - // PATCH DC PC GC XB BB - /* 80 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 81 */ {nullptr, on_81, on_81, on_81, on_81, on_81}, - /* 82 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 83 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 84 */ {nullptr, on_84, on_84, on_84, on_84, on_84}, - /* 85 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 86 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 87 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 88 */ {nullptr, on_88_DCNTE, nullptr, on_88_DCNTE, nullptr, nullptr}, - /* 89 */ {nullptr, on_89, on_89, on_89, on_89, on_89}, - /* 8A */ {nullptr, on_8A, on_8A, on_8A, on_8A, on_8A}, - /* 8B */ {nullptr, on_8B_DCNTE, nullptr, on_8B_DCNTE, nullptr, nullptr}, - /* 8C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 8D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 8E */ {nullptr, on_8E_DCNTE, nullptr, nullptr, nullptr, nullptr}, - /* 8F */ {nullptr, on_8F_DCNTE, nullptr, nullptr, nullptr, nullptr}, - /* 90 */ {nullptr, on_90_DC, nullptr, on_90_DC, nullptr, nullptr}, - /* 91 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 92 */ {nullptr, on_92_DC, nullptr, nullptr, nullptr, nullptr}, - /* 93 */ {nullptr, on_93_DC, nullptr, on_93_DC, nullptr, on_93_BB}, - /* 94 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 95 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 96 */ {nullptr, on_96, on_96, on_96, on_96, nullptr}, - /* 97 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 98 */ {nullptr, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98}, - /* 99 */ {nullptr, on_99, on_99, on_99, on_99, on_99}, - /* 9A */ {nullptr, on_9A, on_9A, on_9A, nullptr, nullptr}, - /* 9B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* 9C */ {nullptr, on_9C, on_9C, on_9C, on_9C, nullptr}, - /* 9D */ {nullptr, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, nullptr}, - /* 9E */ {nullptr, nullptr, on_9D_9E, on_9D_9E, on_9E_XB, nullptr}, - /* 9F */ {nullptr, nullptr, nullptr, on_9F, on_9F, on_9F}, - // PATCH DC PC GC XB BB - /* A0 */ {nullptr, on_A0, on_A0, on_A0, on_A0, on_A0}, - /* A1 */ {nullptr, on_A1, on_A1, on_A1, on_A1, on_A1}, - /* A2 */ {nullptr, on_A2, on_A2, on_A2, on_A2, on_A2}, - /* A3 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* A4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* A5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* A6 */ {nullptr, nullptr, nullptr, on_44_A6_V3_BB, on_44_A6_V3_BB, nullptr}, - /* A7 */ {nullptr, nullptr, nullptr, on_13_A7_V3_BB, on_13_A7_V3_BB, nullptr}, - /* A8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* A9 */ {nullptr, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored}, - /* AA */ {nullptr, nullptr, on_AA, on_AA, on_AA, on_AA}, - /* AB */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* AC */ {nullptr, nullptr, nullptr, on_AC_V3_BB, on_AC_V3_BB, on_AC_V3_BB}, - /* AD */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* AE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* AF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* B0 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* B1 */ {nullptr, on_B1, on_B1, on_B1, on_B1, nullptr}, - /* B2 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* B3 */ {nullptr, on_B3, on_B3, on_B3, on_B3, on_B3}, - /* B4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* B5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* B6 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* B7 */ {nullptr, nullptr, nullptr, on_ignored, nullptr, nullptr}, - /* B8 */ {nullptr, nullptr, nullptr, on_ignored, nullptr, nullptr}, - /* B9 */ {nullptr, nullptr, nullptr, on_ignored, nullptr, nullptr}, - /* BA */ {nullptr, nullptr, nullptr, on_BA_Ep3, nullptr, nullptr}, - /* BB */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* BC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* BD */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* BE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* BF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - // PATCH DC PC GC XB BB - /* C0 */ {nullptr, on_C0, on_C0, on_C0, on_C0, nullptr}, - /* C1 */ {nullptr, on_0C_C1_E7_EC, on_C1_PC, on_0C_C1_E7_EC, on_0C_C1_E7_EC, on_C1_BB}, - /* C2 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* C3 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* C4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* C5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* C6 */ {nullptr, nullptr, on_C6, on_C6, on_C6, on_C6}, - /* C7 */ {nullptr, nullptr, on_C7, on_C7, on_C7, on_C7}, - /* C8 */ {nullptr, nullptr, on_C8, on_C8, on_C8, on_C8}, - /* C9 */ {nullptr, nullptr, nullptr, on_6x_C9_CB, on_C9_XB, nullptr}, - /* CA */ {nullptr, nullptr, nullptr, on_CA_Ep3, nullptr, nullptr}, - /* CB */ {nullptr, nullptr, nullptr, on_6x_C9_CB, nullptr, nullptr}, - /* CC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* CD */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* CE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* CF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* D0 */ {nullptr, nullptr, nullptr, on_D0_V3_BB, on_D0_V3_BB, on_D0_V3_BB}, - /* D1 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* D2 */ {nullptr, nullptr, nullptr, on_D2_V3_BB, on_D2_V3_BB, on_D2_V3_BB}, - /* D3 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* D4 */ {nullptr, nullptr, nullptr, on_D4_V3_BB, on_D4_V3_BB, on_D4_V3_BB}, - /* D5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* D6 */ {nullptr, nullptr, nullptr, on_D6_V3, on_D6_V3, nullptr}, - /* D7 */ {nullptr, nullptr, nullptr, on_D7_GC, on_D7_GC, nullptr}, - /* D8 */ {nullptr, nullptr, on_D8, on_D8, on_D8, on_D8}, - /* D9 */ {nullptr, nullptr, on_D9, on_D9, on_D9, on_D9}, - /* DA */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* DB */ {nullptr, nullptr, nullptr, on_DB_V3, on_DB_V3, nullptr}, - /* DC */ {nullptr, nullptr, nullptr, on_DC_Ep3, nullptr, on_DC_BB}, - /* DD */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* DE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* DF */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_DF_BB}, - // PATCH DC PC GC XB BB - /* E0 */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_E0_BB}, - /* E1 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* E2 */ {nullptr, nullptr, nullptr, on_E2_Ep3, nullptr, on_E2_BB}, - /* E3 */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_E3_BB}, - /* E4 */ {nullptr, nullptr, nullptr, on_E4_Ep3, nullptr, nullptr}, - /* E5 */ {nullptr, nullptr, nullptr, on_E5_Ep3, nullptr, on_E5_BB}, - /* E6 */ {nullptr, nullptr, nullptr, on_08_E6, nullptr, nullptr}, - /* E7 */ {nullptr, nullptr, nullptr, on_0C_C1_E7_EC, nullptr, on_E7_BB}, - /* E8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_E8_BB}, - /* E9 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* EA */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_EA_BB}, - /* EB */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_EB_BB}, - /* EC */ {nullptr, nullptr, nullptr, on_0C_C1_E7_EC, nullptr, on_EC_BB}, - /* ED */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_ED_BB}, - /* EE */ {nullptr, nullptr, nullptr, on_EE_Ep3, nullptr, nullptr}, - /* EF */ {nullptr, nullptr, nullptr, on_EF_Ep3, nullptr, nullptr}, - /* F0 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* F1 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* F2 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* F3 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* F4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* F5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* F6 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* F7 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* F8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* F9 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* FA */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* FB */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* FC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* FD */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* FE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* FF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - // PATCH DC PC GC XB BB +// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC GCNTE GC EP3TE EP3 XB BB +/* 00 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 01 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 02 */ {on_02_P, on_02_P, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 03 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 04 */ {on_04_P, on_04_P, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 05 */ {nullptr, nullptr, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_05_XB, on_ignored}, +/* 06 */ {nullptr, nullptr, on_06, on_06, on_06, on_06, on_06, on_06, on_06, on_06, on_06, on_06, on_06}, +/* 07 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 08 */ {nullptr, nullptr, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6}, +/* 09 */ {nullptr, nullptr, on_09, on_09, on_09, on_09, on_09, on_09, on_09, on_09, on_09, on_09, on_09}, +/* 0A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 0B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 0C */ {nullptr, nullptr, on_0C_C1_E7_EC,on_0C_C1_E7_EC,on_0C_C1_E7_EC,on_0C_C1_E7_EC, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 0D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 0E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 0F */ {on_0F_P, on_0F_P, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC GCNTE GC EP3TE EP3 XB BB +/* 10 */ {on_10_P, on_10_P, on_10, on_10, on_10, on_10, on_10, on_10, on_10, on_10, on_10, on_10, on_10}, +/* 11 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 12 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 13 */ {nullptr, nullptr, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB}, +/* 14 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 15 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 16 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 17 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 18 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 19 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 1A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 1B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 1C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 1D */ {nullptr, nullptr, on_1D, on_1D, on_1D, on_1D, on_1D, on_1D, on_1D, on_1D, on_1D, on_1D, on_1D}, +/* 1E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 1F */ {nullptr, nullptr, on_1F, on_1F, on_1F, on_1F, on_1F, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC GCNTE GC EP3TE EP3 XB BB +/* 20 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 21 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 22 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_ignored}, +/* 23 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 24 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 25 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 26 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 27 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 28 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 29 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 2A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 2B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 2C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 2D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 2E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 2F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC GCNTE GC EP3TE EP3 XB BB +/* 30 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 31 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 32 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 33 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 34 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 35 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 36 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 37 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 38 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 39 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 3A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 3B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 3C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 3D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 3E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 3F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC GCNTE GC EP3TE EP3 XB BB +/* 40 */ {nullptr, nullptr, on_40, on_40, on_40, on_40, on_40, on_40, on_40, on_40, on_40, on_40, on_40}, +/* 41 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 42 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 43 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 44 */ {nullptr, nullptr, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB}, +/* 45 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 46 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 47 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 48 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 49 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 4A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 4B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 4C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 4D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 4E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 4F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC GCNTE GC EP3TE EP3 XB BB +/* 50 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 51 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 52 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 53 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 54 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 55 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 56 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 57 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 58 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 59 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 5A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 5B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 5C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 5D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 5E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 5F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC GCNTE GC EP3TE EP3 XB BB +/* 60 */ {nullptr, nullptr, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB}, +/* 61 */ {nullptr, nullptr, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98}, +/* 62 */ {nullptr, nullptr, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB}, +/* 63 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 64 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 65 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 66 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 67 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 68 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 69 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 6A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 6B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 6C */ {nullptr, nullptr, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB}, +/* 6D */ {nullptr, nullptr, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB}, +/* 6E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 6F */ {nullptr, nullptr, on_6F, on_6F, on_6F, on_6F, on_6F, on_6F, on_6F, on_6F, on_6F, on_6F, on_6F}, +// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC GCNTE GC EP3TE EP3 XB BB +/* 70 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 71 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 72 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 73 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 74 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 75 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 76 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 77 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 78 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 79 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 7A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 7B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 7C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 7D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 7E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 7F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC GCNTE GC EP3TE EP3 XB BB +/* 80 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 81 */ {nullptr, nullptr, on_81, on_81, on_81, on_81, on_81, on_81, on_81, on_81, on_81, on_81, on_81}, +/* 82 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 83 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 84 */ {nullptr, nullptr, on_84, on_84, on_84, on_84, on_84, on_84, on_84, on_84, on_84, on_84, on_84}, +/* 85 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 86 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 87 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 88 */ {nullptr, nullptr, on_88_DCNTE, on_88_DCNTE, on_88_DCNTE, on_88_DCNTE, nullptr, on_88_DCNTE, on_88_DCNTE, on_88_DCNTE, on_88_DCNTE, nullptr, nullptr}, +/* 89 */ {nullptr, nullptr, on_89, on_89, on_89, on_89, on_89, on_89, on_89, on_89, on_89, on_89, on_89}, +/* 8A */ {nullptr, nullptr, on_8A, on_8A, on_8A, on_8A, on_8A, on_8A, on_8A, on_8A, on_8A, on_8A, on_8A}, +/* 8B */ {nullptr, nullptr, on_8B_DCNTE, on_8B_DCNTE, on_8B_DCNTE, on_8B_DCNTE, nullptr, on_8B_DCNTE, on_8B_DCNTE, on_8B_DCNTE, on_8B_DCNTE, nullptr, nullptr}, +/* 8C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 8D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 8E */ {nullptr, nullptr, on_8E_DCNTE, on_8E_DCNTE, on_8E_DCNTE, on_8E_DCNTE, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 8F */ {nullptr, nullptr, on_8F_DCNTE, on_8F_DCNTE, on_8F_DCNTE, on_8F_DCNTE, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC GCNTE GC EP3TE EP3 XB BB +/* 90 */ {nullptr, nullptr, on_90_DC, on_90_DC, on_90_DC, on_90_DC, nullptr, on_90_DC, on_90_DC, on_90_DC, on_90_DC, nullptr, nullptr}, +/* 91 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 92 */ {nullptr, nullptr, on_92_DC, on_92_DC, on_92_DC, on_92_DC, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 93 */ {nullptr, nullptr, on_93_DC, on_93_DC, on_93_DC, on_93_DC, nullptr, on_93_DC, on_93_DC, on_93_DC, on_93_DC, nullptr, on_93_BB}, +/* 94 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 95 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 96 */ {nullptr, nullptr, on_96, on_96, on_96, on_96, on_96, on_96, on_96, on_96, on_96, on_96, nullptr}, +/* 97 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 98 */ {nullptr, nullptr, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98}, +/* 99 */ {nullptr, nullptr, on_99, on_99, on_99, on_99, on_99, on_99, on_99, on_99, on_99, on_99, on_99}, +/* 9A */ {nullptr, nullptr, on_9A, on_9A, on_9A, on_9A, on_9A, on_9A, on_9A, on_9A, on_9A, nullptr, nullptr}, +/* 9B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 9C */ {nullptr, nullptr, on_9C, on_9C, on_9C, on_9C, on_9C, on_9C, on_9C, on_9C, on_9C, on_9C, nullptr}, +/* 9D */ {nullptr, nullptr, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, nullptr}, +/* 9E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9E_XB, nullptr}, +/* 9F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_9F, on_9F, on_9F, on_9F, on_9F, on_9F}, +// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC GCNTE GC EP3TE EP3 XB BB +/* A0 */ {nullptr, nullptr, on_A0, on_A0, on_A0, on_A0, on_A0, on_A0, on_A0, on_A0, on_A0, on_A0, on_A0}, +/* A1 */ {nullptr, nullptr, on_A1, on_A1, on_A1, on_A1, on_A1, on_A1, on_A1, on_A1, on_A1, on_A1, on_A1}, +/* A2 */ {nullptr, nullptr, on_A2, on_A2, on_A2, on_A2, on_A2, on_A2, on_A2, on_A2, on_A2, on_A2, on_A2}, +/* A3 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* A4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* A5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* A6 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB, nullptr}, +/* A7 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB, nullptr}, +/* A8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* A9 */ {nullptr, nullptr, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored}, +/* AA */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_AA, on_AA, on_AA, on_AA, on_AA, on_AA, on_AA}, +/* AB */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* AC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_AC_V3_BB, on_AC_V3_BB, on_AC_V3_BB, on_AC_V3_BB, on_AC_V3_BB, on_AC_V3_BB}, +/* AD */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* AE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* AF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC GCNTE GC EP3TE EP3 XB BB +/* B0 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* B1 */ {nullptr, nullptr, on_B1, on_B1, on_B1, on_B1, on_B1, on_B1, on_B1, on_B1, on_B1, on_B1, nullptr}, +/* B2 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* B3 */ {nullptr, nullptr, on_B3, on_B3, on_B3, on_B3, on_B3, on_B3, on_B3, on_B3, on_B3, on_B3, on_B3}, +/* B4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* B5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* B6 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* B7 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_ignored, on_ignored, on_ignored, on_ignored, nullptr, nullptr}, +/* B8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_ignored, on_ignored, on_ignored, on_ignored, nullptr, nullptr}, +/* B9 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_ignored, on_ignored, on_ignored, on_ignored, nullptr, nullptr}, +/* BA */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_BA_Ep3, on_BA_Ep3, on_BA_Ep3, on_BA_Ep3, nullptr, nullptr}, +/* BB */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* BC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* BD */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* BE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* BF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC GCNTE GC EP3TE EP3 XB BB +/* C0 */ {nullptr, nullptr, on_C0, on_C0, on_C0, on_C0, on_C0, on_C0, on_C0, on_C0, on_C0, on_C0, nullptr}, +/* C1 */ {nullptr, nullptr, on_0C_C1_E7_EC,on_0C_C1_E7_EC,on_0C_C1_E7_EC,on_0C_C1_E7_EC, on_C1_PC, on_0C_C1_E7_EC, on_0C_C1_E7_EC, on_0C_C1_E7_EC, on_0C_C1_E7_EC, on_0C_C1_E7_EC, on_C1_BB}, +/* C2 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* C3 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* C4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* C5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* C6 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_C6, on_C6, on_C6, on_C6, on_C6, on_C6, on_C6}, +/* C7 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_C7, on_C7, on_C7, on_C7, on_C7, on_C7, on_C7}, +/* C8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_C8, on_C8, on_C8, on_C8, on_C8, on_C8, on_C8}, +/* C9 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_C9_XB, nullptr}, +/* CA */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_CA_Ep3, on_CA_Ep3, on_CA_Ep3, on_CA_Ep3, nullptr, nullptr}, +/* CB */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, nullptr, nullptr}, +/* CC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* CD */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* CE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* CF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC GCNTE GC EP3TE EP3 XB BB +/* D0 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_D0_V3_BB, on_D0_V3_BB, on_D0_V3_BB, on_D0_V3_BB, on_D0_V3_BB, on_D0_V3_BB}, +/* D1 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* D2 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_D2_V3_BB, on_D2_V3_BB, on_D2_V3_BB, on_D2_V3_BB, on_D2_V3_BB, on_D2_V3_BB}, +/* D3 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* D4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_D4_V3_BB, on_D4_V3_BB, on_D4_V3_BB, on_D4_V3_BB, on_D4_V3_BB, on_D4_V3_BB}, +/* D5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* D6 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_D6_V3, on_D6_V3, on_D6_V3, on_D6_V3, on_D6_V3, nullptr}, +/* D7 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_D7_GC, on_D7_GC, on_D7_GC, on_D7_GC, on_D7_GC, nullptr}, +/* D8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_D8, on_D8, on_D8, on_D8, on_D8, on_D8, on_D8}, +/* D9 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_D9, on_D9, on_D9, on_D9, on_D9, on_D9, on_D9}, +/* DA */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* DB */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_DB_V3, on_DB_V3, on_DB_V3, on_DB_V3, on_DB_V3, nullptr}, +/* DC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_DC_Ep3, on_DC_Ep3, on_DC_Ep3, on_DC_Ep3, nullptr, on_DC_BB}, +/* DD */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* DE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* DF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_DF_BB}, +// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC GCNTE GC EP3TE EP3 XB BB +/* E0 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_E0_BB}, +/* E1 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* E2 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_E2_Ep3, on_E2_Ep3, on_E2_Ep3, on_E2_Ep3, nullptr, on_E2_BB}, +/* E3 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_E3_BB}, +/* E4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_E4_Ep3, on_E4_Ep3, on_E4_Ep3, on_E4_Ep3, nullptr, nullptr}, +/* E5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_E5_Ep3, on_E5_Ep3, on_E5_Ep3, on_E5_Ep3, nullptr, on_E5_BB}, +/* E6 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_08_E6, on_08_E6, on_08_E6, on_08_E6, nullptr, nullptr}, +/* E7 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_0C_C1_E7_EC, on_0C_C1_E7_EC, on_0C_C1_E7_EC, on_0C_C1_E7_EC, nullptr, on_E7_BB}, +/* E8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_E8_BB}, +/* E9 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* EA */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_EA_BB}, +/* EB */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_EB_BB}, +/* EC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_0C_C1_E7_EC, on_0C_C1_E7_EC, on_0C_C1_E7_EC, on_0C_C1_E7_EC, nullptr, on_EC_BB}, +/* ED */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_ED_BB}, +/* EE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_EE_Ep3, on_EE_Ep3, on_EE_Ep3, on_EE_Ep3, nullptr, nullptr}, +/* EF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_EF_Ep3, on_EF_Ep3, on_EF_Ep3, on_EF_Ep3, nullptr, nullptr}, +// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC GCNTE GC EP3TE EP3 XB BB +/* F0 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* F1 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* F2 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* F3 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* F4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* F5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* F6 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* F7 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* F8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* F9 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* FA */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* FB */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* FC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* FD */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* FE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* FF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC GCNTE GC EP3TE EP3 XB BB // clang-format on }; -static void check_unlicensed_command(GameVersion version, uint8_t command) { +static void check_unlicensed_command(Version version, uint8_t command) { switch (version) { - case GameVersion::DC: + case Version::PC_PATCH: + case Version::BB_PATCH: + if (command != 0x02 && command != 0x04) { + throw runtime_error("only commands 02 and 04 may be sent before login"); + } + break; + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: // newserv doesn't actually know that DC clients are DC until it receives // an appropriate login command (93, 9A, or 9D), but those commands also // log the client in, so this case should never be executed. throw logic_error("cannot check unlicensed command for DC client"); - case GameVersion::PC: + case Version::PC_V2: if (command != 0x9A && command != 0x9C && command != 0x9D) { throw runtime_error("only commands 9A, 9C, and 9D may be sent before login"); } break; - case GameVersion::GC: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: // See comment in the DC case above for why DC commands are included here. if (command != 0x88 && // DC NTE command != 0x8B && // DC NTE @@ -5123,21 +5169,16 @@ static void check_unlicensed_command(GameVersion version, uint8_t command) { throw runtime_error("only commands 88, 8B, 90, 93, 9A, 9C, 9D, 9E, and DB may be sent before login"); } break; - case GameVersion::XB: + case Version::XB_V3: if (command != 0x9E && command != 0x9F) { throw runtime_error("only commands 9E and 9F may be sent before login"); } break; - case GameVersion::BB: + case Version::BB_V4: if (command != 0x93) { throw runtime_error("only command 93 may be sent before login"); } break; - case GameVersion::PATCH: - if (command != 0x02 && command != 0x04) { - throw runtime_error("only commands 02 and 04 may be sent before login"); - } - break; default: throw logic_error("invalid game version"); } @@ -5168,22 +5209,29 @@ void on_command( void on_command_with_header(shared_ptr c, const string& data) { switch (c->version()) { - case GameVersion::DC: - case GameVersion::GC: - case GameVersion::XB: { + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + case Version::XB_V3: { auto& header = check_size_t(data, 0xFFFF); string sub_data = data.substr(sizeof(header)); on_command(c, header.command, header.flag, sub_data); break; } - case GameVersion::PC: - case GameVersion::PATCH: { + case Version::PC_PATCH: + case Version::BB_PATCH: + case Version::PC_V2: { auto& header = check_size_t(data, 0xFFFF); string sub_data = data.substr(sizeof(header)); on_command(c, header.command, header.flag, sub_data); break; } - case GameVersion::BB: { + case Version::BB_V4: { auto& header = check_size_t(data, 0xFFFF); string sub_data = data.substr(sizeof(header)); on_command(c, header.command, header.flag, sub_data); diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index b4d9fd0f..38593f02 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -57,7 +57,7 @@ static void forward_subcommand( // If the command is an Ep3-only command, make sure an Ep3 client sent it bool command_is_ep3 = (command & 0xF0) == 0xC0; - if (command_is_ep3 && !c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (command_is_ep3 && !is_ep3(c->version())) { throw runtime_error("Episode 3 command sent by non-Episode 3 client"); } @@ -70,7 +70,7 @@ static void forward_subcommand( if (!target) { return; } - if (target->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) { + if (target->version() == Version::DC_NTE) { if (dc_nte_subcommand) { string nte_data(reinterpret_cast(data), size); nte_data[0] = dc_nte_subcommand; @@ -85,7 +85,7 @@ static void forward_subcommand( } else { if (command_is_ep3) { for (auto& target : l->clients) { - if (!target || (target == c) || !target->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (!target || (target == c) || !is_ep3(target->version())) { continue; } send_command(target, command, flag, data, size); @@ -97,7 +97,7 @@ static void forward_subcommand( if (!lc || (lc == c)) { continue; } - if (lc->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) { + if (lc->version() == Version::DC_NTE) { if (dc_nte_subcommand) { if (nte_data.empty()) { nte_data.assign(reinterpret_cast(data), size); @@ -121,7 +121,7 @@ static void forward_subcommand( watcher_subcommands.count(subcommand)) { for (const auto& watcher_lobby : l->watcher_lobbies) { for (auto& target : watcher_lobby->clients) { - if (target && target->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (target && is_ep3(target->version())) { send_command(target, command, flag, data, size); } } @@ -317,7 +317,7 @@ static void on_sync_joining_player_item_state(shared_ptr c, uint8_t comm // We need to byteswap mags' data2 fields if exactly one of the sender and // recipient is PSO GC - if ((c->version() == GameVersion::GC) == (target->version() == GameVersion::GC)) { + if (is_big_endian(c->version()) == is_big_endian(target->version())) { send_or_enqueue_joining_player_command(target, command, flag, data, size); } else { @@ -373,8 +373,8 @@ static void on_sync_joining_player_disp_and_inventory( } // This command's format is different on BB and non-BB - bool sender_is_bb = (c->version() == GameVersion::BB); - bool target_is_bb = (target->version() == GameVersion::BB); + bool sender_is_bb = (c->version() == Version::BB_V4); + bool target_is_bb = (target->version() == Version::BB_V4); if (sender_is_bb != target_is_bb) { // TODO: Figure out the BB 6x70 format and implement this throw runtime_error("6x70 command cannot be translated across BB boundary"); @@ -382,8 +382,8 @@ static void on_sync_joining_player_disp_and_inventory( // We need to byteswap mags' data2 fields if exactly one of the sender and // recipient are PSO GC - bool sender_is_gc = (c->version() == GameVersion::GC); - bool target_is_gc = (target->version() == GameVersion::GC); + bool sender_is_gc = is_gc(c->version()); + bool target_is_gc = is_gc(target->version()); if (target_is_gc == sender_is_gc) { send_or_enqueue_joining_player_command(target, command, flag, data, size); @@ -398,8 +398,8 @@ static void on_sync_joining_player_disp_and_inventory( out_cmd.xb_user_id_low = c->license->serial_number; } for (size_t z = 0; z < out_cmd.inventory.num_items; z++) { - out_cmd.inventory.items[z].data.decode_for_version(GameVersion::GC); - out_cmd.inventory.items[z].data.encode_for_version(GameVersion::XB, s->item_parameter_table_for_version(GameVersion::XB)); + out_cmd.inventory.items[z].data.decode_for_version(c->version()); + out_cmd.inventory.items[z].data.encode_for_version(target->version(), s->item_parameter_table_for_version(target->version())); } send_or_enqueue_joining_player_command(target, command, flag, out_cmd); @@ -410,8 +410,8 @@ static void on_sync_joining_player_disp_and_inventory( "GC 6x70 command is larger than XB 6x70 command"); auto out_cmd = check_size_t(data, size); for (size_t z = 0; z < out_cmd.inventory.num_items; z++) { - out_cmd.inventory.items[z].data.decode_for_version(GameVersion::XB); - out_cmd.inventory.items[z].data.encode_for_version(GameVersion::GC, s->item_parameter_table_for_version(GameVersion::GC)); + out_cmd.inventory.items[z].data.decode_for_version(c->version()); + out_cmd.inventory.items[z].data.encode_for_version(target->version(), s->item_parameter_table_for_version(target->version())); } send_or_enqueue_joining_player_command(target, command, flag, out_cmd); } @@ -460,7 +460,7 @@ static void on_ep3_sound_chat(shared_ptr c, uint8_t command, uint8_t fla // forwarded from spectator teams to the primary team. The client only uses // this behavior for the 6xBE command (sound chat), and newserv enforces that // only that command is sent via CB. - if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (!is_ep3(c->version())) { throw runtime_error("non-Episode 3 client sent sound chat command"); } @@ -469,7 +469,7 @@ static void on_ep3_sound_chat(shared_ptr c, uint8_t command, uint8_t fla auto watched_lobby = l->watched_lobby.lock(); if (watched_lobby) { for (auto& target : watched_lobby->clients) { - if (target && target->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (target && is_ep3(target->version())) { send_command(target, command, flag, data, size); } } @@ -521,27 +521,33 @@ static void on_send_guild_card(shared_ptr c, uint8_t command, uint8_t fl } switch (c->version()) { - case GameVersion::DC: { + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: { const auto& cmd = check_size_t(data, size); c->game_data.character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language()); break; } - case GameVersion::PC: { + case Version::PC_V2: { const auto& cmd = check_size_t(data, size); c->game_data.character(true, false)->guild_card.description = cmd.guild_card.description; break; } - case GameVersion::GC: { + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: { const auto& cmd = check_size_t(data, size); c->game_data.character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language()); break; } - case GameVersion::XB: { + case Version::XB_V3: { const auto& cmd = check_size_t(data, size); c->game_data.character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language()); break; } - case GameVersion::BB: + case Version::BB_V4: // Nothing to do... the command is blank; the server generates the guild // card to be sent break; @@ -591,18 +597,18 @@ static void on_word_select_t(shared_ptr c, uint8_t command, uint8_t, con // In non-Ep3 lobbies, Ep3 uses the Ep1&2 word select table. bool is_non_ep3_lobby = (l->episode != Episode::EP3); - QuestScriptVersion from_version = c->quest_version(); - if (is_non_ep3_lobby && (from_version == QuestScriptVersion::GC_EP3)) { - from_version = QuestScriptVersion::GC_V3; + Version from_version = c->version(); + if (is_non_ep3_lobby && is_ep3(from_version)) { + from_version = Version::GC_V3; } for (const auto& lc : target_clients) { try { - QuestScriptVersion lc_version = lc->quest_version(); - if (is_non_ep3_lobby && (lc_version == QuestScriptVersion::GC_EP3)) { - lc_version = QuestScriptVersion::GC_V3; + Version lc_version = lc->version(); + if (is_non_ep3_lobby && is_ep3(lc_version)) { + lc_version = Version::GC_V3; } - if (lc->version() == GameVersion::GC) { + if (is_big_endian(lc->version())) { G_WordSelect_6x74 out_cmd = { cmd.subcommand, cmd.size, cmd.client_id.load(), s->word_select_table->translate(cmd.message, from_version, lc_version)}; @@ -624,7 +630,7 @@ static void on_word_select_t(shared_ptr c, uint8_t command, uint8_t, con } static void on_word_select(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { - if (c->version() == GameVersion::GC) { + if (is_big_endian(c->version())) { on_word_select_t(c, command, flag, data, size); } else { on_word_select_t(c, command, flag, data, size); @@ -646,7 +652,7 @@ static void on_set_player_visible(shared_ptr c, uint8_t command, uint8_t forward_subcommand(c, command, flag, data, size, 0x1F); auto l = c->require_lobby(); - if (!l->is_game() && !c->config.check_flag(Client::Flag::IS_DC_V1)) { + if (!l->is_game() && !is_v1(c->version())) { send_arrow_update(l); } if (!l->is_game() && l->check_flag(Lobby::Flag::IS_OVERFLOW)) { @@ -821,7 +827,7 @@ static void on_player_drop_item(shared_ptr c, uint8_t command, uint8_t f auto l = c->require_lobby(); if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { auto p = c->game_data.character(); - auto item = p->remove_item(cmd.item_id, 0, c->version() != GameVersion::BB); + auto item = p->remove_item(cmd.item_id, 0, c->version() != Version::BB_V4); l->add_item(item, cmd.floor, cmd.x, cmd.z); auto s = c->require_server_state(); @@ -871,7 +877,7 @@ static void on_create_inventory_item_t(shared_ptr c, uint8_t command, ui if ((cmd.header.client_id != c->lobby_client_id)) { return; } - if (c->version() == GameVersion::BB) { + if (c->version() == Version::BB_V4) { // BB should never send this command - inventory items should only be // created by the server in response to shop buy / bank withdraw / etc. reqs return; @@ -917,7 +923,7 @@ static void on_drop_partial_stack_t(shared_ptr c, uint8_t command, uint8 if (!l->is_game()) { return; } - if (l->base_version == GameVersion::BB) { + if (l->base_version == Version::BB_V4) { return; } @@ -956,7 +962,7 @@ static void on_drop_partial_stack(shared_ptr c, uint8_t command, uint8_t static void on_drop_partial_stack_bb(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { auto l = c->require_lobby(); - if (l->base_version == GameVersion::BB) { + if (l->base_version == Version::BB_V4) { const auto& cmd = check_size_t(data, size); if (!l->is_game() || (cmd.header.client_id != c->lobby_client_id)) { @@ -968,7 +974,7 @@ static void on_drop_partial_stack_bb(shared_ptr c, uint8_t command, uint } auto p = c->game_data.character(); - auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != GameVersion::BB); + auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4); // If a stack was split, the original item still exists, so the dropped item // needs a new ID. remove_item signals this by returning an item with an ID @@ -1011,7 +1017,7 @@ static void on_buy_shop_item(shared_ptr c, uint8_t command, uint8_t flag if (!l->is_game() || (cmd.header.client_id != c->lobby_client_id)) { return; } - if (l->base_version == GameVersion::BB) { + if (l->base_version == Version::BB_V4) { return; } @@ -1031,7 +1037,7 @@ static void on_buy_shop_item(shared_ptr c, uint8_t command, uint8_t flag auto name = s->describe_item(c->version(), item, true); send_text_message_printf(c, "$C5BUY %08" PRIX32 "\n%s", item.id.load(), name.c_str()); } - p->remove_meseta(price, c->version() != GameVersion::BB); + p->remove_meseta(price, c->version() != Version::BB_V4); p->print_inventory(stderr, c->version(), s->item_name_index); } @@ -1053,7 +1059,7 @@ static void on_box_or_enemy_item_drop_t(shared_ptr c, uint8_t command, u if (!l->is_game() || (c->lobby_client_id != l->leader_id)) { return; } - if (l->base_version == GameVersion::BB) { + if (l->base_version == Version::BB_V4) { return; } @@ -1103,7 +1109,7 @@ static void on_pick_up_item(shared_ptr c, uint8_t command, uint8_t flag, if (!l->is_game()) { return; } - if (l->base_version == GameVersion::BB) { + if (l->base_version == Version::BB_V4) { // BB clients should never send this; only the server should send this return; } @@ -1178,7 +1184,7 @@ static void on_pick_up_item_request(shared_ptr c, uint8_t command, uint8 l->log.warning("Player %hu requests to pick up %08" PRIX32 ", but the item does not exist; dropping command", cmd.header.client_id.load(), cmd.item_id.load()); - } else if (l->base_version == GameVersion::BB) { + } else if (l->base_version == Version::BB_V4) { // This is handled by the server on BB, and by the leader on other versions. if (!item_tracking_enabled) { throw logic_error("item tracking not enabled in BB game"); @@ -1217,7 +1223,7 @@ static void on_equip_item(shared_ptr c, uint8_t command, uint8_t flag, c auto p = c->game_data.character(); p->inventory.equip_item_id(cmd.item_id, slot); c->log.info("Equipped item %08" PRIX32, cmd.item_id.load()); - } else if (l->base_version == GameVersion::BB) { + } else if (l->base_version == Version::BB_V4) { throw logic_error("item tracking not enabled in BB game"); } @@ -1236,7 +1242,7 @@ static void on_unequip_item(shared_ptr c, uint8_t command, uint8_t flag, auto p = c->game_data.character(); p->inventory.unequip_item_id(cmd.item_id); c->log.info("Unequipped item %08" PRIX32, cmd.item_id.load()); - } else if (l->base_version == GameVersion::BB) { + } else if (l->base_version == Version::BB_V4) { throw logic_error("item tracking not enabled in BB game"); } @@ -1316,7 +1322,7 @@ static void on_feed_mag( // a 6x29 immediately after to destroy the fed item. So on BB, we should // remove the fed item here, but on other versions, we allow the following // 6x29 command to do that. - if (l->base_version == GameVersion::BB) { + if (l->base_version == Version::BB_V4) { p->remove_item(cmd.fed_item_id, 1, false); } @@ -1344,7 +1350,7 @@ static void on_open_shop_bb_or_ep3_battle_subs(shared_ptr c, uint8_t com } else { const auto& cmd = check_size_t(data, size); - if ((l->base_version == GameVersion::BB) && l->is_game()) { + if ((l->base_version == Version::BB_V4) && l->is_game()) { if (!l->item_creator) { throw logic_error("item creator missing from BB game"); } @@ -1376,9 +1382,9 @@ static void on_open_shop_bb_or_ep3_battle_subs(shared_ptr c, uint8_t com static void on_open_bank_bb_or_card_trade_counter_ep3(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { auto l = c->require_lobby(); - if ((l->base_version == GameVersion::BB) && l->is_game()) { + if ((l->base_version == Version::BB_V4) && l->is_game()) { send_bank(c); - } else if ((l->base_version == GameVersion::GC) && l->is_ep3()) { + } else if (l->is_ep3()) { forward_subcommand(c, command, flag, data, size); } } @@ -1386,7 +1392,7 @@ static void on_open_bank_bb_or_card_trade_counter_ep3(shared_ptr c, uint static void on_ep3_private_word_select_bb_bank_action(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { auto s = c->require_server_state(); auto l = c->require_lobby(); - if (l->base_version == GameVersion::BB) { + if (l->base_version == Version::BB_V4) { const auto& cmd = check_size_t(data, size); if (!l->is_game()) { @@ -1414,11 +1420,11 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr c, uint } } else { // Deposit item - auto item = p->remove_item(cmd.item_id, cmd.item_amount, c->version() != GameVersion::BB); + auto item = p->remove_item(cmd.item_id, cmd.item_amount, c->version() != Version::BB_V4); p->bank.add_item(item); send_destroy_item(c, cmd.item_id, cmd.item_amount); - string name = s->item_name_index->describe_item(GameVersion::BB, item); + string name = s->item_name_index->describe_item(Version::BB_V4, item); l->log.info("Player %hu deposited item %08" PRIX32 " (x%hhu) (%s) in the bank", c->lobby_client_id, cmd.item_id.load(), cmd.item_amount, name.c_str()); c->game_data.character()->print_inventory(stderr, c->version(), s->item_name_index); @@ -1445,21 +1451,21 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr c, uint p->add_item(item); send_create_inventory_item(c, item); - string name = s->item_name_index->describe_item(GameVersion::BB, item); + string name = s->item_name_index->describe_item(Version::BB_V4, item); l->log.info("Player %hu withdrew item %08" PRIX32 " (x%hhu) (%s) from the bank", c->lobby_client_id, cmd.item_id.load(), cmd.item_amount, name.c_str()); c->game_data.character()->print_inventory(stderr, c->version(), s->item_name_index); } } - } else if ((c->version() == GameVersion::GC) && c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + } else if (is_ep3(c->version())) { forward_subcommand(c, command, flag, data, size); } } static void on_sort_inventory_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { auto l = c->require_lobby(); - if (l->base_version == GameVersion::BB) { + if (l->base_version == Version::BB_V4) { const auto& cmd = check_size_t(data, size); if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { @@ -1661,7 +1667,8 @@ static void on_set_quest_flag(shared_ptr c, uint8_t command, uint8_t fla } uint16_t flag_index, difficulty, action; - if (c->version() == GameVersion::DC || c->version() == GameVersion::PC) { + // TODO: Which format does GC NTE use? + if (is_v1_or_v2(c->version())) { const auto& cmd = check_size_t(data, size); flag_index = cmd.flag; action = cmd.action; @@ -1692,7 +1699,7 @@ static void on_set_quest_flag(shared_ptr c, uint8_t command, uint8_t fla forward_subcommand(c, command, flag, data, size); - if (c->version() == GameVersion::GC) { + if (is_v3(c->version())) { bool should_send_boss_drop_req = false; bool is_ep2 = (l->episode == Episode::EP2); if ((l->episode == Episode::EP1) && (c->floor == 0x0E)) { @@ -1742,10 +1749,10 @@ static void on_dragon_actions(shared_ptr c, uint8_t command, uint8_t, co G_DragonBossActions_GC_6x12 sw_cmd = {{{cmd.header.subcommand, cmd.header.size, cmd.header.enemy_id}, cmd.unknown_a2, cmd.unknown_a3, cmd.unknown_a4, cmd.x.load(), cmd.z.load()}}; - bool sender_is_gc = (c->version() == GameVersion::GC); + bool sender_is_gc = is_big_endian(c->version()); for (auto lc : l->clients) { if (lc && (lc != c)) { - if ((lc->version() == GameVersion::GC) == sender_is_gc) { + if (is_big_endian(lc->version()) == sender_is_gc) { send_command_t(lc, 0x60, 0x00, cmd); } else { send_command_t(lc, 0x60, 0x00, sw_cmd); @@ -1773,10 +1780,10 @@ static void on_gol_dragon_actions(shared_ptr c, uint8_t command, uint8_t cmd.z.load(), cmd.unknown_a5, 0}}; - bool sender_is_gc = (c->version() == GameVersion::GC); + bool sender_is_gc = is_big_endian(c->version()); for (auto lc : l->clients) { if (lc && (lc != c)) { - if ((lc->version() == GameVersion::GC) == sender_is_gc) { + if (is_big_endian(lc->version()) == sender_is_gc) { send_command_t(lc, 0x60, 0x00, cmd); } else { send_command_t(lc, 0x60, 0x00, sw_cmd); @@ -1796,7 +1803,7 @@ static void on_enemy_hit(shared_ptr c, uint8_t command, uint8_t, const v return; } - if (l->base_version == GameVersion::BB) { + if (l->base_version == Version::BB_V4) { if (c->lobby_client_id > 3) { throw logic_error("client ID is above 3"); } @@ -1816,10 +1823,10 @@ static void on_enemy_hit(shared_ptr c, uint8_t command, uint8_t, const v } G_EnemyHitByPlayer_GC_6x0A sw_cmd = {{{cmd.header.subcommand, cmd.header.size, cmd.header.enemy_id}, cmd.enemy_index, cmd.remaining_hp, cmd.flags.load()}}; - bool sender_is_gc = (c->version() == GameVersion::GC); + bool sender_is_gc = is_big_endian(c->version()); for (auto lc : l->clients) { if (lc && (lc != c)) { - if ((lc->version() == GameVersion::GC) == sender_is_gc) { + if (is_big_endian(lc->version()) == sender_is_gc) { send_command_t(lc, 0x60, 0x00, cmd); } else { send_command_t(lc, 0x60, 0x00, sw_cmd); @@ -1830,7 +1837,7 @@ static void on_enemy_hit(shared_ptr c, uint8_t command, uint8_t, const v static void on_charge_attack_bb(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { auto l = c->require_lobby(); - if (l->base_version != GameVersion::BB) { + if (l->base_version != Version::BB_V4) { throw runtime_error("BB-only command sent in non-BB game"); } @@ -1893,7 +1900,7 @@ static void on_steal_exp_bb(shared_ptr c, uint8_t, uint8_t, const void* auto s = c->require_server_state(); auto l = c->require_lobby(); - if (l->base_version != GameVersion::BB) { + if (l->base_version != Version::BB_V4) { throw runtime_error("BB-only command sent in non-BB game"); } if (!l->map) { @@ -1942,7 +1949,7 @@ static void on_enemy_killed_bb(shared_ptr c, uint8_t, uint8_t, const voi auto s = c->require_server_state(); auto l = c->require_lobby(); - if (l->base_version != GameVersion::BB) { + if (l->base_version != Version::BB_V4) { throw runtime_error("BB-only command sent in non-BB game"); } @@ -2082,7 +2089,7 @@ static void on_destroy_inventory_item(shared_ptr c, uint8_t command, uin if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { auto s = c->require_server_state(); auto p = c->game_data.character(); - auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != GameVersion::BB); + auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4); auto name = s->describe_item(c->version(), item, false); l->log.info("Player %hhu destroyed inventory item %hu:%08" PRIX32 " (%s)", c->lobby_client_id, cmd.header.client_id.load(), cmd.item_id.load(), name.c_str()); @@ -2121,7 +2128,7 @@ static void on_destroy_ground_item(shared_ptr c, uint8_t command, uint8_ static void on_identify_item_bb(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { auto l = c->require_lobby(); - if (l->base_version == GameVersion::BB) { + if (l->base_version == Version::BB_V4) { const auto& cmd = check_size_t(data, size); if (!l->is_game()) { return; @@ -2157,7 +2164,7 @@ static void on_identify_item_bb(shared_ptr c, uint8_t command, uint8_t f static void on_accept_identify_item_bb(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { auto l = c->require_lobby(); - if (l->base_version == GameVersion::BB) { + if (l->base_version == Version::BB_V4) { const auto& cmd = check_size_t(data, size); if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { @@ -2182,7 +2189,7 @@ static void on_accept_identify_item_bb(shared_ptr c, uint8_t command, ui static void on_sell_item_at_shop_bb(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { auto s = c->require_server_state(); auto l = c->require_lobby(); - if (l->base_version == GameVersion::BB) { + if (l->base_version == Version::BB_V4) { const auto& cmd = check_size_t(data, size); if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { @@ -2191,7 +2198,7 @@ static void on_sell_item_at_shop_bb(shared_ptr c, uint8_t command, uint8 auto s = c->require_server_state(); auto p = c->game_data.character(); - auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != GameVersion::BB); + auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4); size_t price = (s->item_parameter_table_for_version(c->version())->price_for_item(item) >> 3) * cmd.amount; p->add_meseta(price); @@ -2211,7 +2218,7 @@ static void on_sell_item_at_shop_bb(shared_ptr c, uint8_t command, uint8 static void on_buy_shop_item_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { auto l = c->require_lobby(); - if (l->base_version == GameVersion::BB) { + if (l->base_version == Version::BB_V4) { const auto& cmd = check_size_t(data, size); if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { throw logic_error("item tracking not enabled in BB game"); @@ -2249,7 +2256,7 @@ static void on_buy_shop_item_bb(shared_ptr c, uint8_t, uint8_t, const vo static void on_medical_center_bb(shared_ptr c, uint8_t, uint8_t, const void*, size_t) { auto l = c->require_lobby(); - if (l->is_game() && (l->base_version == GameVersion::BB)) { + if (l->is_game() && (l->base_version == Version::BB_V4)) { c->game_data.character()->remove_meseta(10, false); } } @@ -2258,7 +2265,7 @@ static void on_battle_restart_bb(shared_ptr c, uint8_t, uint8_t, const v auto s = c->require_server_state(); auto l = c->require_lobby(); if (l->is_game() && - (l->base_version == GameVersion::BB) && + (l->base_version == Version::BB_V4) && (l->mode == GameMode::BATTLE) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS) && l->leader_id == c->lobby_client_id) { @@ -2283,7 +2290,7 @@ static void on_battle_restart_bb(shared_ptr c, uint8_t, uint8_t, const v static void on_battle_level_up_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { auto l = c->require_lobby(); if (l->is_game() && - (l->base_version == GameVersion::BB) && + (l->base_version == Version::BB_V4) && (l->mode == GameMode::BATTLE) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { const auto& cmd = check_size_t(data, size); @@ -2303,7 +2310,7 @@ static void on_battle_level_up_bb(shared_ptr c, uint8_t, uint8_t, const static void on_request_challenge_grave_recovery_item_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { auto l = c->require_lobby(); if (l->is_game() && - (l->base_version == GameVersion::BB) && + (l->base_version == Version::BB_V4) && (l->mode == GameMode::CHALLENGE) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { const auto& cmd = check_size_t(data, size); @@ -2325,7 +2332,7 @@ static void on_request_challenge_grave_recovery_item_bb(shared_ptr c, ui static void on_quest_exchange_item_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { auto l = c->require_lobby(); if (l->is_game() && - (l->base_version == GameVersion::BB) && + (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { const auto& cmd = check_size_t(data, size); @@ -2354,7 +2361,7 @@ static void on_quest_exchange_item_bb(shared_ptr c, uint8_t, uint8_t, co static void on_wrap_item_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { auto l = c->require_lobby(); - if (l->is_game() && (l->base_version == GameVersion::BB)) { + if (l->is_game() && (l->base_version == Version::BB_V4)) { const auto& cmd = check_size_t(data, size); auto p = c->game_data.character(); @@ -2368,7 +2375,7 @@ static void on_wrap_item_bb(shared_ptr c, uint8_t, uint8_t, const void* static void on_photon_drop_exchange_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { auto l = c->require_lobby(); - if (l->is_game() && (l->base_version == GameVersion::BB)) { + if (l->is_game() && (l->base_version == Version::BB_V4)) { const auto& cmd = check_size_t(data, size); try { @@ -2397,7 +2404,7 @@ static void on_photon_drop_exchange_bb(shared_ptr c, uint8_t, uint8_t, c static void on_secret_lottery_ticket_exchange_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { auto s = c->require_server_state(); auto l = c->require_lobby(); - if (l->is_game() && (l->base_version == GameVersion::BB) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { + if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { const auto& cmd = check_size_t(data, size); if (s->secret_lottery_results.empty()) { @@ -2451,7 +2458,7 @@ static void on_secret_lottery_ticket_exchange_bb(shared_ptr c, uint8_t, static void on_photon_crystal_exchange_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { auto l = c->require_lobby(); - if (l->is_game() && (l->base_version == GameVersion::BB) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { + if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { check_size_t(data, size); auto p = c->game_data.character(); size_t index = p->inventory.find_item_by_primary_identifier(0x031002); @@ -2463,7 +2470,7 @@ static void on_photon_crystal_exchange_bb(shared_ptr c, uint8_t, uint8_t static void on_quest_F95E_result_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { auto l = c->require_lobby(); - if (l->is_game() && (l->base_version == GameVersion::BB) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { + if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { const auto& cmd = check_size_t(data, size); auto s = c->require_server_state(); @@ -2493,7 +2500,7 @@ static void on_quest_F95E_result_bb(shared_ptr c, uint8_t, uint8_t, cons static void on_quest_F95F_result_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { auto l = c->require_lobby(); - if (l->is_game() && (l->base_version == GameVersion::BB) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { + if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { const auto& cmd = check_size_t(data, size); auto s = c->require_server_state(); auto p = c->game_data.character(); @@ -2532,7 +2539,7 @@ static void on_quest_F95F_result_bb(shared_ptr c, uint8_t, uint8_t, cons static void on_momoka_item_exchange_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { auto l = c->require_lobby(); - if (l->is_game() && (l->base_version == GameVersion::BB) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { + if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { const auto& cmd = check_size_t(data, size); auto p = c->game_data.character(); try { @@ -2561,7 +2568,7 @@ static void on_momoka_item_exchange_bb(shared_ptr c, uint8_t, uint8_t, c static void on_upgrade_weapon_attribute_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { auto l = c->require_lobby(); - if (l->is_game() && (l->base_version == GameVersion::BB) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { + if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { const auto& cmd = check_size_t(data, size); auto p = c->game_data.character(); try { @@ -2668,7 +2675,7 @@ static void handle_subcommand_dc_nte(shared_ptr c, uint8_t command, uint continue; } - if (lc->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) { + if (lc->version() == Version::DC_NTE) { send_command(lc, command, flag, data, size); } else if (non_nte_subcommand != 0x00) { if (non_nte_data.empty()) { @@ -2970,7 +2977,7 @@ void on_subcommand_multi(shared_ptr c, uint8_t command, uint8_t flag, co } const void* data = r.getv(size); - if (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) { + if (c->version() == Version::DC_NTE) { handle_subcommand_dc_nte(c, command, flag, data, size); } else { auto fn = subcommand_handlers[header.subcommand]; diff --git a/src/ReplaySession.cc b/src/ReplaySession.cc index 789f28e5..e09248c4 100644 --- a/src/ReplaySession.cc +++ b/src/ReplaySession.cc @@ -39,7 +39,7 @@ string ReplaySession::Event::str() const { } ReplaySession::Client::Client( - ReplaySession* session, uint64_t id, uint16_t port, GameVersion version) + ReplaySession* session, uint64_t id, uint16_t port, Version version) : id(id), port(port), version(version), @@ -53,7 +53,7 @@ ReplaySession::Client::Client( string ReplaySession::Client::str() const { return string_printf("Client[%" PRIu64 ", T-%hu, %s]", - this->id, this->port, name_for_version(this->version)); + this->id, this->port, name_for_enum(this->version)); } shared_ptr ReplaySession::create_event( @@ -107,18 +107,20 @@ void ReplaySession::check_for_password(shared_ptr ev) const { } }; - const void* cmd_data = ev->data.data() + ((version == GameVersion::BB) ? 8 : 4); - size_t cmd_size = ev->data.size() - ((version == GameVersion::BB) ? 8 : 4); + const void* cmd_data = ev->data.data() + ((version == Version::BB_V4) ? 8 : 4); + size_t cmd_size = ev->data.size() - ((version == Version::BB_V4) ? 8 : 4); switch (version) { - case GameVersion::PATCH: { + case Version::PC_PATCH: + case Version::BB_PATCH: { const auto& header = check_size_t(ev->data, 0xFFFF); if (header.command == 0x04) { check_either(check_size_t(cmd_data, cmd_size).password.decode()); } break; } - case GameVersion::PC: { + + case Version::PC_V2: { const auto& header = check_size_t(ev->data, 0xFFFF); if (header.command == 0x03) { check_ak(check_size_t(cmd_data, cmd_size).access_key2.decode()); @@ -142,9 +144,16 @@ void ReplaySession::check_for_password(shared_ptr ev) const { } break; } - case GameVersion::DC: - case GameVersion::GC: - case GameVersion::XB: { + + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + case Version::XB_V3: { const auto& header = check_size_t(ev->data, 0xFFFF); if (header.command == 0x03) { check_ak(check_size_t(cmd_data, cmd_size).access_key2.decode()); @@ -170,7 +179,7 @@ void ReplaySession::check_for_password(shared_ptr ev) const { check_ak(cmd.access_key.decode()); check_ak(cmd.access_key2.decode()); } else if (header.command == 0x9E) { - if (version == GameVersion::GC) { + if (is_gc(version)) { const auto& cmd = check_size_t(cmd_data, cmd_size, sizeof(C_LoginExtended_GC_9E)); check_ak(cmd.access_key.decode()); check_ak(cmd.access_key2.decode()); @@ -187,7 +196,8 @@ void ReplaySession::check_for_password(shared_ptr ev) const { } break; } - case GameVersion::BB: { + + case Version::BB_V4: { const auto& header = check_size_t(ev->data, 0xFFFF); if (header.command == 0x04) { check_pw(check_size_t(cmd_data, cmd_size).password.decode()); @@ -202,6 +212,7 @@ void ReplaySession::check_for_password(shared_ptr ev) const { } break; } + default: throw logic_error("invalid game version"); } @@ -210,13 +221,14 @@ void ReplaySession::check_for_password(shared_ptr ev) const { void ReplaySession::apply_default_mask(shared_ptr ev) { auto version = this->clients.at(ev->client_id)->version; - void* cmd_data = ev->data.data() + ((version == GameVersion::BB) ? 8 : 4); - size_t cmd_size = ev->data.size() - ((version == GameVersion::BB) ? 8 : 4); - void* mask_data = ev->mask.data() + ((version == GameVersion::BB) ? 8 : 4); - size_t mask_size = ev->mask.size() - ((version == GameVersion::BB) ? 8 : 4); + void* cmd_data = ev->data.data() + ((version == Version::BB_V4) ? 8 : 4); + size_t cmd_size = ev->data.size() - ((version == Version::BB_V4) ? 8 : 4); + void* mask_data = ev->mask.data() + ((version == Version::BB_V4) ? 8 : 4); + size_t mask_size = ev->mask.size() - ((version == Version::BB_V4) ? 8 : 4); switch (version) { - case GameVersion::PATCH: { + case Version::PC_PATCH: + case Version::BB_PATCH: { const auto& header = check_size_t(ev->data, 0xFFFF); if (header.command == 0x02) { auto& cmd_mask = check_size_t(mask_data, mask_size); @@ -225,12 +237,18 @@ void ReplaySession::apply_default_mask(shared_ptr ev) { } break; } - case GameVersion::DC: - case GameVersion::PC: - case GameVersion::GC: - case GameVersion::XB: { + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::PC_V2: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + case Version::XB_V3: { uint8_t command; - if (version == GameVersion::PC) { + if (version == Version::PC_V2) { command = check_size_t(ev->data, 0xFFFF).command; } else { // V3 command = check_size_t(ev->data, 0xFFFF).command; @@ -257,27 +275,24 @@ void ReplaySession::apply_default_mask(shared_ptr ev) { } break; case 0x41: - if (version == GameVersion::PC) { + if (version == Version::PC_V2) { auto& mask = check_size_t(mask_data, mask_size); mask.reconnect_command.address = 0; - } else if (version == GameVersion::BB) { - auto& mask = check_size_t(mask_data, mask_size); - mask.reconnect_command.address = 0; } else { // V3 auto& mask = check_size_t(mask_data, mask_size); mask.reconnect_command.address = 0; } break; case 0x64: - if (version == GameVersion::PC) { + if (version == Version::PC_V2) { auto& mask = check_size_t(mask_data, mask_size); mask.variations.clear(0); mask.rare_seed = 0; - } else if (version == GameVersion::XB) { + } else if (version == Version::XB_V3) { auto& mask = check_size_t(mask_data, mask_size); mask.variations.clear(0); mask.rare_seed = 0; - } else if (version == GameVersion::DC || version == GameVersion::GC) { + } else if (version != Version::DC_NTE && version != Version::DC_V1_12_2000_PROTOTYPE) { auto& mask = check_size_t(mask_data, mask_size, sizeof(S_JoinGame_GC_Ep3_64)); mask.variations.clear(0); mask.rare_seed = 0; @@ -290,45 +305,45 @@ void ReplaySession::apply_default_mask(shared_ptr ev) { } break; case 0xEB: - if (version != GameVersion::GC) { + if (!is_gc(version)) { break; } [[fallthrough]]; case 0x65: case 0x67: case 0x68: - if ((version == GameVersion::DC) || (version == GameVersion::GC)) { - for (size_t offset = offsetof(S_JoinLobby_DC_GC_65_67_68_Ep3_EB, entries) + - offsetof(S_JoinLobby_DC_GC_65_67_68_Ep3_EB::Entry, disp.visual.name_color_checksum); - offset + 4 <= mask_size; - offset += sizeof(S_JoinLobby_DC_GC_65_67_68_Ep3_EB::Entry)) { - *reinterpret_cast(reinterpret_cast(mask_data) + offset) = 0; - } - } else if (version == GameVersion::XB) { - for (size_t offset = offsetof(S_JoinLobby_XB_65_67_68, entries) + - offsetof(S_JoinLobby_XB_65_67_68::Entry, disp.visual.name_color_checksum); - offset + 4 <= mask_size; - offset += sizeof(S_JoinLobby_XB_65_67_68::Entry)) { - *reinterpret_cast(reinterpret_cast(mask_data) + offset) = 0; - } - } else if (version == GameVersion::PC) { + if (version == Version::PC_V2) { for (size_t offset = offsetof(S_JoinLobby_PC_65_67_68, entries) + offsetof(S_JoinLobby_PC_65_67_68::Entry, disp.visual.name_color_checksum); offset + 4 <= mask_size; offset += sizeof(S_JoinLobby_PC_65_67_68::Entry)) { *reinterpret_cast(reinterpret_cast(mask_data) + offset) = 0; } - } else if (version == GameVersion::BB) { - for (size_t offset = offsetof(S_JoinLobby_BB_65_67_68, entries) + - offsetof(S_JoinLobby_BB_65_67_68::Entry, disp.visual.name_color_checksum); + } else if (version == Version::XB_V3) { + for (size_t offset = offsetof(S_JoinLobby_XB_65_67_68, entries) + + offsetof(S_JoinLobby_XB_65_67_68::Entry, disp.visual.name_color_checksum); offset + 4 <= mask_size; - offset += sizeof(S_JoinLobby_BB_65_67_68::Entry)) { + offset += sizeof(S_JoinLobby_XB_65_67_68::Entry)) { + *reinterpret_cast(reinterpret_cast(mask_data) + offset) = 0; + } + } else if (version == Version::DC_NTE) { + for (size_t offset = offsetof(S_JoinLobby_DCNTE_65_67_68, entries) + + offsetof(S_JoinLobby_DCNTE_65_67_68::Entry, disp.visual.name_color_checksum); + offset + 4 <= mask_size; + offset += sizeof(S_JoinLobby_DCNTE_65_67_68::Entry)) { + *reinterpret_cast(reinterpret_cast(mask_data) + offset) = 0; + } + } else { + for (size_t offset = offsetof(S_JoinLobby_DC_GC_65_67_68_Ep3_EB, entries) + + offsetof(S_JoinLobby_DC_GC_65_67_68_Ep3_EB::Entry, disp.visual.name_color_checksum); + offset + 4 <= mask_size; + offset += sizeof(S_JoinLobby_DC_GC_65_67_68_Ep3_EB::Entry)) { *reinterpret_cast(reinterpret_cast(mask_data) + offset) = 0; } } break; case 0xE8: - if (version == GameVersion::GC) { + if (is_gc(version)) { auto& mask = check_size_t(mask_data, mask_size); mask.rare_seed = 0; for (size_t z = 0; z < 4; z++) { @@ -354,7 +369,7 @@ void ReplaySession::apply_default_mask(shared_ptr ev) { } break; case 0x6C: - if (version == GameVersion::GC && mask_size >= 0x14) { + if (is_gc(version) && mask_size >= 0x14) { const auto& cmd = check_size_t(cmd_data, cmd_size, 0xFFFF); if ((cmd.header.header.basic_header.subcommand == 0xB6) && (cmd.header.subsubcommand == 0x40)) { @@ -372,7 +387,7 @@ void ReplaySession::apply_default_mask(shared_ptr ev) { } break; } - case GameVersion::BB: { + case Version::BB_V4: { uint16_t command = check_size_t(ev->data, 0xFFFF).command; switch (command) { case 0x0003: { @@ -481,7 +496,7 @@ ReplaySession::ReplaySession( this, stoull(tokens[8].substr(2), nullptr, 16), stoul(listen_tokens[1], nullptr, 10), - version_for_name(listen_tokens[2].c_str()))); + enum_for_name(listen_tokens[2].c_str()))); if (!this->clients.emplace(c->id, c).second) { throw runtime_error(string_printf("(ev-line %zu) duplicate client ID in input log", line_num)); } @@ -701,20 +716,27 @@ void ReplaySession::on_command_received( // If the command is an encryption init, set up encryption on the channel switch (c->version) { - case GameVersion::PATCH: + case Version::PC_PATCH: + case Version::BB_PATCH: if (command == 0x02) { auto& cmd = check_size_t(data); c->channel.crypt_in.reset(new PSOV2Encryption(cmd.server_key)); c->channel.crypt_out.reset(new PSOV2Encryption(cmd.client_key)); } break; - case GameVersion::DC: - case GameVersion::PC: - case GameVersion::GC: - case GameVersion::XB: + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::PC_V2: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + case Version::XB_V3: if (command == 0x02 || command == 0x17 || command == 0x91 || command == 0x9B) { auto& cmd = check_size_t(data, 0xFFFF); - if ((c->version == GameVersion::DC) || (c->version == GameVersion::PC)) { + if (is_v1_or_v2(c->version)) { c->channel.crypt_in.reset(new PSOV2Encryption(cmd.server_key)); c->channel.crypt_out.reset(new PSOV2Encryption(cmd.client_key)); } else { // V3 @@ -723,7 +745,7 @@ void ReplaySession::on_command_received( } } break; - case GameVersion::BB: + case Version::BB_V4: if (command == 0x03 || command == 0x9B) { auto& cmd = check_size_t(data, 0xFFFF); // TODO: At some point it may matter which BB private key file we use. diff --git a/src/ReplaySession.hh b/src/ReplaySession.hh index cc7274a1..66e0261d 100644 --- a/src/ReplaySession.hh +++ b/src/ReplaySession.hh @@ -53,12 +53,12 @@ private: struct Client { uint64_t id; uint16_t port; - GameVersion version; + Version version; Channel channel; std::deque> receive_events; std::shared_ptr disconnect_event; - Client(ReplaySession* session, uint64_t id, uint16_t port, GameVersion version); + Client(ReplaySession* session, uint64_t id, uint16_t port, Version version); std::string str() const; }; diff --git a/src/SaveFileFormats.cc b/src/SaveFileFormats.cc index db10e886..fec659c8 100644 --- a/src/SaveFileFormats.cc +++ b/src/SaveFileFormats.cc @@ -562,7 +562,7 @@ void PSOBBCharacterFile::clear_all_material_usage() { } } -void PSOBBCharacterFile::print_inventory(FILE* stream, GameVersion version, shared_ptr name_index) const { +void PSOBBCharacterFile::print_inventory(FILE* stream, Version version, shared_ptr name_index) const { fprintf(stream, "[PlayerInventory] Meseta: %" PRIu32 "\n", this->disp.stats.meseta.load()); fprintf(stream, "[PlayerInventory] %hhu items\n", this->inventory.num_items); for (size_t x = 0; x < this->inventory.num_items; x++) { diff --git a/src/SaveFileFormats.hh b/src/SaveFileFormats.hh index 2376c132..ac233d11 100644 --- a/src/SaveFileFormats.hh +++ b/src/SaveFileFormats.hh @@ -246,7 +246,7 @@ struct PSOBBCharacterFile { void set_material_usage(MaterialType which, uint8_t usage); void clear_all_material_usage(); - void print_inventory(FILE* stream, GameVersion version, std::shared_ptr name_index) const; + void print_inventory(FILE* stream, Version version, std::shared_ptr name_index) const; } __attribute__((packed)); struct PSOBBGuildCardFile { diff --git a/src/SendCommands.cc b/src/SendCommands.cc index dd58db2d..5fac6fa7 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -111,16 +111,23 @@ void send_command_with_header_t(Channel& ch, const void* data, size_t size) { void send_command_with_header(Channel& ch, const void* data, size_t size) { switch (ch.version) { - case GameVersion::DC: - case GameVersion::GC: - case GameVersion::XB: - send_command_with_header_t(ch, data, size); - break; - case GameVersion::PC: - case GameVersion::PATCH: + case Version::PC_PATCH: + case Version::BB_PATCH: + case Version::PC_V2: send_command_with_header_t(ch, data, size); break; - case GameVersion::BB: + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + case Version::XB_V3: + send_command_with_header_t(ch, data, size); + break; + case Version::BB_V4: send_command_with_header_t(ch, data, size); break; default: @@ -153,24 +160,29 @@ void send_server_init_dc_pc_v3(shared_ptr c, uint8_t flags) { uint32_t server_key = random_object(); uint32_t client_key = random_object(); - auto cmd = prepare_server_init_contents_console( - server_key, client_key, initial_connection); + auto cmd = prepare_server_init_contents_console(server_key, client_key, initial_connection); send_command_t(c, command, 0x00, cmd); switch (c->version()) { - case GameVersion::PC: + case Version::PC_V2: c->channel.crypt_in.reset(new PSOV2Encryption(client_key)); c->channel.crypt_out.reset(new PSOV2Encryption(server_key)); break; - case GameVersion::DC: - case GameVersion::GC: { + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: { shared_ptr det_crypt(new PSOV2OrV3DetectorEncryption( client_key, v2_crypt_initial_client_commands, v3_crypt_initial_client_commands)); c->channel.crypt_in = det_crypt; c->channel.crypt_out.reset(new PSOV2OrV3ImitatorEncryption(server_key, det_crypt)); break; } - case GameVersion::XB: + case Version::XB_V3: c->channel.crypt_in.reset(new PSOV3Encryption(client_key)); c->channel.crypt_out.reset(new PSOV3Encryption(server_key)); break; @@ -231,16 +243,23 @@ void send_server_init_patch(shared_ptr c) { void send_server_init(shared_ptr c, uint8_t flags) { switch (c->version()) { - case GameVersion::DC: - case GameVersion::PC: - case GameVersion::GC: - case GameVersion::XB: + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::PC_V2: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + case Version::XB_V3: send_server_init_dc_pc_v3(c, flags); break; - case GameVersion::PATCH: + case Version::PC_PATCH: + case Version::BB_PATCH: send_server_init_patch(c); break; - case GameVersion::BB: + case Version::BB_V4: send_server_init_bb(c, flags); break; default: @@ -250,8 +269,11 @@ void send_server_init(shared_ptr c, uint8_t flags) { void send_update_client_config(shared_ptr c) { switch (c->version()) { - case GameVersion::DC: - case GameVersion::PC: { + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::PC_V2: { if (!c->config.check_flag(Client::Flag::HAS_GUILD_CARD_NUMBER)) { c->config.set_flag(Client::Flag::HAS_GUILD_CARD_NUMBER); S_UpdateClientConfig_DC_PC_04 cmd; @@ -261,8 +283,11 @@ void send_update_client_config(shared_ptr c) { } break; } - case GameVersion::GC: - case GameVersion::XB: { + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + case Version::XB_V3: { c->config.set_flag(Client::Flag::HAS_GUILD_CARD_NUMBER); S_UpdateClientConfig_V3_04 cmd; cmd.player_tag = 0x00010000; @@ -312,8 +337,8 @@ void prepare_client_for_patches(shared_ptr c, std::function on_c if (!c) { return; } - if (c->version() == GameVersion::GC && - c->config.specific_version == default_specific_version_for_version(GameVersion::GC, -1)) { + if (is_gc(c->version()) && + c->config.specific_version == default_specific_version_for_version(c->version(), -1)) { send_function_call(c, s->function_code_index->name_to_function.at("VersionDetect")); c->function_call_response_queue.emplace_back([wc = weak_ptr(c), on_complete](uint32_t specific_version, uint32_t) -> void { auto c = wc.lock(); @@ -441,7 +466,7 @@ void send_function_call( void send_reconnect(shared_ptr c, uint32_t address, uint16_t port) { S_Reconnect_19 cmd = {{address, port, 0}}; - send_command_t(c, (c->version() == GameVersion::PATCH) ? 0x14 : 0x19, 0x00, cmd); + send_command_t(c, is_patch(c->version()) ? 0x14 : 0x19, 0x00, cmd); } void send_pc_console_split_reconnect(shared_ptr c, uint32_t address, @@ -661,7 +686,10 @@ static void send_text( uint16_t command, const string& text, ColorMode color_mode) { - bool is_w = (ch.version == GameVersion::PC || ch.version == GameVersion::BB || ch.version == GameVersion::PATCH); + bool is_w = uses_utf16(ch.version); + if (ch.version == Version::DC_NTE) { + color_mode = ColorMode::STRIP; + } switch (color_mode) { case ColorMode::NONE: @@ -701,22 +729,29 @@ static void send_header_text(Channel& ch, uint16_t command, uint32_t guild_card_ void send_message_box(shared_ptr c, const string& text) { uint16_t command; switch (c->version()) { - case GameVersion::PATCH: + case Version::PC_PATCH: + case Version::BB_PATCH: command = 0x13; break; - case GameVersion::DC: - case GameVersion::PC: + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::PC_V2: command = 0x1A; break; - case GameVersion::GC: - case GameVersion::XB: - case GameVersion::BB: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + case Version::XB_V3: + case Version::BB_V4: command = 0xD5; break; default: throw logic_error("invalid game version"); } - send_text(c->channel, command, text, c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION) ? ColorMode::STRIP : ColorMode::ADD); + send_text(c->channel, command, text, ColorMode::ADD); } void send_ep3_timed_message_box(Channel& ch, uint32_t frames, const string& message) { @@ -736,18 +771,16 @@ void send_lobby_name(shared_ptr c, const string& text) { } void send_quest_info(shared_ptr c, const string& text, bool is_download_quest) { - send_text( - c->channel, is_download_quest ? 0xA5 : 0xA3, text, - c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION) ? ColorMode::STRIP : ColorMode::ADD); + send_text(c->channel, is_download_quest ? 0xA5 : 0xA3, text, ColorMode::ADD); } void send_lobby_message_box(shared_ptr c, const string& text, bool left_side_on_bb) { - uint16_t command = (left_side_on_bb && (c->version() == GameVersion::BB)) ? 0x0101 : 0x0001; - send_header_text(c->channel, command, 0, text, c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION) ? ColorMode::STRIP : ColorMode::ADD); + uint16_t command = (left_side_on_bb && (c->version() == Version::BB_V4)) ? 0x0101 : 0x0001; + send_header_text(c->channel, command, 0, text, ColorMode::ADD); } void send_ship_info(shared_ptr c, const string& text) { - send_header_text(c->channel, 0x11, 0, text, c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION) ? ColorMode::STRIP : ColorMode::ADD); + send_header_text(c->channel, 0x11, 0, text, ColorMode::ADD); } void send_ship_info(Channel& ch, const string& text) { @@ -759,7 +792,7 @@ void send_text_message(Channel& ch, const string& text) { } void send_text_message(shared_ptr c, const string& text) { - if (!c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) { + if (c->version() != Version::DC_NTE) { send_header_text(c->channel, 0xB0, 0, text, ColorMode::ADD); } } @@ -787,7 +820,7 @@ __attribute__((format(printf, 2, 3))) void send_ep3_text_message_printf(shared_p va_end(va); for (auto& it : s->id_to_lobby) { for (auto& c : it.second->clients) { - if (c && c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (c && is_ep3(c->version())) { send_text_message(c, buf); } } @@ -795,8 +828,7 @@ __attribute__((format(printf, 2, 3))) void send_ep3_text_message_printf(shared_p } string prepare_chat_data( - GameVersion version, - bool is_nte, + Version version, uint8_t language, uint8_t from_client_id, const string& from_name, @@ -804,11 +836,11 @@ string prepare_chat_data( char private_flags) { string data; - if (version == GameVersion::BB) { + if (version == Version::BB_V4) { data.append("\tJ"); } data.append(from_name); - if (is_nte) { + if (version == Version::DC_NTE) { data.append(string_printf(">%X", from_client_id)); } else { data.append(1, '\t'); @@ -817,11 +849,11 @@ string prepare_chat_data( data.append(1, static_cast(private_flags)); } - if ((version == GameVersion::BB) || (version == GameVersion::PC)) { + if (uses_utf16(version)) { data.append(language ? "\tE" : "\tJ"); data.append(text); return tt_utf8_to_utf16(data); - } else if (is_nte) { + } else if (version == Version::DC_NTE) { data.append(tt_utf8_to_sjis(text)); return data; } else { @@ -832,7 +864,7 @@ string prepare_chat_data( void send_chat_message_from_client(Channel& ch, const string& text, char private_flags) { if (private_flags != 0) { - if (ch.version != GameVersion::GC) { + if (!is_ep3(ch.version)) { throw runtime_error("nonzero private_flags in non-GC chat message"); } string effective_text; @@ -849,7 +881,7 @@ void send_prepared_chat_message(shared_ptr c, uint32_t from_guild_card_n w.put(SC_TextHeader_01_06_11_B0_EE{0, from_guild_card_number}); w.write(prepared_data); w.put_u8(0); - if ((c->version() == GameVersion::BB) || (c->version() == GameVersion::PC)) { + if (uses_utf16(c->version())) { w.put_u8(0); } while (w.size() & 3) { @@ -869,7 +901,6 @@ void send_prepared_chat_message(shared_ptr l, uint32_t from_guild_card_nu void send_chat_message(shared_ptr c, uint32_t from_guild_card_number, const string& from_name, const string& text, char private_flags) { string prepared_data = prepare_chat_data( c->version(), - c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION), c->language(), c->lobby_client_id, from_name, @@ -902,15 +933,21 @@ void send_simple_mail_bb(shared_ptr c, uint32_t from_guild_card_number, void send_simple_mail(shared_ptr c, uint32_t from_guild_card_number, const string& from_name, const string& text) { switch (c->version()) { - case GameVersion::DC: - case GameVersion::GC: - case GameVersion::XB: + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + case Version::XB_V3: send_simple_mail_t(c, from_guild_card_number, from_name, text); break; - case GameVersion::PC: + case Version::PC_V2: send_simple_mail_t(c, from_guild_card_number, from_name, text); break; - case GameVersion::BB: + case Version::BB_V4: send_simple_mail_bb(c, from_guild_card_number, from_name, text); break; default: @@ -938,9 +975,7 @@ void send_info_board_t(shared_ptr c) { } void send_info_board(shared_ptr c) { - if (c->version() == GameVersion::PC || - c->version() == GameVersion::PATCH || - c->version() == GameVersion::BB) { + if (uses_utf16(c->version())) { send_info_board_t(c); } else if (c->language()) { send_info_board_t(c); @@ -955,7 +990,7 @@ void send_card_search_result_t( shared_ptr result, shared_ptr result_lobby) { auto s = c->require_server_state(); - const auto& port_name = version_to_lobby_port_name.at(static_cast(c->version())); + string port_name = lobby_port_name_for_version(c->version()); S_GuildCardSearchResult cmd; cmd.player_tag = 0x00010000; @@ -989,16 +1024,26 @@ void send_card_search_result( shared_ptr c, shared_ptr result, shared_ptr result_lobby) { - if ((c->version() == GameVersion::DC) || - (c->version() == GameVersion::GC) || - (c->version() == GameVersion::XB)) { - send_card_search_result_t(c, result, result_lobby); - } else if (c->version() == GameVersion::PC) { - send_card_search_result_t(c, result, result_lobby); - } else if (c->version() == GameVersion::BB) { - send_card_search_result_t(c, result, result_lobby); - } else { - throw logic_error("unimplemented versioned command"); + switch (c->version()) { + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + case Version::XB_V3: + send_card_search_result_t(c, result, result_lobby); + break; + case Version::PC_V2: + send_card_search_result_t(c, result, result_lobby); + break; + case Version::BB_V4: + send_card_search_result_t(c, result, result_lobby); + break; + default: + throw logic_error("unimplemented versioned command"); } } @@ -1087,23 +1132,30 @@ void send_guild_card( uint8_t section_id, uint8_t char_class) { switch (ch.version) { - case GameVersion::DC: + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + // TODO: Is this the right format and subcommand for NTE and the prototype? send_guild_card_dc_pc_gc_t( ch, guild_card_number, name, description, language, section_id, char_class); break; - case GameVersion::PC: + case Version::PC_V2: send_guild_card_dc_pc_gc_t( ch, guild_card_number, name, description, language, section_id, char_class); break; - case GameVersion::GC: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: send_guild_card_dc_pc_gc_t( ch, guild_card_number, name, description, language, section_id, char_class); break; - case GameVersion::XB: + case Version::XB_V3: send_guild_card_xb( ch, guild_card_number, xb_user_id, name, description, language, section_id, char_class); break; - case GameVersion::BB: + case Version::BB_V4: send_guild_card_bb(ch, guild_card_number, name, team_name, description, language, section_id, char_class); break; default: @@ -1152,25 +1204,29 @@ void send_menu_t(shared_ptr c, shared_ptr menu, bool is_info for (const auto& item : menu->items) { bool is_visible = true; switch (c->version()) { - case GameVersion::DC: + case Version::DC_NTE: + is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_DCNTE); + [[fallthrough]]; + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_DC); - if (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) { - is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_DCNTE); - } break; - case GameVersion::PC: + case Version::PC_V2: is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_PC); break; - case GameVersion::GC: + case Version::GC_NTE: + is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_GC_TRIAL_EDITION); + [[fallthrough]]; + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_GC); - if (c->config.check_flag(Client::Flag::IS_GC_TRIAL_EDITION)) { - is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_GC_TRIAL_EDITION); - } break; - case GameVersion::XB: + case Version::XB_V3: is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_XB); break; - case GameVersion::BB: + case Version::BB_V4: is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_BB); break; default: @@ -1193,7 +1249,7 @@ void send_menu_t(shared_ptr c, shared_ptr menu, bool is_info auto& e = entries.emplace_back(); e.menu_id = menu->menu_id; e.item_id = item.item_id; - e.flags = (c->version() == GameVersion::BB) ? 0x0004 : 0x0F04; + e.flags = (c->version() == Version::BB_V4) ? 0x0004 : 0x0F04; e.text.encode(item.name, c->language()); } } @@ -1203,9 +1259,7 @@ void send_menu_t(shared_ptr c, shared_ptr menu, bool is_info } void send_menu(shared_ptr c, shared_ptr menu, bool is_info_menu) { - if (c->version() == GameVersion::PC || - c->version() == GameVersion::PATCH || - c->version() == GameVersion::BB) { + if (uses_utf16(c->version())) { send_menu_t(c, menu, is_info_menu); } else { send_menu_t(c, menu, is_info_menu); @@ -1235,7 +1289,7 @@ void send_game_menu_t( if (!l->is_game()) { continue; } - if (!l->version_is_allowed(c->quest_version())) { + if (!l->version_is_allowed(c->version())) { continue; } if (l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM) != is_spectator_team_list) { @@ -1268,16 +1322,16 @@ void send_game_menu_t( e.game_id = l->lobby_id; e.difficulty_tag = (l->is_ep3() ? 0x0A : (l->difficulty + 0x22)); e.num_players = l->count_clients(); - if (c->version() == GameVersion::DC) { - e.episode = l->version_is_allowed(QuestScriptVersion::DC_V1) ? 1 : 0; + if (is_dc(c->version())) { + e.episode = l->version_is_allowed(Version::DC_V1) ? 1 : 0; } else { - e.episode = ((c->version() == GameVersion::BB) ? (l->max_clients << 4) : 0) | episode_num; + e.episode = ((c->version() == Version::BB_V4) ? (l->max_clients << 4) : 0) | episode_num; } if (l->is_ep3()) { e.flags = (l->password.empty() ? 0 : 2) | (l->check_flag(Lobby::Flag::BATTLE_IN_PROGRESS) ? 4 : 0); } else { e.flags = (l->password.empty() ? 0 : 2); - if ((c->version() != GameVersion::DC) && (c->version() != GameVersion::PC)) { + if ((c->version() == Version::GC_NTE) || !is_v1_or_v2(c->version())) { e.flags |= (episode_num << 6); } switch (l->mode) { @@ -1308,12 +1362,10 @@ void send_game_menu( shared_ptr c, bool is_spectator_team_list, bool show_tournaments_only) { - if ((c->version() == GameVersion::DC) || - (c->version() == GameVersion::GC) || - (c->version() == GameVersion::XB)) { - send_game_menu_t(c, is_spectator_team_list, show_tournaments_only); - } else { + if (uses_utf16(c->version())) { send_game_menu_t(c, is_spectator_team_list, show_tournaments_only); + } else { + send_game_menu_t(c, is_spectator_team_list, show_tournaments_only); } } @@ -1323,7 +1375,7 @@ void send_quest_menu_t( uint32_t menu_id, const vector>& quests, bool is_download_menu) { - auto v = c->quest_version(); + auto v = c->version(); vector entries; for (const auto& quest : quests) { auto vq = quest->version(v, c->language()); @@ -1364,17 +1416,23 @@ void send_quest_menu_t( void send_quest_menu(shared_ptr c, uint32_t menu_id, const vector>& quests, bool is_download_menu) { switch (c->version()) { - case GameVersion::PC: + case Version::PC_V2: send_quest_menu_t(c, menu_id, quests, is_download_menu); break; - case GameVersion::DC: - case GameVersion::GC: + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: send_quest_menu_t(c, menu_id, quests, is_download_menu); break; - case GameVersion::XB: + case Version::XB_V3: send_quest_menu_t(c, menu_id, quests, is_download_menu); break; - case GameVersion::BB: + case Version::BB_V4: send_quest_menu_t(c, menu_id, quests, is_download_menu); break; default: @@ -1385,17 +1443,23 @@ void send_quest_menu(shared_ptr c, uint32_t menu_id, void send_quest_menu(shared_ptr c, uint32_t menu_id, shared_ptr category_index, uint8_t flags) { switch (c->version()) { - case GameVersion::PC: + case Version::PC_V2: send_quest_menu_t(c, menu_id, category_index, flags); break; - case GameVersion::DC: - case GameVersion::GC: + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: send_quest_menu_t(c, menu_id, category_index, flags); break; - case GameVersion::XB: + case Version::XB_V3: send_quest_menu_t(c, menu_id, category_index, flags); break; - case GameVersion::BB: + case Version::BB_V4: send_quest_menu_t(c, menu_id, category_index, flags); break; default: @@ -1414,10 +1478,7 @@ void send_lobby_list(shared_ptr c) { if (!l->check_flag(Lobby::Flag::DEFAULT)) { continue; } - if (l->check_flag(Lobby::Flag::V2_AND_LATER) && c->config.check_flag(Client::Flag::IS_DC_V1)) { - continue; - } - if (l->is_ep3() && !c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (!l->version_is_allowed(c->version())) { continue; } auto& e = entries.emplace_back(); @@ -1457,8 +1518,8 @@ void send_player_records_t(shared_ptr c, shared_ptr l, shared_ptr } static void send_join_spectator_team(shared_ptr c, shared_ptr l) { - if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { - throw runtime_error("lobby is not Episode 3"); + if (!is_ep3(c->version())) { + throw runtime_error("client is not Episode 3"); } if (!l->is_ep3()) { throw runtime_error("lobby is not Episode 3"); @@ -1639,51 +1700,55 @@ void send_join_game(shared_ptr c, shared_ptr l) { }; switch (c->version()) { - case GameVersion::DC: { - if (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) { - S_JoinGame_DCNTE_64 cmd; - cmd.client_id = c->lobby_client_id; - cmd.leader_id = l->leader_id; - cmd.disable_udp = 0x01; - cmd.variations = l->variations; - size_t player_count = populate_lobby_data(cmd); - send_command_t(c, 0x64, player_count, cmd); - } else { - S_JoinGame_DC_64 cmd; - size_t player_count = populate_base_cmd(cmd); - send_command_t(c, 0x64, player_count, cmd); - } + case Version::DC_NTE: { + S_JoinGame_DCNTE_64 cmd; + cmd.client_id = c->lobby_client_id; + cmd.leader_id = l->leader_id; + cmd.disable_udp = 0x01; + cmd.variations = l->variations; + size_t player_count = populate_lobby_data(cmd); + send_command_t(c, 0x64, player_count, cmd); break; } - case GameVersion::PC: { + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: { + S_JoinGame_DC_64 cmd; + size_t player_count = populate_base_cmd(cmd); + send_command_t(c, 0x64, player_count, cmd); + break; + } + case Version::PC_V2: { S_JoinGame_PC_64 cmd; size_t player_count = populate_base_cmd(cmd); send_command_t(c, 0x64, player_count, cmd); break; } - case GameVersion::GC: { - if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) { - S_JoinGame_GC_Ep3_64 cmd; - size_t player_count = populate_v3_cmd(cmd); - auto s = c->require_server_state(); - for (size_t x = 0; x < 4; x++) { - if (l->clients[x]) { - auto other_p = l->clients[x]->game_data.character(); - cmd.players_ep3[x].inventory = other_p->inventory; - cmd.players_ep3[x].inventory.encode_for_client(c); - cmd.players_ep3[x].disp = convert_player_disp_data(other_p->disp, c->language(), other_p->inventory.language); - cmd.players_ep3[x].disp.enforce_lobby_join_limits_for_client(c); - } - } - send_command_t(c, 0x64, player_count, cmd); - } else { - S_JoinGame_GC_64 cmd; - size_t player_count = populate_v3_cmd(cmd); - send_command_t(c, 0x64, player_count, cmd); - } + case Version::GC_NTE: + case Version::GC_V3: { + S_JoinGame_GC_64 cmd; + size_t player_count = populate_v3_cmd(cmd); + send_command_t(c, 0x64, player_count, cmd); break; } - case GameVersion::XB: { + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: { + S_JoinGame_GC_Ep3_64 cmd; + size_t player_count = populate_v3_cmd(cmd); + auto s = c->require_server_state(); + for (size_t x = 0; x < 4; x++) { + if (l->clients[x]) { + auto other_p = l->clients[x]->game_data.character(); + cmd.players_ep3[x].inventory = other_p->inventory; + cmd.players_ep3[x].inventory.encode_for_client(c); + cmd.players_ep3[x].disp = convert_player_disp_data(other_p->disp, c->language(), other_p->inventory.language); + cmd.players_ep3[x].disp.enforce_lobby_join_limits_for_client(c); + } + } + send_command_t(c, 0x64, player_count, cmd); + break; + } + case Version::XB_V3: { S_JoinGame_XB_64 cmd; size_t player_count = populate_v3_cmd(cmd); for (size_t x = 0; x < 4; x++) { @@ -1699,7 +1764,7 @@ void send_join_game(shared_ptr c, shared_ptr l) { send_command_t(c, 0x64, player_count, cmd); break; } - case GameVersion::BB: { + case Version::BB_V4: { S_JoinGame_BB_64 cmd; size_t player_count = populate_v3_cmd(cmd); cmd.unused1 = 0; @@ -1708,8 +1773,6 @@ void send_join_game(shared_ptr c, shared_ptr l) { send_command_t(c, 0x64, player_count, cmd); break; } - case GameVersion::PATCH: - throw logic_error("patch server clients cannot join games"); default: throw logic_error("invalid game version"); } @@ -1734,11 +1797,10 @@ void send_join_lobby_t(shared_ptr c, shared_ptr l, shared_ptrversion() != GameVersion::DC) || !c->config.check_flag(Client::Flag::IS_DC_V1)) { + if (!is_v1(c->version())) { send_player_records_t(c, l, joining_client); } - - if (c->version() == GameVersion::BB) { + if (c->version() == Version::BB_V4) { send_all_nearby_team_metadatas_to_client(c, false); } @@ -1746,27 +1808,29 @@ void send_join_lobby_t(shared_ptr c, shared_ptr l, shared_ptrconfig.override_lobby_number != 0x80) { lobby_type = c->config.override_lobby_number; } else if (l->check_flag(Lobby::Flag::IS_OVERFLOW)) { - lobby_type = c->config.check_flag(Client::Flag::IS_EPISODE_3) ? 15 : 0; + lobby_type = is_ep3(c->version()) ? 15 : 0; } else { lobby_type = l->block - 1; } // Allow non-canonical lobby types on GC. They may work on other versions too, // but I haven't verified which values don't crash on each version. - if (c->version() == GameVersion::GC) { - if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + switch (c->version()) { + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: if ((lobby_type > 0x14) && (lobby_type < 0xE9)) { lobby_type = l->block - 1; } - } else { + break; + case Version::GC_V3: if ((lobby_type > 0x11) && (lobby_type != 0x67) && (lobby_type != 0xD4) && (lobby_type < 0xFC)) { lobby_type = l->block - 1; } - } - } else { - if (lobby_type > 0x0E) { - lobby_type = l->block - 1; - } + break; + default: + if (lobby_type > 0x0E) { + lobby_type = l->block - 1; + } } S_JoinLobby cmd; @@ -1832,7 +1896,7 @@ void send_join_lobby_xb(shared_ptr c, shared_ptr l, shared_ptrconfig.override_lobby_number != 0x80) { lobby_type = c->config.override_lobby_number; } else if (l->check_flag(Lobby::Flag::IS_OVERFLOW)) { - lobby_type = c->config.check_flag(Client::Flag::IS_EPISODE_3) ? 15 : 0; + lobby_type = is_ep3(c->version()) ? 15 : 0; } else { lobby_type = l->block - 1; } @@ -1938,23 +2002,27 @@ void send_join_lobby(shared_ptr c, shared_ptr l) { send_join_game(c, l); } else { switch (c->version()) { - case GameVersion::DC: - if (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION) || c->config.check_flag(Client::Flag::IS_DC_V1_PROTOTYPE)) { - send_join_lobby_dc_nte(c, l); - } else { - send_join_lobby_t(c, l); - } + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + send_join_lobby_dc_nte(c, l); break; - case GameVersion::PC: + case Version::DC_V1: + case Version::DC_V2: + send_join_lobby_t(c, l); + break; + case Version::PC_V2: send_join_lobby_t(c, l); break; - case GameVersion::GC: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: send_join_lobby_t(c, l); break; - case GameVersion::XB: + case Version::XB_V3: send_join_lobby_xb(c, l); break; - case GameVersion::BB: + case Version::BB_V4: send_join_lobby_t(c, l); break; default: @@ -1973,23 +2041,27 @@ void send_join_lobby(shared_ptr c, shared_ptr l) { void send_player_join_notification(shared_ptr c, shared_ptr l, shared_ptr joining_client) { switch (c->version()) { - case GameVersion::DC: - if (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION) || c->config.check_flag(Client::Flag::IS_DC_V1_PROTOTYPE)) { - send_join_lobby_dc_nte(c, l, joining_client); - } else { - send_join_lobby_t(c, l, joining_client); - } + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + send_join_lobby_dc_nte(c, l, joining_client); break; - case GameVersion::PC: + case Version::DC_V1: + case Version::DC_V2: + send_join_lobby_t(c, l, joining_client); + break; + case Version::PC_V2: send_join_lobby_t(c, l, joining_client); break; - case GameVersion::GC: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: send_join_lobby_t(c, l, joining_client); break; - case GameVersion::XB: + case Version::XB_V3: send_join_lobby_xb(c, l, joining_client); break; - case GameVersion::BB: + case Version::BB_V4: send_join_lobby_t(c, l, joining_client); break; default: @@ -2017,12 +2089,7 @@ void send_self_leave_notification(shared_ptr c) { } void send_get_player_info(shared_ptr c) { - if ((c->version() == GameVersion::DC) && - c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) { - send_command(c, 0x8D, 0x00); - } else { - send_command(c, 0x95, 0x00); - } + send_command(c, (c->version() == Version::DC_NTE) ? 0x8D : 0x95, 0x00); } //////////////////////////////////////////////////////////////////////////////// @@ -2044,9 +2111,8 @@ void send_execute_item_trade(shared_ptr c, const vector& items send_command_t(c, 0xD3, 0x00, cmd); } -void send_execute_card_trade(shared_ptr c, - const vector>& card_to_count) { - if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { +void send_execute_card_trade(shared_ptr c, const vector>& card_to_count) { + if (is_ep3(c->version())) { throw logic_error("cannot send trade cards command to non-Ep3 client"); } @@ -2077,20 +2143,22 @@ void send_arrow_update(shared_ptr l) { vector entries; for (size_t x = 0; x < l->max_clients; x++) { - if (!l->clients[x]) { + auto lc = l->clients[x]; + if (!lc) { continue; } auto& e = entries.emplace_back(); e.player_tag = 0x00010000; - e.guild_card_number = l->clients[x]->license->serial_number; - e.arrow_color = l->clients[x]->lobby_arrow_color; + e.guild_card_number = lc->license->serial_number; + e.arrow_color = lc->lobby_arrow_color; } for (size_t x = 0; x < l->max_clients; x++) { - if (!l->clients[x] || l->clients[x]->config.check_flag(Client::Flag::IS_DC_V1)) { + auto lc = l->clients[x]; + if (!lc || is_v1(lc->version())) { continue; } - send_command_vt(l->clients[x], 0x88, entries.size(), entries); + send_command_vt(lc, 0x88, entries.size(), entries); } } @@ -2208,7 +2276,7 @@ void send_pick_up_item(shared_ptr c, uint32_t item_id, uint8_t floor) { void send_create_inventory_item(shared_ptr c, const ItemData& item) { auto l = c->require_lobby(); - if (c->version() != GameVersion::BB) { + if (c->version() != Version::BB_V4) { throw logic_error("6xBE can only be sent to BB clients"); } uint16_t client_id = c->lobby_client_id; @@ -2225,7 +2293,7 @@ void send_destroy_item(shared_ptr c, uint32_t item_id, uint32_t amount) void send_item_identify_result(shared_ptr c) { auto l = c->require_lobby(); - if (c->version() != GameVersion::BB) { + if (c->version() != Version::BB_V4) { throw logic_error("cannot send item identify result to non-BB client"); } G_IdentifyResult_BB_6xB9 res; @@ -2237,7 +2305,7 @@ void send_item_identify_result(shared_ptr c) { } void send_bank(shared_ptr c) { - if (c->version() != GameVersion::BB) { + if (c->version() != Version::BB_V4) { throw logic_error("6xBC can only be sent to BB clients"); } @@ -2255,7 +2323,7 @@ void send_bank(shared_ptr c) { } void send_shop(shared_ptr c, uint8_t shop_type) { - if (c->version() != GameVersion::BB) { + if (c->version() != Version::BB_V4) { throw logic_error("6xB6 can only be sent to BB clients"); } @@ -2301,7 +2369,7 @@ void send_level_up(shared_ptr c) { void send_give_experience(shared_ptr c, uint32_t amount) { auto l = c->require_lobby(); - if (c->version() != GameVersion::BB) { + if (c->version() != Version::BB_V4) { throw logic_error("6xBF can only be sent to BB clients"); } uint16_t client_id = c->lobby_client_id; @@ -2311,7 +2379,7 @@ void send_give_experience(shared_ptr c, uint32_t amount) { } void send_set_exp_multiplier(std::shared_ptr l) { - if (l->base_version != GameVersion::BB) { + if (l->base_version != Version::BB_V4) { throw logic_error("6xDD can only be sent to BB clients"); } if (!l->is_game()) { @@ -2349,7 +2417,7 @@ void send_quest_function_call(shared_ptr c, uint16_t function_id) { void send_ep3_card_list_update(shared_ptr c) { if (!c->config.check_flag(Client::Flag::HAS_EP3_CARD_DEFS)) { auto s = c->require_server_state(); - const auto& data = c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION) + const auto& data = (c->version() == Version::GC_EP3_TRIAL_EDITION) ? s->ep3_card_index_trial->get_compressed_definitions() : s->ep3_card_index->get_compressed_definitions(); @@ -2423,7 +2491,7 @@ void send_ep3_set_context_token(shared_ptr c, uint32_t context_token) { void send_ep3_confirm_tournament_entry( shared_ptr c, shared_ptr tourn) { - if (c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION)) { + if (c->version() == Version::GC_EP3_TRIAL_EDITION) { throw runtime_error("cannot send tournament entry command to Episode 3 Trial Edition client"); } @@ -2540,7 +2608,7 @@ void send_ep3_tournament_details( } string ep3_description_for_client(shared_ptr c) { - if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (!is_ep3(c->version())) { throw runtime_error("client is not Episode 3"); } auto p = c->game_data.character(); @@ -2983,17 +3051,22 @@ void send_open_quest_file( shared_ptr contents) { switch (c->version()) { - case GameVersion::DC: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: send_open_quest_file_t(c, quest_name, filename, xb_filename, contents->size(), quest_number, type); break; - case GameVersion::PC: - case GameVersion::GC: + case Version::PC_V2: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: send_open_quest_file_t(c, quest_name, filename, xb_filename, contents->size(), quest_number, type); break; - case GameVersion::XB: + case Version::XB_V3: send_open_quest_file_t(c, quest_name, filename, xb_filename, contents->size(), quest_number, type); break; - case GameVersion::BB: + case Version::BB_V4: send_open_quest_file_t(c, quest_name, filename, xb_filename, contents->size(), quest_number, type); break; default: @@ -3002,7 +3075,7 @@ void send_open_quest_file( // For GC/XB/BB, we wait for acknowledgement commands before sending each // chunk. For DC/PC, we send the entire quest all at once. - if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::PC)) { + if (is_v1_or_v2(c->version())) { for (size_t offset = 0; offset < contents->size(); offset += 0x400) { size_t chunk_bytes = contents->size() - offset; if (chunk_bytes > 0x400) { @@ -3106,7 +3179,7 @@ void send_ep3_card_auction(shared_ptr l) { distribution_size += e.probability; } - auto card_index = l->check_flag(Lobby::Flag::IS_EP3_TRIAL) + auto card_index = (l->base_version == Version::GC_EP3_TRIAL_EDITION) ? s->ep3_card_index_trial : s->ep3_card_index; @@ -3157,12 +3230,9 @@ void send_server_time(shared_ptr c) { void send_change_event(shared_ptr c, uint8_t new_event) { // This command isn't supported on versions before V3, nor on Trial Edition. - if ((c->version() == GameVersion::DC) || - (c->version() == GameVersion::PC) || - c->config.check_flag(Client::Flag::IS_GC_TRIAL_EDITION)) { - return; + if (!is_v1_or_v2(c->version())) { + send_command(c, 0xDA, new_event); } - send_command(c, 0xDA, new_event); } void send_change_event(shared_ptr l, uint8_t new_event) { diff --git a/src/SendCommands.hh b/src/SendCommands.hh index a2c51955..f93e6b9e 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -187,8 +187,7 @@ void send_text_message(std::shared_ptr l, const std::string& text); void send_text_message(std::shared_ptr s, const std::string& text); std::string prepare_chat_data( - GameVersion version, - bool is_nte, + Version version, uint8_t language, uint8_t from_client_id, const std::string& from_name, diff --git a/src/Server.cc b/src/Server.cc index 9d881817..38b4a086 100644 --- a/src/Server.cc +++ b/src/Server.cc @@ -130,7 +130,7 @@ void Server::on_listen_accept( void Server::connect_client( struct bufferevent* bev, uint32_t address, uint16_t client_port, - uint16_t server_port, GameVersion version, ServerBehavior initial_state) { + uint16_t server_port, Version version, ServerBehavior initial_state) { shared_ptr c(new Client(this->shared_from_this(), bev, version, initial_state)); c->channel.on_command_received = Server::on_client_input; c->channel.on_error = Server::on_client_error; @@ -141,8 +141,8 @@ void Server::connect_client( c->id, bev, server_port, - name_for_version(version), - name_for_server_behavior(initial_state)); + name_for_enum(version), + name_for_enum(initial_state)); this->state->channel_to_client.emplace(&c->channel, c); @@ -221,7 +221,7 @@ Server::Server( void Server::listen( const std::string& addr_str, const string& socket_path, - GameVersion version, + Version version, ServerBehavior behavior) { int fd = ::listen(socket_path, 0, SOMAXCONN); server_log.info("Listening on Unix socket %s on fd %d as %s", @@ -233,7 +233,7 @@ void Server::listen( const std::string& addr_str, const string& addr, int port, - GameVersion version, + Version version, ServerBehavior behavior) { if (port == 0) { this->listen(addr_str, addr, version, behavior); @@ -246,13 +246,13 @@ void Server::listen( } } -void Server::listen(const std::string& addr_str, int port, GameVersion version, ServerBehavior behavior) { +void Server::listen(const std::string& addr_str, int port, Version version, ServerBehavior behavior) { this->listen(addr_str, "", port, version, behavior); } Server::ListeningSocket::ListeningSocket( Server* s, const std::string& addr_str, - int fd, GameVersion version, ServerBehavior behavior) + int fd, Version version, ServerBehavior behavior) : addr_str(addr_str), fd(fd), version(version), @@ -269,7 +269,7 @@ Server::ListeningSocket::ListeningSocket( void Server::add_socket( const std::string& addr_str, int fd, - GameVersion version, + Version version, ServerBehavior behavior) { this->listening_sockets.emplace( piecewise_construct, forward_as_tuple(fd), diff --git a/src/Server.hh b/src/Server.hh index 67860743..f794f167 100644 --- a/src/Server.hh +++ b/src/Server.hh @@ -18,14 +18,14 @@ public: Server(std::shared_ptr base, std::shared_ptr state); virtual ~Server() = default; - void listen(const std::string& addr_str, const std::string& socket_path, GameVersion version, ServerBehavior initial_state); - void listen(const std::string& addr_str, const std::string& addr, int port, GameVersion version, ServerBehavior initial_state); - void listen(const std::string& addr_str, int port, GameVersion version, ServerBehavior initial_state); - void add_socket(const std::string& addr_str, int fd, GameVersion version, ServerBehavior initial_state); + void listen(const std::string& addr_str, const std::string& socket_path, Version version, ServerBehavior initial_state); + void listen(const std::string& addr_str, const std::string& addr, int port, Version version, ServerBehavior initial_state); + void listen(const std::string& addr_str, int port, Version version, ServerBehavior initial_state); + void add_socket(const std::string& addr_str, int fd, Version version, ServerBehavior initial_state); void connect_client(struct bufferevent* bev, uint32_t address, uint16_t client_port, uint16_t server_port, - GameVersion version, ServerBehavior initial_state); + Version version, ServerBehavior initial_state); void connect_client(std::shared_ptr c, Channel&& ch); void disconnect_client(std::shared_ptr c); @@ -45,7 +45,7 @@ private: struct ListeningSocket { std::string addr_str; int fd; - GameVersion version; + Version version; ServerBehavior behavior; std::unique_ptr listener; @@ -53,7 +53,7 @@ private: Server* s, const std::string& name, int fd, - GameVersion version, + Version version, ServerBehavior behavior); }; std::unordered_map listening_sockets; diff --git a/src/ServerShell.cc b/src/ServerShell.cc index 272d1bad..78ca0f05 100644 --- a/src/ServerShell.cc +++ b/src/ServerShell.cc @@ -670,7 +670,7 @@ Proxy session commands:\n\ auto ses = this->get_proxy_session(session_name); bool is_dchat = (command_name == "dchat"); - if (!is_dchat && (ses->version() == GameVersion::PC || ses->version() == GameVersion::BB)) { + if (!is_dchat && uses_utf16(ses->version())) { send_chat_message_from_client(ses->server_channel, command_args, 0); } else { string data(8, '\0'); @@ -688,8 +688,7 @@ Proxy session commands:\n\ } else if ((command_name == "wc") || (command_name == "wchat")) { auto ses = this->get_proxy_session(session_name); - if ((ses->version() != GameVersion::GC) || - !ses->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (!is_ep3(ses->version())) { throw runtime_error("wchat can only be used on Episode 3"); } string data(8, '\0'); @@ -746,9 +745,7 @@ Proxy session commands:\n\ ses->config.override_lobby_event = 0xFF; } else { ses->config.override_lobby_event = event_for_name(command_args); - if ((ses->version() != GameVersion::DC) && - (ses->version() != GameVersion::PC) && - !((ses->version() == GameVersion::GC) && ses->config.check_flag(Client::Flag::IS_GC_TRIAL_EDITION))) { + if (!is_v1_or_v2(ses->version())) { ses->client_channel.send(0xDA, ses->config.override_lobby_event); } } @@ -796,7 +793,7 @@ Proxy session commands:\n\ } else if ((command_name == "create-item") || (command_name == "set-next-item")) { auto ses = this->get_proxy_session(session_name); - if (ses->version() == GameVersion::BB) { + if (ses->version() == Version::BB_V4) { throw runtime_error("proxy session is BB"); } if (!ses->is_in_game) { diff --git a/src/ServerState.cc b/src/ServerState.cc index d291a95d..c630c084 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -17,7 +17,7 @@ using namespace std; -ServerState::ServerState(const char* config_filename, bool is_replay) +ServerState::ServerState(const string& config_filename, bool is_replay) : config_filename(config_filename), is_replay(is_replay), dns_server_port(0), @@ -56,28 +56,38 @@ void ServerState::init() { for (size_t x = 0; x < 20; x++) { auto lobby_name = string_printf("LOBBY%zu", x + 1); - bool v2_and_later_only = (x > 9); - bool is_ep3_only = (x > 14); + bool allow_v1 = (x <= 9); + bool allow_non_ep3 = (x <= 14); shared_ptr l = this->create_lobby(); l->set_flag(Lobby::Flag::PUBLIC); l->set_flag(Lobby::Flag::DEFAULT); l->set_flag(Lobby::Flag::PERSISTENT); - if (v2_and_later_only) { - l->set_flag(Lobby::Flag::V2_AND_LATER); + if (allow_non_ep3) { + if (allow_v1) { + l->allow_version(Version::DC_NTE); + l->allow_version(Version::DC_V1_12_2000_PROTOTYPE); + l->allow_version(Version::DC_V1); + } + l->allow_version(Version::DC_V2); + l->allow_version(Version::PC_V2); + l->allow_version(Version::GC_NTE); + l->allow_version(Version::GC_V3); + l->allow_version(Version::XB_V3); + l->allow_version(Version::BB_V4); } + l->allow_version(Version::GC_EP3_TRIAL_EDITION); + l->allow_version(Version::GC_EP3); + l->block = x + 1; l->name = lobby_name; l->max_clients = 12; - if (is_ep3_only) { + if (!allow_non_ep3) { l->episode = Episode::EP3; } - if (!v2_and_later_only) { - this->public_lobby_search_order_v1.emplace_back(l); - } - if (!is_ep3_only) { - this->public_lobby_search_order_non_v1.emplace_back(l); + if (allow_non_ep3) { + this->public_lobby_search_order.emplace_back(l); } else { ep3_only_lobbies.emplace_back(l); } @@ -86,9 +96,8 @@ void ServerState::init() { // Annoyingly, the CARD lobbies should be searched first, but are sent at the // end of the lobby list command, so we have to change the search order // manually here. - this->public_lobby_search_order_ep3 = this->public_lobby_search_order_non_v1; - this->public_lobby_search_order_ep3.insert( - this->public_lobby_search_order_ep3.begin(), + this->public_lobby_search_order.insert( + this->public_lobby_search_order.begin(), ep3_only_lobbies.begin(), ep3_only_lobbies.end()); @@ -120,7 +129,7 @@ void ServerState::add_client_to_available_lobby(shared_ptr c) { if (l && !l->is_game() && l->check_flag(Lobby::Flag::PUBLIC) && - (c->config.check_flag(Client::Flag::IS_EPISODE_3) || (l->episode != Episode::EP3))) { + l->version_is_allowed(c->version())) { l->add_client(c); added_to_lobby = l; } @@ -129,17 +138,16 @@ void ServerState::add_client_to_available_lobby(shared_ptr c) { } if (!added_to_lobby.get()) { - const auto* search_order = &this->public_lobby_search_order_non_v1; - if (c->config.check_flag(Client::Flag::IS_DC_V1)) { - search_order = &this->public_lobby_search_order_v1; - } else if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) { - search_order = &this->public_lobby_search_order_ep3; - } - for (const auto& l : *search_order) { + for (const auto& l : this->public_lobby_search_order) { try { - l->add_client(c); - added_to_lobby = l; - break; + if (l && + !l->is_game() && + l->check_flag(Lobby::Flag::PUBLIC) && + l->version_is_allowed(c->version())) { + l->add_client(c); + added_to_lobby = l; + break; + } } catch (const out_of_range&) { } } @@ -153,6 +161,7 @@ void ServerState::add_client_to_available_lobby(shared_ptr c) { added_to_lobby->name = "Overflow"; added_to_lobby->max_clients = 12; added_to_lobby->event = this->pre_lobby_event; + added_to_lobby->allow_version(c->version()); added_to_lobby->add_client(c); } @@ -325,61 +334,79 @@ uint32_t ServerState::connect_address_for_client(std::shared_ptr c) cons } } -std::shared_ptr ServerState::information_menu_for_version(GameVersion version) const { - if ((version == GameVersion::DC) || (version == GameVersion::PC)) { +std::shared_ptr ServerState::information_menu_for_version(Version version) const { + if (is_v1_or_v2(version)) { return this->information_menu_v2; - } else if ((version == GameVersion::GC) || (version == GameVersion::XB)) { + } else if (is_v3(version)) { return this->information_menu_v3; } throw out_of_range("no information menu exists for this version"); } -shared_ptr ServerState::proxy_destinations_menu_for_version(GameVersion version) const { +shared_ptr ServerState::proxy_destinations_menu_for_version(Version version) const { switch (version) { - case GameVersion::DC: + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: return this->proxy_destinations_menu_dc; - case GameVersion::PC: + case Version::PC_V2: return this->proxy_destinations_menu_pc; - case GameVersion::GC: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: return this->proxy_destinations_menu_gc; - case GameVersion::XB: + case Version::XB_V3: return this->proxy_destinations_menu_xb; default: throw out_of_range("no proxy destinations menu exists for this version"); } } -const vector>& ServerState::proxy_destinations_for_version(GameVersion version) const { +const vector>& ServerState::proxy_destinations_for_version(Version version) const { switch (version) { - case GameVersion::DC: + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: return this->proxy_destinations_dc; - case GameVersion::PC: + case Version::PC_V2: return this->proxy_destinations_pc; - case GameVersion::GC: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: return this->proxy_destinations_gc; - case GameVersion::XB: + case Version::XB_V3: return this->proxy_destinations_xb; default: throw out_of_range("no proxy destinations menu exists for this version"); } } -std::shared_ptr ServerState::item_parameter_table_for_version(GameVersion version) const { +std::shared_ptr ServerState::item_parameter_table_for_version(Version version) const { switch (version) { - case GameVersion::DC: - case GameVersion::PC: + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::PC_V2: return this->item_parameter_table_v2; - case GameVersion::GC: - case GameVersion::XB: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + case Version::XB_V3: return this->item_parameter_table_v3; - case GameVersion::BB: + case Version::BB_V4: return this->item_parameter_table_v4; default: throw out_of_range("no item parameter table exists for this version"); } } -std::string ServerState::describe_item(GameVersion version, const ItemData& item, bool include_color_codes) const { +std::string ServerState::describe_item(Version version, const ItemData& item, bool include_color_codes) const { return this->item_name_index->describe_item( version, item, @@ -499,8 +526,8 @@ static vector parse_port_configuration(const JSON& json) { PortConfiguration& pc = ret.emplace_back(); pc.name = item_json_it.first; pc.port = item_list->at(0).as_int(); - pc.version = version_for_name(item_list->at(1).as_string().c_str()); - pc.behavior = server_behavior_for_name(item_list->at(2).as_string().c_str()); + pc.version = enum_for_name(item_list->at(1).as_string().c_str()); + pc.behavior = enum_for_name(item_list->at(2).as_string().c_str()); } return ret; } @@ -846,7 +873,7 @@ void ServerState::parse_config(const JSON& json, bool is_reload) { this->proxy_destination_patch = parse_netloc(netloc_str); config_log.info("Patch server proxy is enabled with destination %s", netloc_str.c_str()); for (auto& it : this->name_to_port_config) { - if (it.second->version == GameVersion::PATCH) { + if (is_patch(it.second->version)) { it.second->behavior = ServerBehavior::PROXY_SERVER; } } @@ -859,7 +886,7 @@ void ServerState::parse_config(const JSON& json, bool is_reload) { this->proxy_destination_bb = parse_netloc(netloc_str); config_log.info("BB proxy is enabled with destination %s", netloc_str.c_str()); for (auto& it : this->name_to_port_config) { - if (it.second->version == GameVersion::BB) { + if (it.second->version == Version::BB_V4) { it.second->behavior = ServerBehavior::PROXY_SERVER; } } @@ -957,13 +984,13 @@ void ServerState::load_item_tables() { if (ends_with(filename, "-v2.json")) { config_log.info("Loading v2 JSON rare item table %s", filename.c_str()); - this->rare_item_sets.emplace(basename, new RareItemSet(JSON::parse(load_file(path)), GameVersion::PC, this->item_name_index)); + this->rare_item_sets.emplace(basename, new RareItemSet(JSON::parse(load_file(path)), Version::PC_V2, this->item_name_index)); } else if (ends_with(filename, "-v3.json")) { config_log.info("Loading v3 JSON rare item table %s", filename.c_str()); - this->rare_item_sets.emplace(basename, new RareItemSet(JSON::parse(load_file(path)), GameVersion::GC, this->item_name_index)); + this->rare_item_sets.emplace(basename, new RareItemSet(JSON::parse(load_file(path)), Version::GC_V3, this->item_name_index)); } else if (ends_with(filename, "-v4.json")) { config_log.info("Loading v4 JSON rare item table %s", filename.c_str()); - this->rare_item_sets.emplace(basename, new RareItemSet(JSON::parse(load_file(path)), GameVersion::BB, this->item_name_index)); + this->rare_item_sets.emplace(basename, new RareItemSet(JSON::parse(load_file(path)), Version::BB_V4, this->item_name_index)); } else if (ends_with(filename, ".afs")) { config_log.info("Loading AFS rare item table %s", filename.c_str()); @@ -1121,13 +1148,9 @@ void ServerState::load_dol_files() { } shared_ptr> ServerState::information_contents_for_client(shared_ptr c) const { - return ((c->version() == GameVersion::DC) || (c->version() == GameVersion::PC)) - ? this->information_contents_v2 - : this->information_contents_v3; + return is_v1_or_v2(c->version()) ? this->information_contents_v2 : this->information_contents_v3; } shared_ptr ServerState::quest_index_for_client(shared_ptr c) const { - return c->config.check_flag(Client::Flag::IS_EPISODE_3) - ? this->ep3_download_quest_index - : this->default_quest_index; + return is_ep3(c->version()) ? this->ep3_download_quest_index : this->default_quest_index; } diff --git a/src/ServerState.hh b/src/ServerState.hh index 45187134..c3694aae 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -32,7 +32,7 @@ class Server; struct PortConfiguration { std::string name; uint16_t port; - GameVersion version; + Version version; ServerBehavior behavior; }; @@ -164,9 +164,7 @@ struct ServerState : public std::enable_shared_from_this { std::unordered_map> channel_to_client; std::map> id_to_lobby; - std::vector> public_lobby_search_order_v1; - std::vector> public_lobby_search_order_non_v1; - std::vector> public_lobby_search_order_ep3; + std::vector> public_lobby_search_order; std::atomic next_lobby_id; uint8_t pre_lobby_event; int32_t ep3_menu_song; @@ -181,7 +179,7 @@ struct ServerState : public std::enable_shared_from_this { std::shared_ptr proxy_server; std::shared_ptr game_server; - ServerState(const char* config_filename, bool is_replay); + ServerState(const std::string& config_filename, bool is_replay); ServerState(const ServerState&) = delete; ServerState(ServerState&&) = delete; ServerState& operator=(const ServerState&) = delete; @@ -212,12 +210,12 @@ struct ServerState : public std::enable_shared_from_this { uint32_t connect_address_for_client(std::shared_ptr c) const; - std::shared_ptr information_menu_for_version(GameVersion version) const; - std::shared_ptr proxy_destinations_menu_for_version(GameVersion version) const; - const std::vector>& proxy_destinations_for_version(GameVersion version) const; + std::shared_ptr information_menu_for_version(Version version) const; + std::shared_ptr proxy_destinations_menu_for_version(Version version) const; + const std::vector>& proxy_destinations_for_version(Version version) const; - std::shared_ptr item_parameter_table_for_version(GameVersion version) const; - std::string describe_item(GameVersion version, const ItemData& item, bool include_color_codes) const; + std::shared_ptr item_parameter_table_for_version(Version version) const; + std::string describe_item(Version version, const ItemData& item, bool include_color_codes) const; std::shared_ptr> information_contents_for_client(std::shared_ptr c) const; std::shared_ptr quest_index_for_client(std::shared_ptr c) const; diff --git a/src/Version.cc b/src/Version.cc index 950ae97d..95871649 100644 --- a/src/Version.cc +++ b/src/Version.cc @@ -8,52 +8,154 @@ using namespace std; -const vector version_to_login_port_name = { - "bb-patch", "console-login", "pc-login", "console-login", "xb-login", "bb-init"}; -const vector version_to_lobby_port_name = { - "bb-patch", "console-lobby", "pc-lobby", "console-lobby", "xb-lobby", "bb-lobby"}; -const vector version_to_proxy_port_name = { - "", "dc-proxy", "pc-proxy", "gc-proxy", "xb-proxy", "bb-proxy"}; - -const char* name_for_version(GameVersion version) { - switch (version) { - case GameVersion::GC: - return "GC"; - case GameVersion::XB: - return "XB"; - case GameVersion::PC: - return "PC"; - case GameVersion::BB: - return "BB"; - case GameVersion::DC: - return "DC"; - case GameVersion::PATCH: - return "Patch"; +const char* login_port_name_for_version(Version v) { + switch (v) { + case Version::PC_PATCH: + return "pc-patch"; + case Version::BB_PATCH: + return "bb-patch"; + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + return "console-login"; + case Version::PC_V2: + return "pc-login"; + case Version::XB_V3: + return "xb-login"; + case Version::BB_V4: + return "bb-init"; default: - return "Unknown"; + throw runtime_error("unknown version"); } } -GameVersion version_for_name(const char* name) { - if (!strcasecmp(name, "DC") || !strcasecmp(name, "DreamCast")) { - return GameVersion::DC; - } else if (!strcasecmp(name, "PC")) { - return GameVersion::PC; - } else if (!strcasecmp(name, "GC") || !strcasecmp(name, "GameCube")) { - return GameVersion::GC; - } else if (!strcasecmp(name, "XB") || !strcasecmp(name, "Xbox")) { - return GameVersion::XB; - } else if (!strcasecmp(name, "BB") || !strcasecmp(name, "BlueBurst") || - !strcasecmp(name, "Blue Burst")) { - return GameVersion::BB; - } else if (!strcasecmp(name, "Patch")) { - return GameVersion::PATCH; +const char* lobby_port_name_for_version(Version v) { + switch (v) { + case Version::PC_PATCH: + return "pc-patch"; + case Version::BB_PATCH: + return "bb-patch"; + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + return "console-lobby"; + case Version::PC_V2: + return "pc-lobby"; + case Version::XB_V3: + return "xb-lobby"; + case Version::BB_V4: + return "bb-lobby"; + default: + throw runtime_error("unknown version"); + } +} + +const char* proxy_port_name_for_version(Version v) { + switch (v) { + case Version::PC_PATCH: + return "pc-patch"; + case Version::BB_PATCH: + return "bb-patch"; + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + return "dc-proxy"; + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + return "gc-proxy"; + case Version::PC_V2: + return "pc-proxy"; + case Version::XB_V3: + return "xb-proxy"; + case Version::BB_V4: + return "bb-proxy"; + default: + throw runtime_error("unknown version"); + } +} + +template <> +const char* name_for_enum(Version v) { + switch (v) { + case Version::PC_PATCH: + return "PC_PATCH"; + case Version::BB_PATCH: + return "BB_PATCH"; + case Version::DC_NTE: + return "DC_NTE"; + case Version::DC_V1_12_2000_PROTOTYPE: + return "DC_V1_12_2000_PROTOTYPE"; + case Version::DC_V1: + return "DC_V1"; + case Version::DC_V2: + return "DC_V2"; + case Version::PC_V2: + return "PC_V2"; + case Version::GC_NTE: + return "GC_NTE"; + case Version::GC_V3: + return "GC_V3"; + case Version::GC_EP3_TRIAL_EDITION: + return "GC_EP3_TRIAL_EDITION"; + case Version::GC_EP3: + return "GC_EP3"; + case Version::XB_V3: + return "XB_V3"; + case Version::BB_V4: + return "BB_V4"; + default: + throw runtime_error("unknown version"); + } +} + +template <> +Version enum_for_name(const char* name) { + if (!strcmp(name, "PC_PATCH") || !strcasecmp(name, "patch")) { + return Version::PC_PATCH; + } else if (!strcmp(name, "BB_PATCH")) { + return Version::BB_PATCH; + } else if (!strcmp(name, "DC_NTE")) { + return Version::DC_NTE; + } else if (!strcmp(name, "DC_V1_12_2000_PROTOTYPE")) { + return Version::DC_V1_12_2000_PROTOTYPE; + } else if (!strcmp(name, "DC_V1")) { + return Version::DC_V1; + } else if (!strcmp(name, "DC_V2") || !strcasecmp(name, "dc")) { + return Version::DC_V2; + } else if (!strcmp(name, "PC_V2") || !strcasecmp(name, "pc")) { + return Version::PC_V2; + } else if (!strcmp(name, "GC_NTE")) { + return Version::GC_NTE; + } else if (!strcmp(name, "GC_V3") || !strcasecmp(name, "gc")) { + return Version::GC_V3; + } else if (!strcmp(name, "GC_EP3_TRIAL_EDITION")) { + return Version::GC_EP3_TRIAL_EDITION; + } else if (!strcmp(name, "GC_EP3")) { + return Version::GC_EP3; + } else if (!strcmp(name, "XB_V3") || !strcasecmp(name, "xb")) { + return Version::XB_V3; + } else if (!strcmp(name, "BB_V4") || !strcasecmp(name, "bb")) { + return Version::BB_V4; } else { throw invalid_argument("incorrect version name"); } } -const char* name_for_server_behavior(ServerBehavior behavior) { +template <> +const char* name_for_enum(ServerBehavior behavior) { switch (behavior) { case ServerBehavior::PC_CONSOLE_DETECT: return "pc_console_detect"; @@ -69,12 +171,12 @@ const char* name_for_server_behavior(ServerBehavior behavior) { return "patch_server_bb"; case ServerBehavior::PROXY_SERVER: return "proxy_server"; - default: - throw logic_error("invalid server behavior"); } + throw logic_error("invalid server behavior"); } -ServerBehavior server_behavior_for_name(const char* name) { +template <> +ServerBehavior enum_for_name(const char* name) { if (!strcasecmp(name, "pc_console_detect")) { return ServerBehavior::PC_CONSOLE_DETECT; } else if (!strcasecmp(name, "login_server") || !strcasecmp(name, "login")) { @@ -94,36 +196,50 @@ ServerBehavior server_behavior_for_name(const char* name) { } } -uint32_t default_specific_version_for_version(GameVersion version, int64_t sub_version) { - uint32_t base_specific_version = (static_cast(version) + '0') << 24; - if (version == GameVersion::GC) { - // For versions that don't support send_function_call by default, we need - // to set the specific_version based on sub_version. Fortunately, all - // versions that share sub_version values also support send_function_call, - // so for those versions we get the specific_version later by sending the - // VersionDetect call. - switch (sub_version) { - case 0x36: // GC Ep1&2 US v1.02 (Plus) - return 0x334F4532; // 3OE2 - case 0x39: // GC Ep1&2 JP v1.05 (Plus) - return 0x334F4A35; // 3OJ5 - case 0x41: // GC Ep3 US - return 0x33534530; // 3SE0 - case 0x43: // GC Ep3 EU - return 0x33535030; // 3SP0 - case -1: // Initial check (before sub_version recognition) - case 0x30: // GC Ep1&2 JP v1.02, at least one version of PSO XB - case 0x31: // GC Ep1&2 US v1.00, GC US v1.01, GC EU v1.00, GC JP v1.00 - case 0x32: // GC Ep1&2 EU 50Hz - case 0x33: // GC Ep1&2 EU 60Hz - case 0x34: // GC Ep1&2 JP v1.03 - case 0x35: // GC Ep1&2 JP v1.04 (Plus) - case 0x40: // GC Ep3 trial - case 0x42: // GC Ep3 JP - default: - return base_specific_version; - } - } else { - return base_specific_version; +uint32_t default_specific_version_for_version(Version version, int64_t sub_version) { + // For versions that don't support send_function_call by default, we need + // to set the specific_version based on sub_version. Fortunately, all + // versions that share sub_version values also support send_function_call, + // so for those versions we get the specific_version later by sending the + // VersionDetect call. + switch (version) { + case Version::GC_NTE: + return 0x334F4A54; // 3OJT + case Version::GC_V3: + switch (sub_version) { + case 0x32: // GC Ep1&2 EU 50Hz + case 0x33: // GC Ep1&2 EU 60Hz + return 0x334F5030; // 3OP0 + case 0x36: // GC Ep1&2 US v1.02 (Plus) + return 0x334F4532; // 3OE2 + case 0x39: // GC Ep1&2 JP v1.05 (Plus) + return 0x334F4A35; // 3OJ5 + case 0x34: // GC Ep1&2 JP v1.03 + return 0x334F4A33; // 3OJ3 + case 0x35: // GC Ep1&2 JP v1.04 (Plus) + return 0x334F4A34; // 3OJ4 + case -1: // Initial check (before sub_version recognition) + case 0x30: // GC Ep1&2 GameJam demo, GC Ep1&2 Trial Edition, GC Ep1&2 JP v1.02, at least one version of PSO XB + case 0x31: // GC Ep1&2 US v1.00, GC US v1.01, XB US + default: + return 0x33000000; + } + throw logic_error("this should be impossible"); + case Version::GC_EP3_TRIAL_EDITION: + return 0x33534A54; // 3SJT + case Version::GC_EP3: + switch (sub_version) { + case 0x41: // GC Ep3 US + return 0x33534530; // 3SE0 + case 0x42: // GC Ep3 EU 50Hz + case 0x43: // GC Ep3 EU 60Hz + return 0x33535030; // 3SP0 + case -1: // Initial check (before sub_version recognition) + case 0x40: // GC Ep3 trial and GC Ep3 JP + default: + return 0x33000000; + } + default: + return 0x00000000; } } diff --git a/src/Version.hh b/src/Version.hh index 93e3aac6..12e59425 100644 --- a/src/Version.hh +++ b/src/Version.hh @@ -2,18 +2,104 @@ #include +#include #include #include -enum class GameVersion { - PATCH = 0, - DC = 1, - PC = 2, - GC = 3, - XB = 4, - BB = 5, +enum class Version { + PC_PATCH = 0, + BB_PATCH = 1, + DC_NTE = 2, + DC_V1_12_2000_PROTOTYPE = 3, + DC_V1 = 4, + DC_V2 = 5, + PC_V2 = 6, + GC_NTE = 7, + GC_V3 = 8, + GC_EP3_TRIAL_EDITION = 9, + GC_EP3 = 10, + XB_V3 = 11, + BB_V4 = 12, + UNKNOWN = 15, }; +template <> +const char* name_for_enum(Version v); +template <> +Version enum_for_name(const char* name); + +inline bool is_patch(Version version) { + return (version == Version::PC_PATCH) || (version == Version::BB_PATCH); +} +inline bool is_v1(Version version) { + return (version == Version::DC_NTE) || (version == Version::DC_V1_12_2000_PROTOTYPE) || (version == Version::DC_V1); +} +inline bool is_v2(Version version) { + return (version == Version::DC_V2) || (version == Version::PC_V2) || (version == Version::GC_NTE); +} +inline bool is_v1_or_v2(Version version) { + return is_v1(version) || is_v2(version); +} +inline bool is_v3(Version version) { + return (version == Version::GC_V3) || + (version == Version::GC_EP3_TRIAL_EDITION) || + (version == Version::GC_EP3) || + (version == Version::XB_V3); +} +inline bool is_v4(Version version) { + return (version == Version::BB_V4); +} + +inline bool is_ep3(Version version) { + return (version == Version::GC_EP3_TRIAL_EDITION) || (version == Version::GC_EP3); +} + +inline bool is_dc(Version version) { + return (version == Version::DC_NTE) || + (version == Version::DC_V1_12_2000_PROTOTYPE) || + (version == Version::DC_V1) || + (version == Version::DC_V2); +} +inline bool is_gc(Version version) { + return (version == Version::GC_NTE) || + (version == Version::GC_V3) || + (version == Version::GC_EP3_TRIAL_EDITION) || + (version == Version::GC_EP3); +} + +inline bool is_big_endian(Version version) { + return (version == Version::GC_NTE) || + (version == Version::GC_V3) || + (version == Version::GC_EP3_TRIAL_EDITION) || + (version == Version::GC_EP3); +} +inline bool uses_v2_encryption(Version version) { + return (version == Version::PC_PATCH) || + (version == Version::BB_PATCH) || + (version == Version::DC_NTE) || + (version == Version::DC_V1) || + (version == Version::DC_V2) || + (version == Version::PC_V2) || + (version == Version::GC_NTE); +} +inline bool uses_v3_encryption(Version version) { + return (version == Version::GC_V3) || + (version == Version::XB_V3) || + (version == Version::GC_EP3); +} +inline bool uses_v4_encryption(Version version) { + return (version == Version::BB_V4); +} + +inline bool uses_utf16(Version version) { + return (version == Version::PC_PATCH) || + (version == Version::BB_PATCH) || + (version == Version::PC_V2) || + (version == Version::BB_V4); +} + +uint32_t default_specific_version_for_version(Version version, int64_t sub_version); + enum class ServerBehavior { PC_CONSOLE_DETECT = 0, LOGIN_SERVER, @@ -24,14 +110,11 @@ enum class ServerBehavior { PROXY_SERVER, }; -extern const std::vector version_to_login_port_name; -extern const std::vector version_to_lobby_port_name; -extern const std::vector version_to_proxy_port_name; +const char* login_port_name_for_version(Version v); +const char* lobby_port_name_for_version(Version v); +const char* proxy_port_name_for_version(Version v); -const char* name_for_version(GameVersion version); -GameVersion version_for_name(const char* name); - -const char* name_for_server_behavior(ServerBehavior behavior); -ServerBehavior server_behavior_for_name(const char* name); - -uint32_t default_specific_version_for_version(GameVersion version, int64_t sub_version); +template <> +const char* name_for_enum(ServerBehavior behavior); +template <> +ServerBehavior enum_for_name(const char* name); diff --git a/src/WordSelectTable.cc b/src/WordSelectTable.cc index af87d7e2..0b1a0f4c 100644 --- a/src/WordSelectTable.cc +++ b/src/WordSelectTable.cc @@ -44,23 +44,25 @@ WordSelectTable::WordSelectTable(const JSON& json) { } } -uint16_t WordSelectTable::Token::value_for_version(QuestScriptVersion version) const { +uint16_t WordSelectTable::Token::value_for_version(Version version) const { switch (version) { - case QuestScriptVersion::DC_NTE: - case QuestScriptVersion::DC_V1: - case QuestScriptVersion::DC_V2: + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: return this->dc_value; - case QuestScriptVersion::PC_V2: + case Version::PC_V2: return this->pc_value; - // TODO: Which index does GC_NTE use? Here we presume it's the same as GC, - // but this may not be true - case QuestScriptVersion::GC_NTE: - case QuestScriptVersion::GC_V3: - case QuestScriptVersion::XB_V3: + case Version::GC_NTE: + case Version::GC_V3: + case Version::XB_V3: + // TODO: Which index does GC_NTE use? Here we presume it's the same as GC, + // but this may not be true return this->gc_value; - case QuestScriptVersion::GC_EP3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: return this->ep3_value; - case QuestScriptVersion::BB_V4: + case Version::BB_V4: return this->bb_value; default: throw logic_error("invalid word select version"); @@ -69,29 +71,31 @@ uint16_t WordSelectTable::Token::value_for_version(QuestScriptVersion version) c WordSelectMessage WordSelectTable::translate( const WordSelectMessage& msg, - QuestScriptVersion from_version, - QuestScriptVersion to_version) const { + Version from_version, + Version to_version) const { const std::vector* index; switch (from_version) { - case QuestScriptVersion::DC_NTE: - case QuestScriptVersion::DC_V1: - case QuestScriptVersion::DC_V2: + case Version::DC_NTE: + case Version::DC_V1_12_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: index = &this->dc_index; break; - case QuestScriptVersion::PC_V2: + case Version::PC_V2: index = &this->pc_index; break; - // TODO: Which index does GC_NTE use? Here we presume it's the same as GC, - // but this may not be true - case QuestScriptVersion::GC_NTE: - case QuestScriptVersion::GC_V3: - case QuestScriptVersion::XB_V3: + case Version::GC_NTE: + case Version::GC_V3: + case Version::XB_V3: + // TODO: Which index does GC_NTE use? Here we presume it's the same as GC, + // but this may not be true index = &this->gc_index; break; - case QuestScriptVersion::GC_EP3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: index = &this->ep3_index; break; - case QuestScriptVersion::BB_V4: + case Version::BB_V4: index = &this->bb_index; break; default: diff --git a/src/WordSelectTable.hh b/src/WordSelectTable.hh index a795fd0c..132ad66e 100644 --- a/src/WordSelectTable.hh +++ b/src/WordSelectTable.hh @@ -15,8 +15,8 @@ public: WordSelectMessage translate( const WordSelectMessage& msg, - QuestScriptVersion from_version, - QuestScriptVersion to_version) const; + Version from_version, + Version to_version) const; private: struct Token { @@ -26,7 +26,7 @@ private: uint16_t ep3_value; uint16_t bb_value; - uint16_t value_for_version(QuestScriptVersion version) const; + uint16_t value_for_version(Version version) const; }; std::vector dc_index; std::vector pc_index; diff --git a/tests/GC-Episode3Battle.test.txt b/tests/GC-Episode3Battle.test.txt index 9636d5b8..6c719705 100644 --- a/tests/GC-Episode3Battle.test.txt +++ b/tests/GC-Episode3Battle.test.txt @@ -58,7 +58,7 @@ I 16332 2023-09-17 10:14:34 - [Commands] Received from C-1 (version=GC command=9 0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 -0010 | 30 45 53 33 40 91 00 42 60 00 00 00 00 00 00 00 | 0ES3@ ` +0010 | 30 45 53 33 00 91 00 42 60 00 00 00 00 00 00 00 | 0ES3@ ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (version=GC command=B7 flag=00) 0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 | @@ -2619,7 +2619,7 @@ I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (version=GC command=CC f 0500 | 00 00 00 00 00 00 00 00 00 00 00 00 | I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 -0010 | 30 45 53 33 40 91 00 4A 60 00 00 00 00 00 00 00 | 0ES3@ ` +0010 | 30 45 53 33 00 91 00 4A 60 00 00 00 00 00 00 00 | 0ES3@ ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 16332 2023-09-17 10:14:34 - [Commands] Received from C-1 (Tali) (version=GC command=99 flag=00) 0000 | 99 00 04 00 | @@ -2627,7 +2627,7 @@ I 16332 2023-09-17 10:14:34 - [Commands] Received from C-1 (Tali) (version=GC co 0000 | D6 00 04 00 | I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (Tali) (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 -0010 | 30 45 53 33 40 91 00 48 60 00 00 00 00 00 00 00 | 0ES3@ ` +0010 | 30 45 53 33 00 91 00 48 60 00 00 00 00 00 00 00 | 0ES3@ ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (Tali) (version=GC command=07 flag=06) 0000 | 07 06 C8 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al @@ -2651,7 +2651,7 @@ I 16332 2023-09-17 10:14:35 - [Commands] Sending to C-1 (Tali) (version=GC comma 0000 | 97 01 04 00 | I 16332 2023-09-17 10:14:35 - [Commands] Sending to C-1 (Tali) (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 -0010 | 30 45 53 33 40 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` +0010 | 30 45 53 33 00 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 16332 2023-09-17 10:14:35 - [Commands] Received from C-1 (Tali) (version=GC command=B1 flag=00) 0000 | B1 00 04 00 | @@ -2702,11 +2702,11 @@ I 16332 2023-09-17 10:14:37 - [Commands] Received from C-2 (version=GC command=9 00A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00B0 | 00 00 00 00 00 00 00 00 00 00 00 00 54 61 6C 69 | Tali 00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 32 AC 99 83 | , 2 -00D0 | 30 45 53 33 40 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` +00D0 | 30 45 53 33 00 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` 00E0 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 16332 2023-09-17 10:14:37 - [Commands] Sending to C-2 (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 -0010 | 30 45 53 33 40 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` +0010 | 30 45 53 33 00 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 16332 2023-09-17 10:14:37 - [Commands] Sending to C-2 (version=GC command=83 flag=14) 0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 | @@ -3584,7 +3584,7 @@ I 16332 2023-09-17 10:14:37 - [Commands] Sending to C-2 (Tali) (version=GC comma 0440 | FF FF FF FF FF FF FF FF FF FF FF FF | I 16332 2023-09-17 10:14:37 - [Commands] Sending to C-2 (Tali) (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 -0010 | 30 45 53 33 40 93 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` +0010 | 30 45 53 33 00 93 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 16332 2023-09-17 10:14:39 - [Commands] Received from C-2 (Tali) (version=GC command=60 flag=00) 0000 | 60 00 1C 00 3F 06 00 00 00 00 00 00 0F 00 FF FF | ` ? diff --git a/tests/GC-Episode3BattleWithSpectator.test.txt b/tests/GC-Episode3BattleWithSpectator.test.txt index 709d71c5..7560d159 100644 --- a/tests/GC-Episode3BattleWithSpectator.test.txt +++ b/tests/GC-Episode3BattleWithSpectator.test.txt @@ -58,7 +58,7 @@ I 17097 2023-09-19 21:52:47 - [Commands] Received from C-1 (version=GC command=9 0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 -0010 | 30 45 53 33 40 91 00 42 60 00 00 00 00 00 00 00 | 0ES3@ ` +0010 | 30 45 53 33 00 91 00 42 60 00 00 00 00 00 00 00 | 0ES3@ ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=B7 flag=00) 0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 | @@ -2619,7 +2619,7 @@ I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=CC f 0500 | 00 00 00 00 00 00 00 00 00 00 00 00 | I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 -0010 | 30 45 53 33 40 91 00 4A 60 00 00 00 00 00 00 00 | 0ES3@ ` +0010 | 30 45 53 33 00 91 00 4A 60 00 00 00 00 00 00 00 | 0ES3@ ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:52:47 - [Commands] Received from C-1 (Tali) (version=GC command=99 flag=00) 0000 | 99 00 04 00 | @@ -2627,7 +2627,7 @@ I 17097 2023-09-19 21:52:47 - [Commands] Received from C-1 (Tali) (version=GC co 0000 | D6 00 04 00 | I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (Tali) (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 -0010 | 30 45 53 33 40 91 00 48 60 00 00 00 00 00 00 00 | 0ES3@ ` +0010 | 30 45 53 33 00 91 00 48 60 00 00 00 00 00 00 00 | 0ES3@ ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (Tali) (version=GC command=07 flag=06) 0000 | 07 06 C8 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al @@ -2651,7 +2651,7 @@ I 17097 2023-09-19 21:52:48 - [Commands] Sending to C-1 (Tali) (version=GC comma 0000 | 97 01 04 00 | I 17097 2023-09-19 21:52:48 - [Commands] Sending to C-1 (Tali) (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 -0010 | 30 45 53 33 40 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` +0010 | 30 45 53 33 00 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:52:49 - [Commands] Received from C-1 (Tali) (version=GC command=B1 flag=00) 0000 | B1 00 04 00 | @@ -2702,11 +2702,11 @@ I 17097 2023-09-19 21:52:50 - [Commands] Received from C-2 (version=GC command=9 00A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00B0 | 00 00 00 00 00 00 00 00 00 00 00 00 54 61 6C 69 | Tali 00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 32 AC 99 83 | , 2 -00D0 | 30 45 53 33 40 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` +00D0 | 30 45 53 33 00 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` 00E0 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:52:50 - [Commands] Sending to C-2 (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 -0010 | 30 45 53 33 40 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` +0010 | 30 45 53 33 00 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:52:50 - [Commands] Sending to C-2 (version=GC command=04 flag=00) 0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 | @@ -3584,7 +3584,7 @@ I 17097 2023-09-19 21:52:50 - [Commands] Sending to C-2 (Tali) (version=GC comma 0440 | FF FF FF FF FF FF FF FF FF FF FF FF | I 17097 2023-09-19 21:52:50 - [Commands] Sending to C-2 (Tali) (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 -0010 | 30 45 53 33 40 93 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` +0010 | 30 45 53 33 00 93 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:52:52 - [Commands] Received from C-2 (Tali) (version=GC command=60 flag=00) 0000 | 60 00 1C 00 3F 06 00 00 00 00 00 00 0F 00 FF FF | ` ? @@ -5869,7 +5869,7 @@ I 17097 2023-09-19 21:53:53 - [Commands] Received from C-3 (version=GC command=9 0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 32 AC 99 83 | , """"2 -0010 | 00 00 00 33 40 A1 00 42 60 00 00 00 00 00 00 00 | 3@ ` +0010 | 00 00 00 33 00 A1 00 42 60 00 00 00 00 00 00 00 | 3@ ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (version=GC command=B7 flag=00) 0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 | @@ -8430,7 +8430,7 @@ I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (version=GC command=CC f 0500 | 00 00 00 00 00 00 00 00 00 00 00 00 | I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 32 AC 99 83 | , """"2 -0010 | 00 00 00 33 40 A1 00 4A 60 00 00 00 00 00 00 00 | 3@ ` +0010 | 30 4A 53 33 00 A1 00 4A 60 00 00 00 00 00 00 00 | 3@ ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:53:53 - [Commands] Received from C-3 (Tali) (version=GC command=99 flag=00) 0000 | 99 00 04 00 | @@ -8438,7 +8438,7 @@ I 17097 2023-09-19 21:53:53 - [Commands] Received from C-3 (Tali) (version=GC co 0000 | D6 00 04 00 | I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (Tali) (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 32 AC 99 83 | , """"2 -0010 | 00 00 00 33 40 A1 00 48 60 00 00 00 00 00 00 00 | 3@ ` +0010 | 30 4A 53 33 00 A1 00 48 60 00 00 00 00 00 00 00 | 3@ ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (Tali) (version=GC command=07 flag=08) 0000 | 07 06 C8 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al @@ -8462,7 +8462,7 @@ I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (Tali) (version=GC comma 0000 | 97 01 04 00 | I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (Tali) (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 32 AC 99 83 | , """"2 -0010 | 00 00 00 33 40 A1 00 4C 60 00 00 00 00 00 00 00 | 3@ ` +0010 | 30 4A 53 33 00 A1 00 4C 60 00 00 00 00 00 00 00 | 3@ ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:53:53 - [Commands] Received from C-3 (Tali) (version=GC command=B1 flag=00) 0000 | B1 00 04 00 | @@ -8513,11 +8513,11 @@ I 17097 2023-09-19 21:53:54 - [Commands] Received from C-4 (version=GC command=9 00A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00B0 | 00 00 00 00 00 00 00 00 00 00 00 00 54 61 6C 69 | Tali 00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 32 AC 99 83 | , """"2 -00D0 | 00 00 00 33 40 A1 00 4C 60 00 00 00 00 00 00 00 | 3@ ` +00D0 | 30 4A 53 33 00 A1 00 4C 60 00 00 00 00 00 00 00 | 3@ ` 00E0 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:53:54 - [Commands] Sending to C-4 (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 32 AC 99 83 | , """"2 -0010 | 00 00 00 33 40 A1 00 4C 60 00 00 00 00 00 00 00 | 3@ ` +0010 | 30 4A 53 33 00 A1 00 4C 60 00 00 00 00 00 00 00 | 3@ ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:53:54 - [Commands] Sending to C-4 (version=GC command=04 flag=00) 0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 | @@ -9395,7 +9395,7 @@ I 17097 2023-09-19 21:53:54 - [Commands] Sending to C-4 (Tali) (version=GC comma 0440 | FF FF FF FF FF FF FF FF FF FF FF FF | I 17097 2023-09-19 21:53:54 - [Commands] Sending to C-4 (Tali) (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 32 AC 99 83 | , """"2 -0010 | 00 00 00 33 40 A3 00 4C 60 00 00 00 00 00 00 00 | 3@ ` +0010 | 30 4A 53 33 00 A3 00 4C 60 00 00 00 00 00 00 00 | 3@ ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:53:54 - [Commands] Received from C-4 (Tali) (version=GC command=60 flag=00) 0000 | 60 00 1C 00 3F 06 00 00 00 00 00 00 0F 00 FF FF | ` ? diff --git a/tests/GC-ForestGame.test.txt b/tests/GC-ForestGame.test.txt index 74b52f0f..fc032dc9 100644 --- a/tests/GC-ForestGame.test.txt +++ b/tests/GC-ForestGame.test.txt @@ -58,7 +58,7 @@ I 49108 2023-05-26 16:18:01 - [Commands] Received from C-1 (version=GC command=9 0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | I 49108 2023-05-26 16:18:01 - [Commands] Sending to C-1 (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 -0010 | 00 00 00 33 00 01 00 42 60 00 00 00 00 00 00 00 | 3 ` +0010 | 30 50 4F 33 00 01 00 42 60 00 00 00 00 00 00 00 | 3 ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 49108 2023-05-26 16:18:01 - [Commands] Sending to C-1 (version=GC command=D5 flag=00) 0000 | D5 00 2C 00 59 6F 75 20 61 72 65 20 63 6F 6E 6E | , You are conn @@ -75,7 +75,7 @@ I 49108 2023-05-26 16:18:02 - [Commands] Received from C-1 (version=GC command=D 0000 | D6 00 04 00 | I 49108 2023-05-26 16:18:02 - [Commands] Sending to C-1 (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 -0010 | 00 00 00 33 00 01 00 40 60 00 00 00 00 00 00 00 | 3 ` +0010 | 30 50 4F 33 00 01 00 40 60 00 00 00 00 00 00 00 | 3 ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 49108 2023-05-26 16:18:02 - [Commands] Sending to C-1 (version=GC command=07 flag=08) 0000 | 07 06 C8 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al @@ -97,7 +97,7 @@ I 49108 2023-05-26 16:18:06 - [Commands] Sending to C-1 (version=GC command=97 f 0000 | 97 01 04 00 | I 49108 2023-05-26 16:18:06 - [Commands] Sending to C-1 (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 -0010 | 00 00 00 33 00 01 00 44 60 00 00 00 00 00 00 00 | 3 ` +0010 | 30 50 4F 33 00 01 00 44 60 00 00 00 00 00 00 00 | 3 ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 49108 2023-05-26 16:18:06 - [Commands] Received from C-1 (version=GC command=B1 flag=00) 0000 | B1 00 04 00 | @@ -148,11 +148,11 @@ I 49108 2023-05-26 16:18:08 - [Commands] Received from C-2 (version=GC command=9 00A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00B0 | 00 00 00 00 00 00 00 00 00 00 00 00 4A 65 73 73 | Jess 00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 32 AC 99 83 | , 2 -00D0 | 00 00 00 33 00 01 00 44 60 00 00 00 00 00 00 00 | 3 ` +00D0 | 30 50 4F 33 00 01 00 44 60 00 00 00 00 00 00 00 | 3 ` 00E0 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 49108 2023-05-26 16:18:08 - [Commands] Sending to C-2 (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 -0010 | 00 00 00 33 00 01 00 44 60 00 00 00 00 00 00 00 | 3 ` +0010 | 30 50 4F 33 00 01 00 44 60 00 00 00 00 00 00 00 | 3 ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 49108 2023-05-26 16:18:08 - [Commands] Sending to C-2 (version=GC command=83 flag=0F) 0000 | 83 0F B8 00 33 00 00 33 01 00 00 00 00 00 00 00 | 3 3 @@ -365,7 +365,7 @@ I 49108 2023-05-26 16:18:08 - [Commands] Sending to C-2 (Jess) (version=GC comma 0440 | 0D FF 05 03 01 01 00 04 00 FF FF FF | I 49108 2023-05-26 16:18:08 - [Commands] Sending to C-2 (Jess) (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 -0010 | 00 00 00 33 00 03 00 44 60 00 00 00 00 00 00 00 | 3 ` +0010 | 30 50 4F 33 00 03 00 44 60 00 00 00 00 00 00 00 | 3 ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 49108 2023-05-26 16:18:08 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 1C 00 3F 06 00 00 00 00 00 80 0F 00 FF FF | ` ? @@ -10122,7 +10122,7 @@ I 49108 2023-05-26 16:28:29 - [Commands] Received from C-3 (version=GC command=9 00A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00B0 | 00 00 00 00 00 00 00 00 00 00 00 00 4A 65 73 73 | Jess 00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 32 AC 99 83 | , 2 -00D0 | 00 00 00 33 00 03 00 44 60 00 00 00 00 00 00 00 | 3 ` +00D0 | 30 50 4F 33 00 03 00 44 60 00 00 00 00 00 00 00 | 3 ` 00E0 | 00 00 00 00 00 00 FF FF 80 FF FF FF 00 00 00 00 | 00F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0100 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | @@ -10132,7 +10132,7 @@ I 49108 2023-05-26 16:28:29 - [Commands] Received from C-3 (version=GC command=9 0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | I 49108 2023-05-26 16:28:29 - [Commands] Sending to C-3 (version=GC command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 -0010 | 00 00 00 33 00 03 00 44 60 00 00 00 00 00 00 00 | 3 ` +0010 | 30 50 4F 33 00 03 00 44 60 00 00 00 00 00 00 00 | 3 ` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 49108 2023-05-26 16:28:29 - [Commands] Sending to C-3 (version=GC command=07 flag=06) 0000 | 07 05 AC 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al diff --git a/tests/XB-ForestGame.test.txt b/tests/XB-ForestGame.test.txt index 9e9a62bd..ab4dcece 100644 --- a/tests/XB-ForestGame.test.txt +++ b/tests/XB-ForestGame.test.txt @@ -52,7 +52,7 @@ I 16496 2023-11-08 01:54:08 - [Commands] Received from C-1 (version=XB command=9 0020 | 00 00 00 00 | I 16496 2023-11-08 01:54:08 - [Commands] Sending to C-1 (version=XB command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 E2 D4 45 39 32 AC 99 83 | , E92 -0010 | 00 00 00 34 00 81 00 42 60 00 00 00 00 00 00 00 | 4 B` +0010 | 00 00 00 00 00 81 00 42 60 00 00 00 00 00 00 00 | 4 B` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 16496 2023-11-08 01:54:08 - [Commands] Sending to C-1 (version=XB command=D5 flag=00) 0000 | D5 00 2C 00 59 6F 75 20 61 72 65 20 63 6F 6E 6E | , You are conn @@ -69,7 +69,7 @@ I 16496 2023-11-08 01:54:13 - [Commands] Received from C-1 (version=XB command=D 0000 | D6 00 04 00 | I 16496 2023-11-08 01:54:13 - [Commands] Sending to C-1 (version=XB command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 E2 D4 45 39 32 AC 99 83 | , E92 -0010 | 00 00 00 34 00 81 00 40 60 00 00 00 00 00 00 00 | 4 @` +0010 | 00 00 00 00 00 81 00 40 60 00 00 00 00 00 00 00 | 4 @` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 16496 2023-11-08 01:54:13 - [Commands] Sending to C-1 (version=XB command=07 flag=05) 0000 | 07 04 90 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al @@ -87,7 +87,7 @@ I 16496 2023-11-08 01:54:15 - [Commands] Sending to C-1 (version=XB command=97 f 0000 | 97 01 04 00 | I 16496 2023-11-08 01:54:15 - [Commands] Sending to C-1 (version=XB command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 E2 D4 45 39 32 AC 99 83 | , E92 -0010 | 00 00 00 34 00 81 00 44 60 00 00 00 00 00 00 00 | 4 D` +0010 | 00 00 00 00 00 81 00 44 60 00 00 00 00 00 00 00 | 4 D` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 16496 2023-11-08 01:54:16 - [Commands] Received from C-1 (version=XB command=B1 flag=00) 0000 | B1 00 04 00 | @@ -149,12 +149,12 @@ I 16496 2023-11-08 01:54:17 - [Commands] Received from C-2 (version=XB command=9 I 16496 2023-11-08 01:54:17 - [Commands] Sending to C-2 (version=XB command=9F flag=00) 0000 | 9F 00 04 00 | I 16496 2023-11-08 01:54:18 - [Commands] Received from C-2 (version=XB command=9F flag=00) -0000 | 9F 00 24 00 32 AC 99 83 00 00 00 34 00 81 00 44 | $ 2 4 D +0000 | 9F 00 24 00 32 AC 99 83 00 00 00 00 00 81 00 44 | $ 2 4 D 0010 | 60 00 00 00 00 00 00 00 00 00 00 00 00 00 FF FF | ` 0020 | 80 FF FF FF | I 16496 2023-11-08 01:54:18 - [Commands] Sending to C-2 (version=XB command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 E2 D4 45 39 32 AC 99 83 | , E92 -0010 | 00 00 00 34 00 81 00 44 60 00 00 00 00 00 00 00 | 4 D` +0010 | 00 00 00 00 00 81 00 44 60 00 00 00 00 00 00 00 | 4 D` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 16496 2023-11-08 01:54:18 - [Commands] Sending to C-2 (version=XB command=83 flag=0F) 0000 | 83 0F B8 00 33 00 00 33 01 00 00 00 00 00 00 00 | 3 3 @@ -371,7 +371,7 @@ I 16496 2023-11-08 01:54:18 - [Commands] Sending to C-2 (Tali) (version=XB comma 0480 | FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | I 16496 2023-11-08 01:54:18 - [Commands] Sending to C-2 (Tali) (version=XB command=04 flag=00) 0000 | 04 00 2C 00 00 00 01 00 E2 D4 45 39 32 AC 99 83 | , E92 -0010 | 00 00 00 34 00 83 00 44 60 00 00 00 00 00 00 00 | 4 D` +0010 | 00 00 00 00 00 83 00 44 60 00 00 00 00 00 00 00 | 4 D` 0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 16496 2023-11-08 01:54:19 - [Commands] Received from C-2 (Tali) (version=XB command=60 flag=00) 0000 | 60 00 1C 00 3F 06 00 00 00 00 00 80 0F 00 FF FF | ` ? diff --git a/tests/compression.sh b/tests/compression.sh index 358089dc..ebe8e228 100755 --- a/tests/compression.sh +++ b/tests/compression.sh @@ -11,7 +11,7 @@ fi BASENAME="card-defs-test-$SCHEME" -echo "... decompress" +echo "... decompress-prs" $EXECUTABLE decompress-prs system/ep3/card-definitions.mnr $BASENAME.mnrd echo "... compress with level=-1 (no compression)" diff --git a/tests/decrypt-gci-save.test.sh b/tests/decrypt-gci-save.test.sh index 3015a404..01a22a8d 100755 --- a/tests/decrypt-gci-save.test.sh +++ b/tests/decrypt-gci-save.test.sh @@ -10,17 +10,17 @@ fi DIR="tests/saves-gci" echo "... decrypt Ep1&2 charfile" -$EXECUTABLE decrypt-gci-save $DIR/8P-GPOJ-PSO_CHARACTER.gci --sys=$DIR/8P-GPOJ-PSO_SYSTEM.gci +$EXECUTABLE decrypt-gci-save $DIR/8P-GPOJ-PSO_CHARACTER.gci $DIR/8P-GPOJ-PSO_CHARACTER.gcid --sys=$DIR/8P-GPOJ-PSO_SYSTEM.gci diff $DIR/8P-GPOJ-PSO_CHARACTER-expected.gcid $DIR/8P-GPOJ-PSO_CHARACTER.gcid echo "... decrypt Ep1&2 guildfile" -$EXECUTABLE decrypt-gci-save $DIR/8P-GPOJ-PSO_GUILDCARD.gci --sys=$DIR/8P-GPOJ-PSO_SYSTEM.gci +$EXECUTABLE decrypt-gci-save $DIR/8P-GPOJ-PSO_GUILDCARD.gci $DIR/8P-GPOJ-PSO_GUILDCARD.gcid --sys=$DIR/8P-GPOJ-PSO_SYSTEM.gci diff $DIR/8P-GPOJ-PSO_GUILDCARD-expected.gcid $DIR/8P-GPOJ-PSO_GUILDCARD.gcid echo "... decrypt Ep3 charfile" -$EXECUTABLE decrypt-gci-save $DIR/8P-GPSJ-PSO3_CHARACTER.gci --sys=$DIR/8P-GPSJ-PSO3_SYSTEM.gci +$EXECUTABLE decrypt-gci-save $DIR/8P-GPSJ-PSO3_CHARACTER.gci $DIR/8P-GPSJ-PSO3_CHARACTER.gcid --sys=$DIR/8P-GPSJ-PSO3_SYSTEM.gci diff $DIR/8P-GPSJ-PSO3_CHARACTER-expected.gcid $DIR/8P-GPSJ-PSO3_CHARACTER.gcid echo "... decrypt Ep3 guildfile" -$EXECUTABLE decrypt-gci-save $DIR/8P-GPSJ-PSO3_GUILDCARD.gci --sys=$DIR/8P-GPSJ-PSO3_SYSTEM.gci +$EXECUTABLE decrypt-gci-save $DIR/8P-GPSJ-PSO3_GUILDCARD.gci $DIR/8P-GPSJ-PSO3_GUILDCARD.gcid --sys=$DIR/8P-GPSJ-PSO3_SYSTEM.gci diff $DIR/8P-GPSJ-PSO3_GUILDCARD-expected.gcid $DIR/8P-GPSJ-PSO3_GUILDCARD.gcid echo "... clean up"