diff --git a/README.md b/README.md index 03e5393c..db0d2465 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ newserv supports several versions of PSO. Specifically: | DC 08/2001 | Untested (1) | Untested (1) | Untested (1) | | DC V2 | Yes | Yes | Yes | | PC | Yes | Yes | Yes | -| GC Ep1&2 Trial | Yes | Yes | No | +| GC Ep1&2 Trial | Yes | Yes | Yes | | GC Ep1&2 | Yes | Yes | Yes | | GC Ep1&2 Plus | Yes | Yes | Yes | | GC Ep3 Trial | Yes | Partial (3) | Yes | @@ -94,7 +94,7 @@ Within the category directories, quest files should be named like `q###-VERSION- For .dat files, the `LANGUAGE` token may be omitted. If it's present, then that .dat file will only be used for that language of the quest; if omitted, then that .dat file will be used for all languages of the quest. -Some quests (mostly battle and challenge mode quests) have additional JSON metadata files that describe how the server should handle them. This includes flags that can be used to hide the quest unless a preceding quest has been cleared, or to hide the quest unless purchased as a team reward. These metadata files are generally named similarly to their .bin and .dat counterparts, except the `VERSION` token may also be omitted if the metadata applies to all versions of the quest on all PSO versions. See system/quests/battle/b88001.json for documentation on the exact format of the JSON file. +Some quests (mostly battle and challenge mode quests) have additional JSON metadata files that describe how the server should handle them. This includes flags that can be used to hide the quest unless a preceding quest has been cleared, or to hide the quest unless purchased as a team reward. These metadata files are generally named similarly to their .bin and .dat counterparts, except the `VERSION` token may also be omitted if the metadata applies to all languages of the quest on all PSO versions. See system/quests/battle/b88001.json for documentation on the exact format of the JSON file. Some quests may also include a .pvr file, which contains an image used in the quest. These files are named similarly to their .bin and .dat counterparts. diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index 40ab22bb..29c3c31f 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -1072,6 +1072,7 @@ static HandlerResult C_GXB_61(shared_ptr ses, uint16 pd = reinterpret_cast(&ep3_pd); } else { if (is_ep3(ses->version())) { + ses->log.info("Version changed to GC_EP3_TRIAL_EDITION"); ses->set_version(Version::GC_EP3_TRIAL_EDITION); } pd = &check_size_t(data, 0xFFFF); diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index 8bc42f08..e99a2715 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -239,8 +239,7 @@ ProxyServer::UnlinkedSession::UnlinkedSession( string_printf("UnlinkedSession:%p", bev), TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN), - local_port(local_port), - version(version) { + local_port(local_port) { memset(&this->next_destination, 0, sizeof(this->next_destination)); } @@ -264,40 +263,46 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 bool should_close_unlinked_session = false; try { - 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"); - } - 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; - } - + switch (ses->version()) { + case Version::DC_NTE: case Version::DC_V1_11_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) { + case Version::GC_NTE: + // We should only get an 8B, 93 or 9D while the session is unlinked + if (command == 0x8B) { + ses->channel.version = Version::DC_NTE; + ses->log.info("Version changed to DC_NTE"); + 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 + } else if (command == 0x93) { // 11/2000 proto through DC V1 + ses->channel.version = Version::DC_V1; + ses->log.info("Version changed to DC_V1"); 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()); + if (cmd.sub_version >= 0x30) { + ses->log.info("Version changed to GC_NTE"); + ses->channel.version = Version::GC_NTE; + ses->license = s->license_index->verify_gc(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode()); + } else { // DC V2 + ses->log.info("Version changed to DC_V2"); + ses->channel.version = Version::DC_V2; + 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->config.set_flags_for_version(ses->version(), cmd.sub_version); } else { throw runtime_error("command is not 93 or 9D"); } @@ -316,20 +321,11 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 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) { + // We should only get a 9E while the session is unlinked + 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; @@ -337,7 +333,8 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 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; + ses->log.info("Version changed to GC_EP3"); + ses->channel.version = Version::GC_EP3; } } else { throw runtime_error("command is not 9D or 9E"); @@ -425,10 +422,10 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 // destination somewhere - either in the client config or in the unlinked // session if (ses->config.proxy_destination_address != 0) { - linked_ses = make_shared(server, ses->local_port, ses->version, ses->license, ses->config); + linked_ses = make_shared(server, ses->local_port, ses->version(), ses->license, ses->config); linked_ses->log.info("Opened licensed session for unlinked session based on client config"); } else if (ses->next_destination.ss_family == AF_INET) { - linked_ses = make_shared(server, ses->local_port, ses->version, ses->license, ses->next_destination); + linked_ses = make_shared(server, ses->local_port, ses->version(), ses->license, ses->next_destination); linked_ses->log.info("Opened licensed session for unlinked session based on unlinked default destination"); } else { ses->log.error("Cannot open linked session: no valid destination in client config or unlinked session"); @@ -437,12 +434,12 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 if (linked_ses.get()) { server->id_to_session.emplace(ses->license->serial_number, linked_ses); - if (linked_ses->version() != ses->version) { + if (linked_ses->version() != ses->version()) { linked_ses->log.error("Linked session has different game version"); } else { // Resume the linked session using the unlinked session try { - if (ses->version == Version::BB_V4) { + if (ses->version() == Version::BB_V4) { linked_ses->resume( std::move(ses->channel), ses->detector_crypt, diff --git a/src/ProxyServer.hh b/src/ProxyServer.hh index 0039b687..59a56b96 100644 --- a/src/ProxyServer.hh +++ b/src/ProxyServer.hh @@ -219,7 +219,6 @@ private: PrefixedLogger log; Channel channel; uint16_t local_port; - Version version; struct sockaddr_storage next_destination; std::shared_ptr detector_crypt; @@ -242,6 +241,10 @@ private: std::shared_ptr require_server() const; std::shared_ptr require_server_state() const; + inline Version version() const { + return this->channel.version; + } + void receive_and_process_commands(); static void on_input(Channel& ch, uint16_t command, uint32_t flag, std::string& msg); diff --git a/src/ServerState.cc b/src/ServerState.cc index ea6ca914..dee5c6fd 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -370,10 +370,10 @@ const vector>& ServerState::proxy_destinations_for_versio case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1: case Version::DC_V2: + case Version::GC_NTE: return this->proxy_destinations_dc; case Version::PC_V2: return this->proxy_destinations_pc; - case Version::GC_NTE: case Version::GC_V3: case Version::GC_EP3_TRIAL_EDITION: case Version::GC_EP3: diff --git a/src/Version.cc b/src/Version.cc index 2dbcfb9a..7b3573b4 100644 --- a/src/Version.cc +++ b/src/Version.cc @@ -70,8 +70,8 @@ const char* proxy_port_name_for_version(Version v) { case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1: case Version::DC_V2: - return "dc-proxy"; case Version::GC_NTE: + return "dc-proxy"; case Version::GC_V3: case Version::GC_EP3_TRIAL_EDITION: case Version::GC_EP3: diff --git a/src/Version.hh b/src/Version.hh index a23c6085..681ed813 100644 --- a/src/Version.hh +++ b/src/Version.hh @@ -84,8 +84,9 @@ inline bool uses_v2_encryption(Version version) { } inline bool uses_v3_encryption(Version version) { return (version == Version::GC_V3) || - (version == Version::XB_V3) || - (version == Version::GC_EP3); + (version == Version::GC_EP3_TRIAL_EDITION) || + (version == Version::GC_EP3) || + (version == Version::XB_V3); } inline bool uses_v4_encryption(Version version) { return (version == Version::BB_V4); diff --git a/system/config.example.json b/system/config.example.json index db562bb5..71122f81 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -147,6 +147,9 @@ // version, the proxy server is disabled for that version. Entries in these // dictionaries should be of the form "name": "address:port"; the names are // used in the proxy server menu. + // Note that PSO GameCube Episodes 1&2 Trial Edition uses the DC's + // ProxyDestinations dictionary here. This is because other servers that + // support that version treat it as PSO DC v2. "ProxyDestinations-DC": {}, "ProxyDestinations-PC": {}, "ProxyDestinations-GC": {},