diff --git a/src/Client.cc b/src/Client.cc index ae0f21e2..44338db8 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -15,6 +15,10 @@ using namespace std; +static const uint64_t CLIENT_CONFIG_MAGIC = 0x492A890E82AC9839; + + + Client::Client( struct bufferevent* bev, GameVersion version, @@ -61,3 +65,27 @@ bool Client::send(string&& data) { evbuffer_add(buf, data.data(), data.size()); return true; } + +ClientConfig Client::export_config() const { + ClientConfig cc; + cc.magic = CLIENT_CONFIG_MAGIC; + cc.bb_game_state = this->bb_game_state; + cc.bb_player_index = this->bb_player_index; + cc.flags = this->flags; + for (size_t x = 0; x < 5; x++) { + cc.unused[x] = 0xFFFFFFFF; + } + for (size_t x = 0; x < 2; x++) { + cc.unused_bb_only[x] = 0xFFFFFFFF; + } + return cc; +} + +void Client::import_config(const ClientConfig& cc) { + if (cc.magic != CLIENT_CONFIG_MAGIC) { + throw invalid_argument("invalid client config"); + } + this->bb_game_state = cc.bb_game_state; + this->bb_player_index = cc.bb_player_index; + this->flags = cc.flags; +} diff --git a/src/Client.hh b/src/Client.hh index 0f902c64..e68ba35a 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -19,24 +19,24 @@ enum class ServerBehavior { }; struct ClientConfig { - uint32_t magic; // must be set to 0x48615467 - uint8_t bb_game_state; // status of client connecting on BB - uint8_t bb_player_index; // selected char - uint16_t flags; // just in case we lose them somehow between connections - uint16_t ports[4]; // used by shipgate clients - uint32_t unused[4]; -}; - -struct ClientConfigBB { - ClientConfig cfg; - uint32_t unused[2]; -}; + uint64_t magic; + uint8_t bb_game_state; + uint8_t bb_player_index; + uint16_t flags; + uint32_t unused[5]; + uint32_t unused_bb_only[2]; +} __attribute__((packed)); struct Client { // License & account std::shared_ptr license; - ClientConfigBB config; GameVersion version; + + // Note: these fields are included in the client config. On GC, the client + // config can be up to 0x20 bytes; on BB it can be 0x28 bytes. We don't use + // all of that space. + uint8_t bb_game_state; + uint8_t bb_player_index; uint16_t flags; // Encryption @@ -78,4 +78,7 @@ struct Client { // adds data to the client's output buffer, encrypting it first bool send(std::string&& data); + + ClientConfig export_config() const; + void import_config(const ClientConfig& cc); }; diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index c76fb6e6..2fbe148d 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -90,7 +90,7 @@ void process_connect(std::shared_ptr s, std::shared_ptr c) } case ServerBehavior::LoginServer: { - if (!s->welcome_message.empty() && !(c->flags & ClientFlag::NoMessageBoxCloseConfirmation)) { + if (!s->welcome_message.empty()) { c->flags |= ClientFlag::AtWelcomeMessage; } send_server_init(s, c, true); @@ -121,7 +121,7 @@ void process_login_complete(shared_ptr s, shared_ptr c) { send_ep3_rank_update(c); } - if (s->welcome_message.empty()) { + if (s->welcome_message.empty() || (c->flags & ClientFlag::NoMessageBoxCloseConfirmation)) { c->flags &= ~ClientFlag::AtWelcomeMessage; send_menu(c, s->name.c_str(), MAIN_MENU_ID, s->main_menu, false); } else { @@ -139,10 +139,10 @@ void process_login_complete(shared_ptr s, shared_ptr c) { c->player.load_account_data("system/blueburst/default.nsa"); } - sprintf(c->player.bank_name, "player%d", c->config.cfg.bb_player_index + 1); + sprintf(c->player.bank_name, "player%d", c->bb_player_index + 1); string player_filename = filename_for_player_bb(c->license->username, - c->config.cfg.bb_player_index); + c->bb_player_index); try { c->player.load_player_data(player_filename); } catch (const exception&) { @@ -181,7 +181,7 @@ void process_disconnect(shared_ptr s, shared_ptr c) { c->player.disp.play_time += ((now() - c->play_time_begin) / 1000000); string account_filename = filename_for_account_bb(c->license->username); string player_filename = filename_for_player_bb(c->license->username, - c->config.cfg.bb_player_index); + c->bb_player_index); string bank_filename = filename_for_bank_bb(c->license->username, c->player.bank_name); c->player.save_account_data(account_filename); @@ -325,8 +325,11 @@ void process_login_d_e_pc_gc(shared_ptr s, shared_ptr c, char access_key[0x10]; char unused3[0x60]; char name[0x10]; + // Note: there are 8 bytes at the end of cfg that are technically not + // included in the client config on GC, but the field after it is + // sufficiently large and unused anyway ClientConfig cfg; - uint8_t unused4[0x64]; + uint8_t unused4[0x5C]; }; // sometimes the unused bytes aren't sent? check_size(size, sizeof(Cmd) - 0x64, sizeof(Cmd)); @@ -361,11 +364,11 @@ void process_login_d_e_pc_gc(shared_ptr s, shared_ptr c, } } - memcpy(&c->config.cfg, &cmd->cfg, sizeof(ClientConfig)); - - if (c->config.cfg.magic != CONFIG_MAGIC) { - memset(&c->config, 0, sizeof(ClientConfigBB)); - c->config.cfg.magic = CONFIG_MAGIC; + try { + c->import_config(cmd->cfg); + } catch (const invalid_argument&) { + c->bb_game_state = 0; + c->bb_player_index = 0; } send_update_client_config(c); @@ -381,7 +384,7 @@ void process_login_bb(shared_ptr s, shared_ptr c, char unused2[0x20]; char password[0x10]; char unused3[0x30]; - ClientConfigBB cfg; + ClientConfig cfg; }; check_size(size, sizeof(Cmd)); const auto* cmd = reinterpret_cast(data); @@ -397,18 +400,17 @@ void process_login_bb(shared_ptr s, shared_ptr c, return; } - memcpy(&c->config, &cmd->cfg, sizeof(ClientConfigBB)); - - if (c->config.cfg.magic != CONFIG_MAGIC) { - memset(&c->config, 0, sizeof(ClientConfigBB)); - c->config.cfg.magic = CONFIG_MAGIC; - } else { - c->config.cfg.bb_game_state++; + try { + c->import_config(cmd->cfg); + c->bb_game_state++; + } catch (const invalid_argument&) { + c->bb_game_state = 0; + c->bb_player_index = 0; } send_client_init_bb(c, 0); - switch (c->config.cfg.bb_game_state) { + switch (c->bb_game_state) { case ClientStateBB::InitialLogin: // first login? send them to the other port send_reconnect(c, s->connect_address_for_client(c), @@ -871,7 +873,7 @@ void process_change_lobby(shared_ptr s, shared_ptr c, try { new_lobby = s->find_lobby(cmd->item_id); } catch (const out_of_range&) { - send_lobby_message_box(c, u"$C6Can't change lobby\n\n$C7The lobby does not exist."); + send_lobby_message_box(c, u"$C6Can't change lobby\n\n$C7The lobby does not\nexist."); return; } @@ -992,9 +994,9 @@ void process_gba_file_request(shared_ptr, shared_ptr c, // player data commands void process_player_data(shared_ptr s, shared_ptr c, - uint16_t command, uint32_t, uint16_t size, const void* data) { // 61 98 + uint16_t command, uint32_t flag, uint16_t size, const void* data) { // 61 98 - // note: we add extra buffer on the end when checking sizes because the + // Note: we add extra buffer on the end when checking sizes because the // autoreply text is a variable length switch (c->version) { case GameVersion::PC: @@ -1002,7 +1004,12 @@ void process_player_data(shared_ptr s, shared_ptr c, c->player.import(*reinterpret_cast(data)); break; case GameVersion::GC: - check_size(size, sizeof(PSOPlayerDataGC), sizeof(PSOPlayerDataGC) + 0xAC); + if (flag == 4) { // Episode 3 + check_size(size, sizeof(PSOPlayerDataGC) + 0x23FC); + // TODO: import Episode 3 data somewhere + } else { + check_size(size, sizeof(PSOPlayerDataGC), sizeof(PSOPlayerDataGC) + 0xAC); + } c->player.import(*reinterpret_cast(data)); break; case GameVersion::BB: @@ -1151,9 +1158,9 @@ void process_player_preview_request_bb(shared_ptr, shared_ptr(data); - if (c->config.cfg.bb_game_state == ClientStateBB::ChoosePlayer) { - c->config.cfg.bb_player_index = cmd->player_index; - c->config.cfg.bb_game_state++; + if (c->bb_game_state == ClientStateBB::ChoosePlayer) { + c->bb_player_index = cmd->player_index; + c->bb_game_state++; send_client_init_bb(c, 0); send_approve_player_choice_bb(c); } else { @@ -1162,7 +1169,7 @@ void process_player_preview_request_bb(shared_ptr, shared_ptrlicense->username, - c->config.cfg.bb_player_index); + c->bb_player_index); try { // generate a preview @@ -1235,7 +1242,7 @@ void process_create_character_bb(shared_ptr s, shared_ptr c return; } - c->config.cfg.bb_player_index = cmd->player_index; + c->bb_player_index = cmd->player_index; snprintf(c->player.bank_name, 0x20, "player%" PRIu32, cmd->player_index + 1); string player_filename = filename_for_player_bb(c->license->username, cmd->player_index); string bank_filename = filename_for_bank_bb(c->license->username, c->player.bank_name); @@ -2033,7 +2040,8 @@ static process_command_t gc_handlers[0x100] = { process_ep3_menu_challenge, nullptr, nullptr, nullptr, // E0 - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, + process_ignored_command, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, process_create_game_dc_gc, nullptr, nullptr, nullptr, diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 11ef049b..d1738166 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -242,7 +242,7 @@ void send_update_client_config(shared_ptr c) { } cmd = { 0x00010000, c->license->serial_number, - c->config.cfg, + c->export_config(), }; send_command(c, 0x04, 0x00, cmd); } @@ -295,14 +295,14 @@ void send_client_init_bb(shared_ptr c, uint32_t error) { uint32_t player_tag; uint32_t serial_number; uint32_t team_id; // just randomize it; teams aren't supported - ClientConfigBB cfg; + ClientConfig cfg; uint32_t caps; // should be 0x00000102 } cmd = { error, 0x00010000, c->license->serial_number, static_cast(random_object()), - c->config, + c->export_config(), 0x00000102, }; send_command(c, 0x00E6, 0x00000000, cmd); @@ -440,7 +440,7 @@ void send_approve_player_choice_bb(shared_ptr c) { struct { uint32_t player_index; uint32_t unused; - } cmd = {c->config.cfg.bb_player_index, 1}; + } cmd = {c->bb_player_index, 1}; send_command(c, 0x00E4, 0x00000000, cmd); } @@ -1286,9 +1286,9 @@ void send_quest_menu(shared_ptr c, uint32_t menu_id, } void send_lobby_list(shared_ptr c, shared_ptr s) { - // this command appeast to be deprecated, as PSO expects it to be exactly how + // This command appears to be deprecated, as PSO expects it to be exactly how // this server sends it, and does not react if it's different, except by - // changing the lobby IDs + // changing the lobby IDs. struct Entry { uint32_t menu_id; @@ -1301,7 +1301,7 @@ void send_lobby_list(shared_ptr c, shared_ptr s) { if (!(l->flags & LobbyFlag::Default)) { continue; } - if (!(c->flags & ClientFlag::Episode3Games) != !(l->flags & LobbyFlag::Episode3)) { + if ((l->flags & LobbyFlag::Episode3) && !(c->flags & ClientFlag::Episode3Games)) { continue; } @@ -1643,6 +1643,14 @@ void send_join_lobby(shared_ptr c, shared_ptr l) { throw logic_error("unimplemented versioned command"); } } + + // If the client will stop sending message box close confirmations after + // joining any lobby, set the appropriate flag and update the client config + if ((c->flags & (ClientFlag::NoMessageBoxCloseConfirmationAfterLobbyJoin | ClientFlag::NoMessageBoxCloseConfirmation)) + == ClientFlag::NoMessageBoxCloseConfirmationAfterLobbyJoin) { + c->flags |= ClientFlag::NoMessageBoxCloseConfirmation; + send_update_client_config(c); + } } diff --git a/src/Version.hh b/src/Version.hh index 6617bab2..4e58414a 100644 --- a/src/Version.hh +++ b/src/Version.hh @@ -13,24 +13,26 @@ enum class GameVersion { }; enum ClientFlag { - // after joining a lobby, client will no longer send D6 commands when they close message boxes + // After joining a lobby, client will no longer send D6 commands when they close message boxes NoMessageBoxCloseConfirmationAfterLobbyJoin = 0x0004, - // client has the above flag and has already joined a lobby + // Client has the above flag and has already joined a lobby NoMessageBoxCloseConfirmation = 0x0008, - // client can see Ep3 lobbies + // Client can see Ep3 lobbies CanSeeExtraLobbies = 0x0010, - // client is episode 3 and should use its game mechanic + // Client is episode 3 and should use its game mechanic Episode3Games = 0x0020, - // client is DC v1 (disables some features) + // Client is DC v1 (disables some features) IsDCv1 = 0x0040, - // client is loading into a game + // Client is loading into a game Loading = 0x0080, - // client is in the information menu (login server only) + // Client is in the information menu (login server only) InInformationMenu = 0x0100, - // client is at the welcome message (login server only) + // Client is at the welcome message (login server only) AtWelcomeMessage = 0x0200, + // Note: There isn't a good way to detect Episode 3 until the player data is + // sent (via a 61 command), so the Episode3Games flag is set in that handler DefaultV1 = IsDCv1, DefaultV2DC = 0x0000, DefaultV2PC = 0x0000,