diff --git a/src/Client.cc b/src/Client.cc index 8ff4e93a..acb8c555 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -28,6 +28,7 @@ Client::Client( bev(bev), server_behavior(server_behavior), should_disconnect(false), + should_send_to_lobby_server(false), proxy_destination_address(0), proxy_destination_port(0), play_time_begin(now()), diff --git a/src/Client.hh b/src/Client.hh index aad3d662..4c9c4106 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -25,11 +25,10 @@ struct Client { // After joining a lobby, client will no longer send D6 commands when they // close message boxes NO_MESSAGE_BOX_CLOSE_CONFIRMATION_AFTER_LOBBY_JOIN = 0x0002, - // Client has the above flag and has already joined a lobby, or is Blue Burst - // (BB never sends D6 commands) + // Client has the above flag and has already joined a lobby, or is not GC NO_MESSAGE_BOX_CLOSE_CONFIRMATION = 0x0004, - // Client is Episode 3, should be able to see CARD lobbies, and should only be - // able to see/join games with the IS_EPISODE_3 flag + // Client is Episode 3, should be able to see CARD lobbies, and should only + // be able to see/join games with the IS_EPISODE_3 flag EPISODE_3 = 0x0008, // Client is DC v1 (disables some features) DCV1 = 0x0010, @@ -43,10 +42,13 @@ struct Client { AT_WELCOME_MESSAGE = 0x0100, // Client disconnect if it receives B2 (send_function_call) DOES_NOT_SUPPORT_SEND_FUNCTION_CALL = 0x0200, + // Client has already received a 97 (enable saves) command, so don't show + // the programs menu anymore + SAVE_ENABLED = 0x0400, // TODO: Do DCv1 and PC support send_function_call? Here we assume they don't - DEFAULT_V1 = DCV1 | DOES_NOT_SUPPORT_SEND_FUNCTION_CALL, - DEFAULT_V2_DC = 0x0000, + DEFAULT_V1 = DCV1 | NO_MESSAGE_BOX_CLOSE_CONFIRMATION | DOES_NOT_SUPPORT_SEND_FUNCTION_CALL, + DEFAULT_V2_DC = NO_MESSAGE_BOX_CLOSE_CONFIRMATION, DEFAULT_V2_PC = NO_MESSAGE_BOX_CLOSE_CONFIRMATION | DOES_NOT_SUPPORT_SEND_FUNCTION_CALL, DEFAULT_V3_GC = 0x0000, DEFAULT_V3_GC_PLUS = NO_MESSAGE_BOX_CLOSE_CONFIRMATION_AFTER_LOBBY_JOIN | DOES_NOT_SUPPORT_SEND_FUNCTION_CALL, @@ -76,6 +78,7 @@ struct Client { ServerBehavior server_behavior; bool is_virtual_connection; bool should_disconnect; + bool should_send_to_lobby_server; uint32_t proxy_destination_address; uint16_t proxy_destination_port; diff --git a/src/Menu.hh b/src/Menu.hh index de0c6bd3..84391753 100644 --- a/src/Menu.hh +++ b/src/Menu.hh @@ -64,6 +64,7 @@ struct MenuItem { BB_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_GC, REQUIRES_MESSAGE_BOXES = 0x10, REQUIRES_SEND_FUNCTION_CALL = 0x20, + REQUIRES_SAVE_DISABLED = 0x40, }; uint32_t item_id; diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index de7ce9ca..c749f2db 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -99,6 +99,17 @@ static bool process_default(shared_ptr, return true; } +static bool process_server_97(shared_ptr, + ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) { + // Trap 97 commands and always send 97 01 04 00. (If flag is 0, the client + // triggers cheat protection and deletes a bunch of data.) + session.send_to_end(false, 0x97, 0x01); + // Also, update the newserv client config so we'll know not to show the + // programs menu if they return to newserv. + session.newserv_client_config.cfg.flags |= Client::Flag::SAVE_ENABLED; + return false; +} + static bool process_server_gc_9A(shared_ptr, ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) { if (!session.license) { @@ -1022,7 +1033,7 @@ static process_command_t dc_server_handlers[0x100] = { /* 60 */ process_server_60_62_6C_6D_C9_CB, defh, process_server_60_62_6C_6D_C9_CB, defh, defh, defh, process_server_66_69, defh, defh, process_server_66_69, defh, defh, process_server_60_62_6C_6D_C9_CB, process_server_60_62_6C_6D_C9_CB, defh, defh, /* 70 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 80 */ defh, defh, defh, defh, defh, defh, defh, defh, process_server_88, defh, defh, defh, defh, defh, defh, defh, - /* 90 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, + /* 90 */ defh, defh, defh, defh, defh, defh, defh, process_server_97, defh, defh, defh, defh, defh, defh, defh, defh, /* A0 */ defh, defh, defh, defh, defh, defh, defh, process_server_13_A7, defh, defh, defh, defh, defh, defh, defh, defh, /* B0 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* C0 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, @@ -1040,7 +1051,7 @@ static process_command_t pc_server_handlers[0x100] = { /* 60 */ process_server_60_62_6C_6D_C9_CB, defh, process_server_60_62_6C_6D_C9_CB, defh, process_server_64, process_server_65_67_68, process_server_66_69, process_server_65_67_68, process_server_65_67_68, process_server_66_69, defh, defh, process_server_60_62_6C_6D_C9_CB, process_server_60_62_6C_6D_C9_CB, defh, defh, /* 70 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 80 */ defh, defh, defh, defh, defh, defh, defh, defh, process_server_88, defh, defh, defh, defh, defh, defh, defh, - /* 90 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, + /* 90 */ defh, defh, defh, defh, defh, defh, defh, process_server_97, defh, defh, defh, defh, defh, defh, defh, defh, /* A0 */ defh, defh, defh, defh, defh, defh, process_server_44_A6, process_server_13_A7, defh, defh, defh, defh, defh, defh, defh, defh, /* B0 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* C0 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, @@ -1058,7 +1069,7 @@ static process_command_t gc_server_handlers[0x100] = { /* 60 */ process_server_60_62_6C_6D_C9_CB, defh, process_server_60_62_6C_6D_C9_CB, defh, process_server_64, process_server_65_67_68, process_server_66_69, process_server_65_67_68, process_server_65_67_68, process_server_66_69, defh, defh, process_server_60_62_6C_6D_C9_CB, process_server_60_62_6C_6D_C9_CB, defh, defh, /* 70 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 80 */ defh, process_server_81, defh, defh, defh, defh, defh, defh, process_server_88, defh, defh, defh, defh, defh, defh, defh, - /* 90 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, process_server_gc_9A, defh, defh, defh, defh, defh, + /* 90 */ defh, defh, defh, defh, defh, defh, defh, process_server_97, defh, defh, process_server_gc_9A, defh, defh, defh, defh, defh, /* A0 */ defh, defh, defh, defh, defh, defh, process_server_44_A6, process_server_13_A7, defh, defh, defh, defh, defh, defh, defh, defh, /* B0 */ defh, defh, process_server_B2, defh, defh, defh, defh, defh, process_server_gc_B8, defh, defh, defh, defh, defh, defh, defh, /* C0 */ defh, defh, defh, defh, process_server_C4, defh, defh, defh, defh, process_server_60_62_6C_6D_C9_CB, defh, process_server_60_62_6C_6D_C9_CB, defh, defh, defh, defh, diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index b8d8f239..fdf722f3 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -392,13 +392,29 @@ void process_return_client_config(shared_ptr, shared_ptr c, void process_client_checksum(shared_ptr, shared_ptr c, uint16_t, uint32_t, const string& data) { // 96 check_size_t(data); - send_command(c, 0x97, 0x01); + send_server_time(c); } -void process_server_time_request(shared_ptr, shared_ptr c, +void process_server_time_request(shared_ptr s, shared_ptr c, uint16_t, uint32_t, const string& data) { // B1 check_size_v(data.size(), 0); send_server_time(c); + // The B1 command is sent in response to a 97 command, which is normally part + // of the pre-ship-select login sequence. However, newserv delays this until + // after the ship select menu so that loading a GameCube program doesn't cause + // the player's items to be deleted when they next play PSO. It's also not a + // good idea to send a 97 and 19 at the same time, because the memory card and + // BBA are on the same EXI bus on the GameCube and this seems to cause the SYN + // packet after a 19 to get dropped pretty often, which causes a delay in + // joining the lobby. This is why we delay the 19 command until the client + // responds after saving. + if (c->should_send_to_lobby_server) { + static const vector version_to_port_name({ + "dc-lobby", "pc-lobby", "bb-lobby", "gc-lobby", "bb-lobby"}); + 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); + } } @@ -710,12 +726,18 @@ void process_menu_selection(shared_ptr s, shared_ptr c, case MenuID::MAIN: { switch (cmd.item_id) { case MainMenuItemID::GO_TO_LOBBY: { - static const vector version_to_port_name({ - "dc-lobby", "pc-lobby", "bb-lobby", "gc-lobby", "bb-lobby"}); - 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); + c->should_send_to_lobby_server = true; + if (!(c->flags & Client::Flag::SAVE_ENABLED)) { + send_command(c, 0x97, 0x01); + c->flags |= Client::Flag::SAVE_ENABLED; + send_update_client_config(c); + } else { + static const vector version_to_port_name({ + "dc-lobby", "pc-lobby", "bb-lobby", "gc-lobby", "bb-lobby"}); + 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); + } break; } diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 0be6789d..3357665d 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -790,7 +790,8 @@ void send_menu_t( ((c->version == GameVersion::GC) && (item.flags & MenuItem::Flag::INVISIBLE_ON_GC)) || ((c->version == GameVersion::BB) && (item.flags & MenuItem::Flag::INVISIBLE_ON_BB)) || ((item.flags & MenuItem::Flag::REQUIRES_MESSAGE_BOXES) && (c->flags & Client::Flag::NO_MESSAGE_BOX_CLOSE_CONFIRMATION)) || - ((item.flags & MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL) && (c->flags & Client::Flag::DOES_NOT_SUPPORT_SEND_FUNCTION_CALL))) { + ((item.flags & MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL) && (c->flags & Client::Flag::DOES_NOT_SUPPORT_SEND_FUNCTION_CALL)) || + ((item.flags & MenuItem::Flag::REQUIRES_SAVE_DISABLED) && (c->flags & Client::Flag::SAVE_ENABLED))) { continue; } auto& e = entries.emplace_back(); diff --git a/src/ServerState.cc b/src/ServerState.cc index 034cd31f..a6b6a263 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -337,7 +337,7 @@ void ServerState::create_menus(shared_ptr config_json) { } if (!this->dol_file_index->empty()) { this->main_menu.emplace_back(MainMenuItemID::PROGRAMS, u"Programs", - u"Run GameCube\nprograms", MenuItem::Flag::GC_ONLY | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL); + u"Run GameCube\nprograms", MenuItem::Flag::GC_ONLY | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL | MenuItem::Flag::REQUIRES_SAVE_DISABLED); } this->main_menu.emplace_back(MainMenuItemID::DISCONNECT, u"Disconnect", u"Disconnect", 0);