From 76fd9c22bf68c021bd4246bb1e1ca3f9bbace3b9 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Fri, 1 Apr 2022 23:44:12 -0700 Subject: [PATCH] get PSO PC login sequence working --- src/Client.hh | 2 +- src/CommandFormats.hh | 47 +++++++----- src/Menu.hh | 14 ++-- src/ProxyServer.cc | 162 ++++++++++++++++++++++++++++++++--------- src/ProxyServer.hh | 7 +- src/ReceiveCommands.cc | 112 ++++++++++++++++++---------- src/SendCommands.cc | 33 ++++----- src/SendCommands.hh | 6 +- 8 files changed, 263 insertions(+), 120 deletions(-) diff --git a/src/Client.hh b/src/Client.hh index 52883029..5e93eb12 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -67,7 +67,7 @@ struct Client { // sent (via a 61 command), so the IS_EPISODE_3 flag is set in that handler DEFAULT_V1 = DCV1, DEFAULT_V2_DC = 0x0000, - DEFAULT_V2_PC = 0x0000, + DEFAULT_V2_PC = NO_MESSAGE_BOX_CLOSE_CONFIRMATION, DEFAULT_V3_GC = 0x0000, DEFAULT_V3_GC_PLUS = NO_MESSAGE_BOX_CLOSE_CONFIRMATION_AFTER_LOBBY_JOIN, DEFAULT_V3_GC_EP3 = NO_MESSAGE_BOX_CLOSE_CONFIRMATION_AFTER_LOBBY_JOIN | EPISODE_3, diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 59321344..e94ea57f 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -47,7 +47,7 @@ struct SC_TextHeader_01_06_11_B0 { // 02 (S->C): Start encryption (except on BB) // Client will respond with an (encrypted) 9A, 9D, or 9E command -struct S_ServerInit_DC_GC_02_17 { +struct S_ServerInit_DC_PC_GC_02_17 { ptext copyright; le_uint32_t server_key; // Key for data sent by server le_uint32_t client_key; // Key for data sent by client @@ -313,15 +313,15 @@ struct SC_GameCardCheck_BB_22 { struct C_GuildCardSearch_40 { le_uint32_t player_tag; - le_uint32_t searcher_serial_number; - le_uint32_t target_serial_number; + le_uint32_t searcher_guild_card_number; + le_uint32_t target_guild_card_number; } __attribute__((packed)); template struct S_GuildCardSearchResult { le_uint32_t player_tag; - le_uint32_t searcher_serial_number; - le_uint32_t result_serial_number; + le_uint32_t searcher_guild_card_number; + le_uint32_t result_guild_card_number; HeaderT reconnect_command_header; S_Reconnect_19 reconnect_command; ptext location_string; @@ -534,9 +534,9 @@ struct S_LeaveLobby_66_69 { struct SC_SimpleMail_GC_81 { le_uint32_t player_tag; - le_uint32_t from_serial_number; + le_uint32_t from_guild_card_number; ptext from_name; - le_uint32_t to_serial_number; + le_uint32_t to_guild_card_number; ptext text; }; @@ -568,7 +568,7 @@ struct C_LobbySelection_84 { // Command is a list of these; header.flag is the entry count struct S_ArrowUpdateEntry_88 { le_uint32_t player_tag; - le_uint32_t serial_number; + le_uint32_t guild_card_number; le_uint32_t arrow_color; }; @@ -635,6 +635,12 @@ struct C_Login_DC_PC_GC_9A { ptext unused; ptext serial_number; ptext access_key; + uint32_t player_tag; + uint32_t guild_card_number; + uint32_t sub_version; + ptext serial_number2; + ptext access_key2; + ptext email_address; }; // 9B: Invalid command @@ -654,12 +660,9 @@ struct C_Register_DC_PC_GC_9C { ptext password; }; -// 9D (C->S): Log in with client config -// Same as 9E? (We treat them identicall in newserv) +// 9D (C->S): Log in -// 9E (C->S): Log in with client config - -struct C_Login_PC_GC_9D_9E { +struct C_Login_PC_9D { le_uint32_t player_tag; // 00 00 01 00 if guild card is set (via 04) le_uint32_t guild_card_number; // FF FF FF FF if not set le_uint64_t unused; @@ -670,13 +673,23 @@ struct C_Login_PC_GC_9D_9E { ptext serial_number2; ptext access_key2; ptext name; +}; +struct C_LoginWithUnusedSpace_PC_9D : C_Login_PC_9D { + parray unused_space; +}; + +// 9E (C->S): Log in with client config + +// This struct is identical to PC's 9D command, but it has more data at the end +struct C_Login_GC_9E : C_Login_PC_9D { union ClientConfigFields { ClientConfig cfg; parray data; - ClientConfigFields() : data() { } } client_config; - parray unused4; +}; +struct C_LoginWithUnusedSpace_GC_9E : C_Login_GC_9E { + parray unused_space; }; // 9F: Invalid command @@ -1186,7 +1199,7 @@ struct S_SendGuildCard_GC { uint8_t subsize; le_uint16_t unused; le_uint32_t player_tag; - le_uint32_t serial_number; + le_uint32_t guild_card_number; ptext name; ptext desc; uint8_t reserved1; @@ -1199,7 +1212,7 @@ struct S_SendGuildCard_BB { uint8_t subcommand; uint8_t subsize; le_uint16_t unused; - le_uint32_t serial_number; + le_uint32_t guild_card_number; ptext name; ptext team_name; ptext desc; diff --git a/src/Menu.hh b/src/Menu.hh index 0e2fa7f4..9c0ea482 100644 --- a/src/Menu.hh +++ b/src/Menu.hh @@ -29,14 +29,12 @@ struct MenuItem { INVISIBLE_ON_DC = 0x01, INVISIBLE_ON_PC = 0x02, INVISIBLE_ON_GC = 0x04, - INVISIBLE_ON_GC_EPISODE_3 = 0x08, - INVISIBLE_ON_BB = 0x10, - DC_ONLY = INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_GC_EPISODE_3 | INVISIBLE_ON_BB, - PC_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_GC | INVISIBLE_ON_GC_EPISODE_3 | INVISIBLE_ON_BB, - GC_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_GC_EPISODE_3 | INVISIBLE_ON_BB, - GC_EPISODE_3_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_BB, - BB_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_GC_EPISODE_3, - REQUIRES_MESSAGE_BOXES = 0x00010000, + INVISIBLE_ON_BB = 0x08, + DC_ONLY = INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_BB, + PC_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_GC | INVISIBLE_ON_BB, + GC_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_BB, + BB_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_GC, + REQUIRES_MESSAGE_BOXES = 0x10, }; uint32_t item_id; diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index 26fa18ce..9d058d43 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -93,8 +93,8 @@ ProxyServer::ListeningSocket::ListeningSocket( } void ProxyServer::ListeningSocket::dispatch_on_listen_accept( - struct evconnlistener*, evutil_socket_t, struct sockaddr* address, int socklen, void* ctx) { - reinterpret_cast(ctx)->on_listen_accept(address, socklen); + struct evconnlistener*, evutil_socket_t fd, struct sockaddr*, int, void* ctx) { + reinterpret_cast(ctx)->on_listen_accept(fd); } void ProxyServer::ListeningSocket::dispatch_on_listen_error( @@ -102,7 +102,7 @@ void ProxyServer::ListeningSocket::dispatch_on_listen_error( reinterpret_cast(ctx)->on_listen_error(); } -void ProxyServer::ListeningSocket::on_listen_accept(struct sockaddr*, int fd) { +void ProxyServer::ListeningSocket::on_listen_accept(int fd) { log(INFO, "[ProxyServer] Client connected on fd %d", fd); auto* bev = bufferevent_socket_new(this->server->base.get(), fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); @@ -158,7 +158,7 @@ void ProxyServer::on_client_connect( throw logic_error("linked session already exists for unlicensed client"); } auto session = emplace_ret.first->second; - log(INFO, "[ProxyServer/%08" PRIX64 "] Opened session", session->id); + log(INFO, "[ProxyServer/%08" PRIX64 "] Opened linked session", session->id); session->resume(bev); // If no default destination exists, create an unlinked session - we'll have @@ -171,6 +171,7 @@ void ProxyServer::on_client_connect( throw logic_error("stale unlinked session exists"); } auto session = emplace_ret.first->second; + log(INFO, "[ProxyServer] Opened unlinked session"); switch (version) { case GameVersion::PATCH: @@ -181,11 +182,23 @@ void ProxyServer::on_client_connect( uint32_t client_key = random_object(); auto cmd = prepare_server_init_contents_dc_pc_gc( false, server_key, client_key); - send_command(session->bev.get(), session->version, - session->crypt_out.get(), 0x02, 0, &cmd, sizeof(cmd), + send_command( + session->bev.get(), + session->version, + session->crypt_out.get(), + 0x02, + 0, + &cmd, + sizeof(cmd), "unlinked proxy client"); - session->crypt_out.reset(new PSOGCEncryption(server_key)); - session->crypt_in.reset(new PSOGCEncryption(client_key)); + bufferevent_flush(session->bev.get(), EV_READ | EV_WRITE, BEV_FLUSH); + if (version == GameVersion::PC) { + session->crypt_out.reset(new PSOPCEncryption(server_key)); + session->crypt_in.reset(new PSOPCEncryption(client_key)); + } else { + session->crypt_out.reset(new PSOGCEncryption(server_key)); + session->crypt_in.reset(new PSOGCEncryption(client_key)); + } break; } default: @@ -230,17 +243,41 @@ void ProxyServer::UnlinkedSession::on_client_input() { print_received_command(command, flag, data.data(), data.size(), this->version, "unlinked proxy client"); - if (this->version == GameVersion::GC) { - // We should really only get a 9E while the session is unlinked; if we - // get anything else, disconnect - if (command != 0x9E) { + if (this->version == GameVersion::PC) { + // We should only get a 9D while the session is unlinked; if we get + // anything else, disconnect + if (command != 0x9D) { log(ERROR, "[ProxyServer] Received unexpected command %02hX", command); should_close_unlinked_session = true; - } else if (data.size() < sizeof(C_Login_PC_GC_9D_9E) - 0x64) { + } else if (data.size() < sizeof(C_Login_GC_9E) - 0x64) { log(ERROR, "[ProxyServer] Login command is too small"); should_close_unlinked_session = true; } else { - const auto* cmd = reinterpret_cast(data.data()); + const auto* cmd = reinterpret_cast(data.data()); + uint32_t serial_number = strtoul(cmd->serial_number.c_str(), nullptr, 16); + try { + license = this->server->state->license_manager->verify_pc( + serial_number, cmd->access_key.c_str(), nullptr); + sub_version = cmd->sub_version; + character_name = cmd->name; + client_config = cmd->client_config.cfg; + } catch (const exception& e) { + log(ERROR, "[ProxyServer] Unlinked client has no valid license"); + should_close_unlinked_session = true; + } + } + + } else if (this->version == GameVersion::GC) { + // We should only get a 9E while the session is unlinked; if we get + // anything else, disconnect + if (command != 0x9E) { + log(ERROR, "[ProxyServer] Received unexpected command %02hX", command); + should_close_unlinked_session = true; + } else if (data.size() < sizeof(C_Login_GC_9E) - 0x64) { + log(ERROR, "[ProxyServer] Login command is too small"); + should_close_unlinked_session = true; + } else { + const auto* cmd = reinterpret_cast(data.data()); uint32_t serial_number = strtoul(cmd->serial_number.c_str(), nullptr, 16); try { license = this->server->state->license_manager->verify_gc( @@ -253,6 +290,9 @@ void ProxyServer::UnlinkedSession::on_client_input() { should_close_unlinked_session = true; } } + + } else { + throw logic_error("unsupported unlinked session version"); } }); @@ -269,6 +309,7 @@ void ProxyServer::UnlinkedSession::on_client_input() { shared_ptr session; try { session = this->server->id_to_session.at(license->serial_number); + log(INFO, "[ProxyServer/%08" PRIX64 "] Resuming linked session from unlinked session", session->id); } catch (const out_of_range&) { // If there's no open session for this license, then there must be a valid @@ -284,7 +325,7 @@ void ProxyServer::UnlinkedSession::on_client_input() { license, client_config)); this->server->id_to_session.emplace(license->serial_number, session); - log(INFO, "[ProxyServer/%08" PRIX64 "] Opened session", session->id); + log(INFO, "[ProxyServer/%08" PRIX64 "] Opened licensed session for unlinked session", session->id); } } @@ -355,6 +396,7 @@ ProxyServer::LinkedSession::LinkedSession( std::shared_ptr license, const ClientConfig& newserv_client_config) : LinkedSession(server, license->serial_number, local_port, version) { + this->license = license; this->newserv_client_config = newserv_client_config; memset(&this->next_destination, 0, sizeof(this->next_destination)); struct sockaddr_in* dest_sin = reinterpret_cast(&this->next_destination); @@ -701,13 +743,16 @@ void ProxyServer::LinkedSession::on_server_input() { // Most servers don't include after_message or have a shorter // after_message than newserv does, so don't require it - if (data.size() < offsetof(S_ServerInit_DC_GC_02_17, after_message)) { + if (data.size() < offsetof(S_ServerInit_DC_PC_GC_02_17, after_message)) { throw std::runtime_error("init encryption command is too small"); } - const auto* cmd = reinterpret_cast( + const auto* cmd = reinterpret_cast( data.data()); if (!this->license) { + log(INFO, "[ProxyServer/%08" PRIX64 "] No license in linked session", + this->id); + if ((this->version == GameVersion::PC) || (this->version == GameVersion::PATCH)) { this->server_input_crypt.reset(new PSOPCEncryption(cmd->server_key)); this->server_output_crypt.reset(new PSOPCEncryption(cmd->client_key)); @@ -724,6 +769,9 @@ void ProxyServer::LinkedSession::on_server_input() { break; } else { + log(INFO, "[ProxyServer/%08" PRIX64 "] Existing license in linked session", + this->id); + // This doesn't get forwarded to the client, so don't recreate the // client's crypts if (this->version == GameVersion::PATCH) { @@ -740,26 +788,56 @@ void ProxyServer::LinkedSession::on_server_input() { should_forward = false; - // If this is a 17, respond with a DB; otherwise respond with a 9E. - // We don't let the client do this because it believes it already - // did (when it was in an unlinked session). - if (command == 0x17) { - C_VerifyLicense_GC_DB cmd; + // Respond with an appropriate login command. We don't let the + // client do this because it believes it already did (when it was + // in an unlinked session). + if (this->version == GameVersion::PC) { + C_Login_PC_9D cmd; + if (this->guild_card_number == 0) { + cmd.player_tag = 0xFFFF0000; + cmd.guild_card_number = 0xFFFFFFFF; + } else { + cmd.player_tag = 0x00010000; + cmd.guild_card_number = this->guild_card_number; + } + cmd.unused = 0xFFFFFFFFFFFF0000; + cmd.sub_version = this->sub_version; + cmd.unused2.data()[1] = 1; cmd.serial_number = string_printf("%08" PRIX32 "", this->license->serial_number); cmd.access_key = this->license->access_key; - cmd.sub_version = this->sub_version; cmd.serial_number2 = cmd.serial_number; cmd.access_key2 = cmd.access_key; - cmd.password = this->license->gc_password; + cmd.name = this->character_name; send_command(this->server_bev.get(), this->version, - this->server_output_crypt.get(), 0xDB, 0, &cmd, sizeof(cmd), + this->server_output_crypt.get(), 0x9D, 0, &cmd, sizeof(cmd), name.c_str()); break; + + } else if (this->version == GameVersion::GC) { + if (command == 0x17) { + C_VerifyLicense_GC_DB cmd; + cmd.serial_number = string_printf("%08" PRIX32 "", + this->license->serial_number); + cmd.access_key = this->license->access_key; + cmd.sub_version = this->sub_version; + cmd.serial_number2 = cmd.serial_number; + cmd.access_key2 = cmd.access_key; + cmd.password = this->license->gc_password; + send_command(this->server_bev.get(), this->version, + this->server_output_crypt.get(), 0xDB, 0, &cmd, sizeof(cmd), + name.c_str()); + break; + + } else if (command == 0x02) { + // Command 02 should be handled like 9A at this point (we + // should send a 9E in response) + [[fallthrough]]; + } + + } else { + throw logic_error("invalid game version in server init handler"); } - // Command 02 should be handled like 9A at this point (we should - // send a 9E in response) - [[fallthrough]]; } } @@ -767,9 +845,11 @@ void ProxyServer::LinkedSession::on_server_input() { if (!this->license) { break; } + if (this->version != GameVersion::GC) { + throw runtime_error("9A received in non-GC session"); + } should_forward = false; - C_Login_PC_GC_9D_9E cmd; - + C_LoginWithUnusedSpace_GC_9E cmd; if (this->guild_card_number == 0) { cmd.player_tag = 0xFFFF0000; cmd.guild_card_number = 0xFFFFFFFF; @@ -796,16 +876,12 @@ void ProxyServer::LinkedSession::on_server_input() { 0x9E, 0x01, &cmd, - this->guild_card_number ? offsetof(C_Login_PC_GC_9D_9E, unused4) : sizeof(cmd), + sizeof(C_LoginWithUnusedSpace_GC_9E) - (this->guild_card_number ? sizeof(cmd.unused_space) : 0), name.c_str()); break; } case 0x04: { - if (this->version != GameVersion::GC) { - break; - } - // Some servers send a short 04 command if they don't use all of the // 0x20 bytes available. We should be prepared to handle that. if (data.size() < offsetof(S_UpdateClientConfig_DC_PC_GC_04, cfg)) { @@ -900,7 +976,7 @@ void ProxyServer::LinkedSession::on_server_input() { case 0x1A: case 0xD5: { - if (this->version != GameVersion::PATCH) { + if (this->version != GameVersion::GC) { break; } @@ -1228,6 +1304,20 @@ shared_ptr ProxyServer::get_session() { return this->id_to_session.begin()->second; } +std::shared_ptr ProxyServer::create_licensed_session( + std::shared_ptr l, uint16_t local_port, GameVersion version, + const ClientConfig& newserv_client_config) { + shared_ptr session(new LinkedSession( + this, local_port, version, l, newserv_client_config)); + auto emplace_ret = this->id_to_session.emplace(session->id, session); + if (!emplace_ret.second) { + throw runtime_error("session already exists for this license"); + } + log(INFO, "[ProxyServer/%08" PRIX64 "] Opening licensed session", session->id); + return emplace_ret.first->second; +} + + void ProxyServer::delete_session(uint64_t id) { if (this->id_to_session.erase(id)) { log(WARNING, "[ProxyServer/%08" PRIX64 "] Closed session", id); diff --git a/src/ProxyServer.hh b/src/ProxyServer.hh index 2be3d7be..f4cf6e2b 100644 --- a/src/ProxyServer.hh +++ b/src/ProxyServer.hh @@ -132,6 +132,11 @@ public: }; std::shared_ptr get_session(); + std::shared_ptr create_licensed_session( + std::shared_ptr l, + uint16_t local_port, + GameVersion version, + const ClientConfig& newserv_client_config); void delete_session(uint64_t id); bool save_files; @@ -155,7 +160,7 @@ private: static void dispatch_on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr *address, int socklen, void* ctx); static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx); - void on_listen_accept(struct sockaddr *address, int socklen); + void on_listen_accept(int fd); void on_listen_error(); }; diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index ce7637ac..a5deef01 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -247,6 +247,8 @@ void process_login_a_dc_pc_gc(shared_ptr s, shared_ptr c, uint16_t, uint32_t, const string& data) { // 9A const auto& cmd = check_size_t(data); + c->flags |= flags_for_version(c->version, cmd.sub_version); + uint32_t serial_number = strtoul(cmd.serial_number.c_str(), nullptr, 16); try { if (c->version == GameVersion::GC) { @@ -257,10 +259,10 @@ void process_login_a_dc_pc_gc(shared_ptr s, shared_ptr c, cmd.access_key.c_str(), nullptr); } } catch (const exception& e) { - // The client should have sent a different command containing the password - // already, which should have created and added a temporary license. If no - // license exists, disconnect the client even if unregistered clients are - // allowed. + // On GC, the client should have sent a different command containing the + // password already, which should have created and added a temporary + // license. So, if no license exists at this point, disconnect the client + // even if unregistered clients are allowed. u16string message = u"Login failed: " + decode_sjis(e.what()); send_message_box(c, message.c_str()); c->should_disconnect = true; @@ -309,21 +311,45 @@ void process_login_c_dc_pc_gc(shared_ptr s, shared_ptr c, } void process_login_d_e_pc_gc(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { // 9D 9E - // Sometimes the unused bytes aren't sent - const auto& cmd = check_size_t(data, - sizeof(C_Login_PC_GC_9D_9E) - 0x64, sizeof(C_Login_PC_GC_9D_9E)); + uint16_t command, uint32_t, const string& data) { // 9D 9E - c->flags |= flags_for_version(c->version, cmd.sub_version); + // The client sends extra unused data the first time it sends these commands, + // hence the odd check_size calls here - uint32_t serial_number = strtoul(cmd.serial_number.c_str(), nullptr, 16); + const C_Login_PC_9D* base_cmd; + if (command == 0x9D) { + base_cmd = &check_size_t(data, + sizeof(C_Login_PC_9D), sizeof(C_Login_PC_9D) + 0x84); + + } else if (command == 0x9E) { + const auto& cmd = check_size_t(data, + sizeof(C_Login_GC_9E), sizeof(C_Login_GC_9E) + 0x64); + base_cmd = &cmd; + + try { + c->import_config(cmd.client_config.cfg); + } catch (const invalid_argument&) { + // If we can't import the config, assume that the client was not connected + // to newserv before, so we should show the welcome message. + c->flags |= Client::Flag::AT_WELCOME_MESSAGE; + c->bb_game_state = 0; + c->bb_player_index = 0; + } + + } else { + throw logic_error("9D/9E handler called for incorrect command"); + } + + c->flags |= flags_for_version(c->version, base_cmd->sub_version); + + uint32_t serial_number = strtoul(base_cmd->serial_number.c_str(), nullptr, 16); try { if (c->version == GameVersion::GC) { c->license = s->license_manager->verify_gc(serial_number, - cmd.access_key.c_str(), nullptr); + base_cmd->access_key.c_str(), nullptr); } else { c->license = s->license_manager->verify_pc(serial_number, - cmd.access_key.c_str(), nullptr); + base_cmd->access_key.c_str(), nullptr); } } catch (const exception& e) { // See comment in 9A handler about why we do this even if unregistered users @@ -334,16 +360,6 @@ void process_login_d_e_pc_gc(shared_ptr s, shared_ptr c, return; } - try { - c->import_config(cmd.client_config.cfg); - } catch (const invalid_argument&) { - // If we can't import the config, assume that the client was not connected - // to newserv before, so we should show the welcome message. - c->flags |= Client::Flag::AT_WELCOME_MESSAGE; - c->bb_game_state = 0; - c->bb_player_index = 0; - } - if ((c->flags & Client::Flag::EPISODE_3) && (s->ep3_menu_song >= 0)) { send_ep3_change_music(c, s->ep3_menu_song); } @@ -726,17 +742,20 @@ void process_menu_selection(shared_ptr s, shared_ptr c, // the client to the proxy server instead (would have to provide // license/char name/etc. for remote auth) + static const vector version_to_port_name({ + "dc-proxy", "pc-proxy", "", "gc-proxy", "bb-proxy"}); + const auto& port_name = version_to_port_name.at(static_cast(c->version)); + uint16_t local_port = s->name_to_port_config.at(port_name)->port; + c->proxy_destination_address = resolve_ipv4(dest->first); c->proxy_destination_port = dest->second; send_update_client_config(c); s->proxy_server->delete_session(c->license->serial_number); + s->proxy_server->create_licensed_session( + c->license, local_port, c->version, c->export_config()); - static const vector version_to_port_name({ - "dc-proxy", "pc-proxy", "", "gc-proxy", "bb-proxy"}); - const auto& port_name = version_to_port_name.at(static_cast(c->version)); - send_reconnect(c, s->connect_address_for_client(c), - s->name_to_port_config.at(port_name)->port); + send_reconnect(c, s->connect_address_for_client(c), local_port); } } break; @@ -1362,7 +1381,7 @@ void process_card_search(shared_ptr s, shared_ptr c, uint16_t, uint32_t, const string& data) { // 40 const auto& cmd = check_size_t(data); try { - auto result = s->find_client(nullptr, cmd.target_serial_number); + auto result = s->find_client(nullptr, cmd.target_guild_card_number); auto result_lobby = s->find_lobby(result->lobby_id); send_card_search_result(s, c, result, result_lobby); } catch (const out_of_range&) { } @@ -1384,7 +1403,7 @@ void process_simple_mail(shared_ptr s, shared_ptr c, const auto& cmd = check_size_t(data); - auto target = s->find_client(nullptr, cmd.to_serial_number); + auto target = s->find_client(nullptr, cmd.to_guild_card_number); // If the sender is blocked, don't forward the mail for (size_t y = 0; y < 30; y++) { @@ -1710,20 +1729,39 @@ void process_login_patch(shared_ptr s, shared_ptr c, throw runtime_error("unknown patch server login format"); } - u16string message = u"\ + // 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. + u16string message; + if (c->flags & Client::Flag::BB_PATCH) { + message = u"\ $C7newserv patch server\n\ \n\ -This server is for private use only.\n\ This server is not affiliated with, sponsored by, or in any\n\ other way connected to SEGA or Sonic Team, and is owned\n\ and operated completely independently.\n\ -\n\ -License check: "; +\n"; + } else { + message = u"\ +newserv patch server\r\n\ +\r\n\ +This server is not affiliated with, sponsored by, or in any other way \ +connected to SEGA or Sonic Team, and is owned and operated completely \ +independently.\r\n\ +\r\n"; + } + message += u"License check "; try { - c->license = s->license_manager->verify_bb( - cmd.username.c_str(), cmd.password.c_str()); + if (c->flags & Client::Flag::BB_PATCH) { + c->license = s->license_manager->verify_bb( + cmd.username.c_str(), cmd.password.c_str()); + } else { + uint32_t serial_number = strtoul(cmd.username.c_str(), nullptr, 16); + c->license = s->license_manager->verify_pc( + serial_number, cmd.password.c_str(), nullptr); + } message += u"OK"; } catch (const exception& e) { + message += u"failed: "; message += decode_sjis(e.what()); } @@ -1735,8 +1773,8 @@ License check: "; send_command(c, 0x0A); send_command(c, 0x0A); - // this command terminates the patch connection successfully. PSO complains if - // we don't check the above directories though + // This command terminates the patch connection successfully. PSOBB complains + // if we don't check the above directories before sending this though send_command(c, 0x0012, 0x00000000); } diff --git a/src/SendCommands.cc b/src/SendCommands.cc index fcbee3c2..4f3ea6d1 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -154,11 +154,11 @@ static const char* dc_lobby_server_copyright = "DreamCast Lobby Server. Copyrigh static const char* bb_game_server_copyright = "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM."; static const char* patch_server_copyright = "Patch Server. Copyright SonicTeam, LTD. 2001"; -S_ServerInit_DC_GC_02_17 prepare_server_init_contents_dc_pc_gc( +S_ServerInit_DC_PC_GC_02_17 prepare_server_init_contents_dc_pc_gc( bool initial_connection, uint32_t server_key, uint32_t client_key) { - S_ServerInit_DC_GC_02_17 cmd; + S_ServerInit_DC_PC_GC_02_17 cmd; cmd.copyright = initial_connection ? dc_port_map_copyright : dc_lobby_server_copyright; cmd.server_key = server_key; @@ -169,9 +169,7 @@ S_ServerInit_DC_GC_02_17 prepare_server_init_contents_dc_pc_gc( void send_server_init_dc_pc_gc(shared_ptr c, bool initial_connection) { - // PC uses 17 for all server inits; GC uses it only for the first one - uint8_t command = (initial_connection || (c->version == GameVersion::PC)) - ? 0x17 : 0x02; + uint8_t command = initial_connection ? 0x17 : 0x02; uint32_t server_key = random_object(); uint32_t client_key = random_object(); @@ -475,7 +473,7 @@ void send_text_message(shared_ptr s, const char16_t* text) { } } -void send_chat_message(shared_ptr c, uint32_t from_serial_number, +void send_chat_message(shared_ptr c, uint32_t from_guild_card_number, const char16_t* from_name, const char16_t* text) { u16string data; if (c->version == GameVersion::BB) { @@ -484,24 +482,24 @@ void send_chat_message(shared_ptr c, uint32_t from_serial_number, data.append(remove_language_marker(from_name)); data.append(u"\x09\x09J"); data.append(text); - send_header_text(c, 0x06, from_serial_number, data.c_str()); + send_header_text(c, 0x06, from_guild_card_number, data.c_str()); } -void send_simple_mail_gc(std::shared_ptr c, uint32_t from_serial_number, +void send_simple_mail_gc(std::shared_ptr c, uint32_t from_guild_card_number, const char16_t* from_name, const char16_t* text) { SC_SimpleMail_GC_81 cmd; cmd.player_tag = 0x00010000; - cmd.from_serial_number = from_serial_number; + cmd.from_guild_card_number = from_guild_card_number; cmd.from_name = from_name; - cmd.to_serial_number = c->license->serial_number; + cmd.to_guild_card_number = c->license->serial_number; cmd.text = text; send_command(c, 0x81, 0x00, cmd); } -void send_simple_mail(std::shared_ptr c, uint32_t from_serial_number, +void send_simple_mail(std::shared_ptr c, uint32_t from_guild_card_number, const char16_t* from_name, const char16_t* text) { if (c->version == GameVersion::GC) { - send_simple_mail_gc(c, from_serial_number, from_name, text); + send_simple_mail_gc(c, from_guild_card_number, from_name, text); } else { throw logic_error("unimplemented versioned command"); } @@ -549,8 +547,8 @@ void send_card_search_result_t( shared_ptr result_lobby) { S_GuildCardSearchResult cmd; cmd.player_tag = 0x00010000; - cmd.searcher_serial_number = c->license->serial_number; - cmd.result_serial_number = result->license->serial_number; + cmd.searcher_guild_card_number = c->license->serial_number; + cmd.result_guild_card_number = result->license->serial_number; cmd.reconnect_command_header.size = sizeof(cmd.reconnect_command_header) + sizeof(cmd.reconnect_command); cmd.reconnect_command_header.command = 0x19; cmd.reconnect_command_header.flag = 0x00; @@ -608,7 +606,7 @@ void send_guild_card_gc(shared_ptr c, shared_ptr source) { cmd.player_tag = 0x00010000; cmd.reserved1 = 1; cmd.reserved2 = 1; - cmd.serial_number = source->license->serial_number; + cmd.guild_card_number = source->license->serial_number; cmd.name = source->player.disp.name; remove_language_marker_inplace(cmd.name); cmd.desc = source->player.guild_card_desc; @@ -624,7 +622,7 @@ void send_guild_card_bb(shared_ptr c, shared_ptr source) { cmd.unused = 0x0000; cmd.reserved1 = 1; cmd.reserved2 = 1; - cmd.serial_number = source->license->serial_number; + cmd.guild_card_number = source->license->serial_number; cmd.name = remove_language_marker(source->player.disp.name); cmd.team_name = remove_language_marker(source->player.team_name); cmd.desc = source->player.guild_card_desc; @@ -670,7 +668,6 @@ void send_menu_t( ((c->version == GameVersion::PC) && (item.flags & MenuItem::Flag::INVISIBLE_ON_PC)) || ((c->version == GameVersion::GC) && (item.flags & MenuItem::Flag::INVISIBLE_ON_GC)) || ((c->version == GameVersion::BB) && (item.flags & MenuItem::Flag::INVISIBLE_ON_BB)) || - ((c->flags & Client::Flag::EPISODE_3) && (item.flags & MenuItem::Flag::INVISIBLE_ON_GC_EPISODE_3)) || ((item.flags & MenuItem::Flag::REQUIRES_MESSAGE_BOXES) && (c->flags & Client::Flag::NO_MESSAGE_BOX_CLOSE_CONFIRMATION))) { continue; } @@ -1026,7 +1023,7 @@ void send_arrow_update(shared_ptr l) { } auto& e = entries.emplace_back(); e.player_tag = 0x00010000; - e.serial_number = l->clients[x]->license->serial_number; + e.guild_card_number = l->clients[x]->license->serial_number; e.arrow_color = l->clients[x]->lobby_arrow_color; } diff --git a/src/SendCommands.hh b/src/SendCommands.hh index efcf5945..3529d49e 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -63,8 +63,10 @@ void send_command(std::shared_ptr c, uint16_t command, uint32_t flag, -S_ServerInit_DC_GC_02_17 prepare_server_init_contents_dc_pc_gc( - bool initial_connection, uint32_t server_key, uint32_t client_key); +S_ServerInit_DC_PC_GC_02_17 prepare_server_init_contents_dc_pc_gc( + bool initial_connection, + uint32_t server_key, + uint32_t client_key); void send_server_init(std::shared_ptr s, std::shared_ptr c, bool initial_connection); void send_update_client_config(std::shared_ptr c);