fix GC NTE proxy behavior

This commit is contained in:
Martin Michelsen
2023-12-03 16:23:56 -08:00
parent b8d4ab589e
commit fbda7a2a48
8 changed files with 50 additions and 45 deletions
+2 -2
View File
@@ -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.
+1
View File
@@ -1072,6 +1072,7 @@ static HandlerResult C_GXB_61(shared_ptr<ProxyServer::LinkedSession> ses, uint16
pd = reinterpret_cast<C_CharacterData_V3_61_98*>(&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<C_CharacterData_V3_61_98>(data, 0xFFFF);
+35 -38
View File
@@ -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<C_Login_DCNTE_8B>(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<C_Login_DCNTE_8B>(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<C_LoginV1_DC_93>(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<C_Login_DC_PC_GC_9D>(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<C_Login_DC_PC_GC_9D>(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<C_Login_GC_9E>(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<LinkedSession>(server, ses->local_port, ses->version, ses->license, ses->config);
linked_ses = make_shared<LinkedSession>(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<LinkedSession>(server, ses->local_port, ses->version, ses->license, ses->next_destination);
linked_ses = make_shared<LinkedSession>(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,
+4 -1
View File
@@ -219,7 +219,6 @@ private:
PrefixedLogger log;
Channel channel;
uint16_t local_port;
Version version;
struct sockaddr_storage next_destination;
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
@@ -242,6 +241,10 @@ private:
std::shared_ptr<ProxyServer> require_server() const;
std::shared_ptr<ServerState> 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);
+1 -1
View File
@@ -370,10 +370,10 @@ const vector<pair<string, uint16_t>>& 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:
+1 -1
View File
@@ -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:
+3 -2
View File
@@ -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);
+3
View File
@@ -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": {},