From 62a9da9ed30ef1e7c0d8e19a2ba839165c721193 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Mon, 25 May 2026 07:57:10 -0700 Subject: [PATCH] update game join procedure implementation --- src/Client.hh | 1 + src/CommandFormats.hh | 42 ++++++++--- src/ReceiveCommands.cc | 25 ++++--- src/ReceiveSubcommands.cc | 72 ++++++++++++------- src/SendCommands.cc | 14 +++- src/ServerState.cc | 20 ++++++ ...1-DCv2-PCv2-CrossplayPrivateDrops.test.txt | 10 ++- tests/GC-TradeWindow.test.txt | 2 - tests/GC-XB-CrossplayForestGame.test.txt | 24 ++----- tests/GC-XB-EnemyDamageSyncSwitch.test.txt | 2 - tests/GCEp3-BattleWithSpectatorTeam.test.txt | 4 +- tests/GCEp3-CardTrade.test.txt | 4 +- tests/XB-GC-EnemyDamageSyncSwitch.test.txt | 4 +- 13 files changed, 139 insertions(+), 85 deletions(-) diff --git a/src/Client.hh b/src/Client.hh index 09c896f3..755e1d5b 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -190,6 +190,7 @@ public: std::unordered_set blocked_senders; std::unique_ptr v1_v2_last_reported_disp; std::shared_ptr last_reported_6x70; + std::unordered_set expected_game_state_sync_commands; // (command_num << 8) | target_client_id // These are null unless the client is within the trade sequence (D0-D4 or EE commands) std::unique_ptr pending_item_trade; std::unique_ptr pending_card_trade; diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index a146eb06..3bdcf09c 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -1139,17 +1139,37 @@ struct C_CharacterData_BB_61_98 { // 64 (S->C): Join game // Internal name: RcvStartGame3 -// This is sent to the joining player; the other players get a 65 instead. Note that (except on Episode 3) this command -// does not include the player's disp or inventory data. The clients in the game are responsible for sending that data -// to each other during the join process with 60/62/6C/6D commands. - -// After receiving a 64 command, the client starts the game loading procedure, during which it will completely ignore -// other 64 or 65 commands, and will delay processing of all other commands except 1D until loading is done. If more -// than 0x10000 bytes of commands are sent during loading, any commands that don't fit in the buffer are lost. - // Curiously, this command is named RcvStartGame3 internally, while 0E is named RcvStartGame. The string RcvStartGame2 // appears in the DC versions, but it seems the relevant code was deleted - there are no references to the string. +// The game joining procedure goes as follows: +// 1. The server sends 64 to the joining player, and 65 to all other players in the game. This pauses the game and +// brings up the "please wait" message box for all players. The joining player unloads the lobby assets and begins +// loading Pioneer 2 assets. On v2 and later, the joining player sends 8A near the beginning of this procedure. +// During this time, the joining player will completely ignore other 64 or 65 commands, and will delay processing of +// all other commands except 1D until loading is done. If more than 0x10000 bytes of commands are sent during +// loading, any commands that don't fit in the buffer are lost. +// 2. If the joining player is not the only player in the game: +// a. If the leader is DC v1 or later, the leader sends 6x6D (item state). +// b. The leader sends 6x6B (enemy state). +// c. The leader sends 6x6C (object state). +// d. If the leader is DC NTE or DC 11/2000, the leader sends 6x6D (item state). +// e. The leader sends 6x6E (set flag state). +// f. If the leader is DC v1 or later, the leader sends 6x6F (quest flag state). +// g. If the leader is DC v1 or later, the leader sends 6x71 (construct player). +// h. All players except the joining player send 6x70 to the joining player. (This is not synchronized; non-leader +// players do not wait for any of the above things to happen, so their 6x70 commands may be interleaved with the +// preceding commands from the leader, or may arrive after the following 6x72.) Character data is sent in a +// different format here (6x70 instead of 65) because it contains ephemeral fields that the server doesn't know +// about - things like current HP, state, game flags, player flags, etc. which are not present in 65. +// i. If the leader is not BB, the leader sends 6x72 to all players, which resumes the game. If the leader is BB, +// the server is responsible for sending 6x72, and should do so here. (There is no sequence-breaking risk here, +// since the 6x72 sent to other players will always be ordered after the 65 from the server, so they will always +// send a 6x70 containing their player state at the time the game was paused.) +// 3. Once the joining player has fully loaded, the player processes all the commands sent during loading, which sets +// up the game state and constructs all the other players. Once the local player is constructed, the joining player +// sends 6F, which notifies the server that it can unlock the game and allow more players to join. + // Header flag = entry count template struct S_JoinGameT_DC_PC { @@ -1521,10 +1541,10 @@ struct C_ConnectionInfo_DCNTE_8A { // header.flag is a success flag. If it's zero, the client shows an error message and disconnects. Otherwise, the // client responds with an 8B command. -// 8A (C->S): Request lobby/game name (except DC NTE) +// 8A (C->S): Request lobby/game name (DCv2 and later) // No arguments. -// 8A (S->C): Lobby/game name (except DC NTE) +// 8A (S->C): Lobby/game name (DCv2 and later) // Contents is a string containing the lobby or game name. All versions after DCv1 send an 8A command to request the // team name after joining a game. The response is used to handle the team_name token in quest strings, and appears in // some Challenge Mode information windows. @@ -2623,7 +2643,7 @@ struct C_GuildCardDataRequest_BB_03DC { // DD (S->C): Send quest state to joining player (BB) // When a player joins a game with a quest already in progress, the server should send this command to the leader. // No arguments except header.flag, which is the client ID that the leader should send quest state to. The leader will -// then send a series of target commands (62/6D) that the server can forward to the joining player. +// then send 6x6D, 6x6B, 6x6C, and 6x6E, in that order, targeted at the client specified in header.flag. // DE (S->C): Rare monster list (BB) diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 98e656cb..57b24b8f 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -3259,6 +3259,12 @@ static void on_joinable_quest_loaded(shared_ptr c) { // happens when the response to the ping (1D) is received, so we don't need the game join command queue in that case. if (leader_c->version() == Version::BB_V4) { send_command(leader_c, 0xDD, c->lobby_client_id); + l->log.info_f("Expecting {} to send 6x6B, 6x6C, 6x6D, and 6x6E to {}, and 6x72 to all", + leader_c->channel->name, c->channel->name); + leader_c->expected_game_state_sync_commands.emplace(0x6B00 | (c->lobby_client_id)); + leader_c->expected_game_state_sync_commands.emplace(0x6C00 | (c->lobby_client_id)); + leader_c->expected_game_state_sync_commands.emplace(0x6D00 | (c->lobby_client_id)); + leader_c->expected_game_state_sync_commands.emplace(0x6E00 | (c->lobby_client_id)); c->log.info_f("Creating game join command queue"); c->game_join_command_queue = make_unique>(); } else { @@ -4998,6 +5004,16 @@ static asio::awaitable on_6F(shared_ptr c, Channel::Message& msg) } } + // DC NTE creates players in the invisible state by default; if the joiner is not DC NTE, it won't send 6x23 to make + // itself visible, so we have to do it + for (const auto& lc : l->clients) { + if (lc && (lc != c) && is_pre_v1(lc->version())) { + G_EntityIDHeader cmd = { + translate_subcommand_number(lc->version(), Version::BB_V4, 0x23), 0x01, c->lobby_client_id}; + send_command_t(lc, 0x60, 0x00, cmd); + } + } + if (l->ep3_server && l->ep3_server->battle_finished) { auto s = l->require_server_state(); l->log.info_f("Deleting Episode 3 server state"); @@ -5021,7 +5037,6 @@ static asio::awaitable on_6F(shared_ptr c, Channel::Message& msg) send_text_message_fmt(c, "Rare seed: {:08X}/{:c}\nVariations:{}\n", l->random_seed, type_ch, variations_str); } - bool should_resume_game = true; if (c->version() == Version::BB_V4) { send_set_exp_multiplier(l); send_update_team_reward_flags(c); @@ -5045,7 +5060,6 @@ static asio::awaitable on_6F(shared_ptr c, Channel::Message& msg) send_open_quest_file(c, dat_filename, dat_filename, "", vq->meta.quest_number, QuestFileType::ONLINE, vq->dat_contents); c->set_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST); c->log.info_f("LOADING_RUNNING_JOINABLE_QUEST flag set"); - should_resume_game = false; } else if ((msg.command == 0x016F) && c->check_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST)) { c->clear_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST); @@ -5054,13 +5068,6 @@ static asio::awaitable on_6F(shared_ptr c, Channel::Message& msg) send_rare_enemy_index_list(c, l->map_state->bb_rare_enemy_indexes); } - // We should resume the game if: - // - command is 016F and a joinable quest is in progress - // - command is 006F and a joinable quest is NOT in progress - if (should_resume_game) { - send_resume_game(l, c); - } - // Handle initial commands for spectator teams auto watched_lobby = l->watched_lobby.lock(); if (l->battle_player && l->check_flag(Lobby::Flag::START_BATTLE_PLAYER_IMMEDIATELY)) { diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 8b8a99ef..6a5dad47 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -299,9 +299,19 @@ static asio::awaitable on_debug_info(shared_ptr, SubcommandMessage co_return; } -static asio::awaitable on_forward_check_game_loading(shared_ptr c, SubcommandMessage& msg) { +void check_expected_loading_command(shared_ptr c, const SubcommandMessage& msg) { + const auto& base_header = msg.check_size_t(0xFFFF); + uint8_t subcommand = translate_subcommand_number(Version::BB_V4, c->version(), base_header.subcommand); + if (!c->expected_game_state_sync_commands.erase((subcommand << 8) | (msg.flag & 0xFF))) { + throw std::runtime_error("client sent unexpected game state sync command"); + } +} + +static asio::awaitable on_forward_check_game_loading_expected(shared_ptr c, SubcommandMessage& msg) { auto l = c->require_lobby(); - if (l->is_game() && l->any_client_loading()) { + if (l->is_game()) { + check_expected_loading_command(c, msg); + msg.check_size_t().unused = 0; forward_subcommand(c, msg); } co_return; @@ -492,6 +502,8 @@ static shared_ptr get_sync_target( } static asio::awaitable on_sync_joining_player_compressed_state(shared_ptr c, SubcommandMessage& msg) { + check_expected_loading_command(c, msg); + auto target = get_sync_target(c, msg.command, msg.flag, false); // Checks l->is_game if (!target) { co_return; @@ -702,6 +714,15 @@ static asio::awaitable on_sync_joining_player_compressed_state(shared_ptr< } else { send_game_set_state(target); } + + // If the sender is the leader and is pre-V1, and the target is V1 or later, we need to synthesize a 6x71 command + // to tell the target to construct its TObjPlayer. (If both are pre-V1, the target won't expect this command; if + // both are V1 or later, the leader will send this command itself.) + if (is_pre_v1(c->version()) && !is_pre_v1(target->version())) { + G_UnusedHeader cmd = {0x71, 0x01, 0x0000}; + send_command_t(target, 0x62, target->lobby_client_id, cmd); + } + break; } @@ -712,8 +733,9 @@ static asio::awaitable on_sync_joining_player_compressed_state(shared_ptr< template static asio::awaitable on_sync_joining_player_quest_flags_t(shared_ptr c, SubcommandMessage& msg) { - const auto& cmd = msg.check_size_t(); + check_expected_loading_command(c, msg); + const auto& cmd = msg.check_size_t(); if (!command_is_private(msg.command)) { co_return; } @@ -1211,9 +1233,9 @@ uint32_t Parsed6x70Data::get_player_flags(bool is_v3) const { : Parsed6x70Data::convert_player_flags(this->player_flags, is_v3); } -static asio::awaitable on_sync_joining_player_disp_and_inventory( - shared_ptr c, SubcommandMessage& msg) { +static asio::awaitable on_sync_joining_player_disp_and_inventory(shared_ptr c, SubcommandMessage& msg) { auto s = c->require_server_state(); + check_expected_loading_command(c, msg); // In V1/V2 games, this command sometimes is sent after the new client has finished loading, so we don't check // l->any_client_loading() here. @@ -1222,28 +1244,18 @@ static asio::awaitable on_sync_joining_player_disp_and_inventory( co_return; } - // If the sender is the leader and is pre-V1, and the target is V1 or later, we need to synthesize a 6x71 command to - // tell the target all state has been sent. (If both are pre-V1, the target won't expect this command; if both are V1 - // or later, the leader will send this command itself.) - Version target_v = target->version(); - Version c_v = c->version(); - if (is_pre_v1(c_v) && !is_pre_v1(target_v)) { - static const be_uint32_t data = 0x71010000; - send_command(target, 0x62, target->lobby_client_id, &data, sizeof(data)); - } - bool is_client_customisation = c->check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION); - switch (c_v) { + switch (c->version()) { case Version::DC_NTE: c->last_reported_6x70.reset(new Parsed6x70Data( msg.check_size_t(), - c->login->account->account_id, c_v, is_client_customisation)); + c->login->account->account_id, c->version(), is_client_customisation)); c->last_reported_6x70->clear_dc_protos_unused_item_fields(); break; case Version::DC_11_2000: c->last_reported_6x70.reset(new Parsed6x70Data( msg.check_size_t(), - c->login->account->account_id, c->language(), c_v, is_client_customisation)); + c->login->account->account_id, c->language(), c->version(), is_client_customisation)); c->last_reported_6x70->clear_dc_protos_unused_item_fields(); break; case Version::DC_V1: @@ -1252,8 +1264,8 @@ static asio::awaitable on_sync_joining_player_disp_and_inventory( case Version::PC_V2: c->last_reported_6x70.reset(new Parsed6x70Data( msg.check_size_t(), - c->login->account->account_id, c_v, is_client_customisation)); - if (c_v == Version::DC_V1) { + c->login->account->account_id, c->version(), is_client_customisation)); + if (c->version() == Version::DC_V1) { c->last_reported_6x70->clear_v1_unused_item_fields(); } break; @@ -1263,17 +1275,17 @@ static asio::awaitable on_sync_joining_player_disp_and_inventory( case Version::GC_EP3: c->last_reported_6x70.reset(new Parsed6x70Data( msg.check_size_t(), - c->login->account->account_id, c_v, is_client_customisation)); + c->login->account->account_id, c->version(), is_client_customisation)); break; case Version::XB_V3: c->last_reported_6x70.reset(new Parsed6x70Data( msg.check_size_t(), - c->login->account->account_id, c_v, is_client_customisation)); + c->login->account->account_id, c->version(), is_client_customisation)); break; case Version::BB_V4: c->last_reported_6x70.reset(new Parsed6x70Data( msg.check_size_t(), - c->login->account->account_id, c_v, is_client_customisation)); + c->login->account->account_id, c->version(), is_client_customisation)); break; default: throw logic_error("6x70 command from unknown game version"); @@ -1281,6 +1293,15 @@ static asio::awaitable on_sync_joining_player_disp_and_inventory( c->pos = c->last_reported_6x70->base.pos; send_game_player_state(target, c, false); + + // On BB, the server is expected to send 6x72 rather than the client. We just do it at the same time the client did + // on previous versions (the leader sends it immediately after its own 6x70). + if (c->version() == Version::BB_V4) { + auto l = c->require_lobby(); + if (c->lobby_client_id == l->leader_id) { + send_resume_game(l, target); + } + } } static asio::awaitable on_forward_check_client(shared_ptr c, SubcommandMessage& msg) { @@ -1619,7 +1640,6 @@ static asio::awaitable on_change_floor_6x1F(shared_ptr c, Subcomma if (c->check_flag(Client::Flag::LOADING)) { c->clear_flag(Client::Flag::LOADING); c->log.info_f("LOADING flag cleared"); - send_resume_game(c->require_lobby(), c); c->require_lobby()->assign_inventory_and_bank_item_ids(c, true); } @@ -5653,8 +5673,8 @@ const vector subcommand_definitions{ /* 6x6E */ {0x5F, 0x66, 0x6E, on_sync_joining_player_compressed_state}, /* 6x6F */ {NONE, NONE, 0x6F, on_sync_joining_player_quest_flags}, /* 6x70 */ {0x60, 0x67, 0x70, on_sync_joining_player_disp_and_inventory}, - /* 6x71 */ {NONE, NONE, 0x71, on_forward_check_game_loading}, - /* 6x72 */ {0x61, 0x68, 0x72, on_forward_check_game_loading}, + /* 6x71 */ {NONE, NONE, 0x71, on_forward_check_game_loading_expected}, + /* 6x72 */ {0x61, 0x68, 0x72, on_forward_check_game_loading_expected}, /* 6x73 */ {NONE, NONE, 0x73, on_forward_check_game_quest}, /* 6x74 */ {0x62, 0x69, 0x74, on_word_select, SDF::ALWAYS_FORWARD_TO_WATCHERS}, /* 6x75 */ {NONE, NONE, 0x75, on_set_quest_flag}, diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 90f5ced0..fa203543 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -2545,6 +2545,7 @@ void send_arrow_update(shared_ptr l) { } void send_unblock_join(shared_ptr c) { + // Pre-V1 clients don't have 6x71 at all if (!is_pre_v1(c->version())) { static const be_uint32_t data = 0x71010000; send_command(c, 0x60, 0x00, &data, sizeof(be_uint32_t)); @@ -2553,9 +2554,16 @@ void send_unblock_join(shared_ptr c) { void send_resume_game(shared_ptr l, shared_ptr ready_client) { for (auto lc : l->clients) { - if (lc && (lc != ready_client) && !is_pre_v1(lc->version())) { - static const be_uint32_t data = 0x72010000; - send_command(lc, 0x60, 0x00, &data, sizeof(be_uint32_t)); + if (lc && (lc != ready_client)) { + G_UnusedHeader cmd = {0x00, 0x01, 0x0000}; + if (lc->version() == Version::DC_NTE) { + cmd.subcommand = 0x61; + } else if (lc->version() == Version::DC_11_2000) { + cmd.subcommand = 0x68; + } else { + cmd.subcommand = 0x72; + } + send_command_t(lc, 0x60, 0x00, cmd); } } } diff --git a/src/ServerState.cc b/src/ServerState.cc index c4d6b5cf..dde6bc8e 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -192,6 +192,26 @@ void ServerState::send_lobby_join_notifications(shared_ptr l, shared_ptr< send_player_join_notification(watcher_c, watcher_l, joining_client); } } + + if (l->is_game()) { + for (auto lc : l->clients) { + if (!lc || (lc == joining_client)) { + continue; + } + if (lc->lobby_client_id == l->leader_id) { + l->log.info_f("Expecting {} to send game state to {}", lc->channel->name, joining_client->channel->name); + lc->expected_game_state_sync_commands.emplace(0x6B00 | (joining_client->lobby_client_id)); + lc->expected_game_state_sync_commands.emplace(0x6C00 | (joining_client->lobby_client_id)); + lc->expected_game_state_sync_commands.emplace(0x6D00 | (joining_client->lobby_client_id)); + lc->expected_game_state_sync_commands.emplace(0x6E00 | (joining_client->lobby_client_id)); + lc->expected_game_state_sync_commands.emplace(0x6F00 | (joining_client->lobby_client_id)); + lc->expected_game_state_sync_commands.emplace(0x7100 | (joining_client->lobby_client_id)); + lc->expected_game_state_sync_commands.emplace(0x7200); + } + l->log.info_f("Expecting {} to send 6x70 to {}", lc->channel->name, joining_client->channel->name); + lc->expected_game_state_sync_commands.emplace(0x7000 | (joining_client->lobby_client_id)); + } + } } shared_ptr ServerState::find_lobby(uint32_t lobby_id) { diff --git a/tests/DCv1-DCv2-PCv2-CrossplayPrivateDrops.test.txt b/tests/DCv1-DCv2-PCv2-CrossplayPrivateDrops.test.txt index 9772c27c..caab81f8 100644 --- a/tests/DCv1-DCv2-PCv2-CrossplayPrivateDrops.test.txt +++ b/tests/DCv1-DCv2-PCv2-CrossplayPrivateDrops.test.txt @@ -6155,8 +6155,6 @@ I 35932 2025-05-22 21:59:56 - [C-2] Bank is empty I 35932 2025-05-22 21:59:56 - [Commands] Sending to C-2 (%% Lv.1) @ ip:172.16.0.30:49631 (version=DC_V2 command=B1 flag=00) 0000 | B1 00 20 00 32 30 32 35 3A 30 35 3A 32 33 3A 20 | 2025:05:23: 0010 | 30 34 3A 35 39 3A 35 36 2E 30 30 30 00 01 00 00 | 04:59:56.000 -I 35932 2025-05-22 21:59:56 - [Commands] Sending to C-3 (ELENOR Lv.2) @ ip:172.16.0.30:49632 (version=DC_V1 command=60 flag=00) -0000 | 60 00 08 00 72 01 00 00 | ` r I 35932 2025-05-22 21:59:56 - [Commands] Received from C-3 (ELENOR Lv.2) @ ip:172.16.0.30:49632 (version=DC_V1 command=60 flag=00) 0000 | 60 00 1C 00 20 06 00 00 00 00 00 00 CE FE 64 43 | ` dC 0010 | 00 00 00 00 B8 FF 7D 43 00 C0 FF FF | }C @@ -8482,10 +8480,6 @@ I 35932 2025-05-22 22:00:17 - [C-1] Bank is empty I 35932 2025-05-22 22:00:17 - [Commands] Sending to C-1 (Tali Lv.185) @ ip:10.37.129.2:49620 (version=PC_V2 command=B1 flag=00) 0000 | 20 00 B1 00 32 30 32 35 3A 30 35 3A 32 33 3A 20 | 2025:05:23: 0010 | 30 35 3A 30 30 3A 31 37 2E 30 30 30 00 01 00 00 | 05:00:17.000 -I 35932 2025-05-22 22:00:17 - [Commands] Sending to C-3 (ELENOR Lv.2) @ ip:172.16.0.30:49632 (version=DC_V1 command=60 flag=00) -0000 | 60 00 08 00 72 01 00 00 | ` r -I 35932 2025-05-22 22:00:17 - [Commands] Sending to C-2 (%% Lv.1) @ ip:172.16.0.30:49631 (version=DC_V2 command=60 flag=00) -0000 | 60 00 08 00 72 01 00 00 | ` r I 35932 2025-05-22 22:00:17 - [Commands] Received from C-1 (Tali Lv.185) @ ip:10.37.129.2:49620 (version=PC_V2 command=99 flag=00) 0000 | 04 00 99 00 | I 35932 2025-05-22 22:00:17 - [Commands] Received from C-2 (%% Lv.1) @ ip:172.16.0.30:49631 (version=DC_V2 command=60 flag=00) @@ -8649,6 +8643,10 @@ I 35932 2025-05-22 22:00:17 - [Commands] Sending to C-1 (Tali Lv.185) @ ip:10.37 0490 | 00 00 00 00 | I 35932 2025-05-22 22:00:17 - [Commands] Received from C-3 (ELENOR Lv.2) @ ip:172.16.0.30:49632 (version=DC_V1 command=60 flag=00) 0000 | 60 00 08 00 72 01 00 00 | ` r +I 35932 2025-05-22 22:00:17 - [Commands] Sending to C-2 (%% Lv.1) @ peer:0x7b673a898->0x7b673a598 (version=DC_V2 command=60 flag=00) +0000 | 60 00 08 00 72 01 00 00 | ` r +I 35932 2025-05-22 22:00:17 - [Commands] Sending to C-1 (Tali Lv.185) @ peer:0x7b673a298->0x7b673a418 (version=PC_V2 command=60 flag=00) +0000 | 08 00 60 00 72 01 00 00 | ` r I 35932 2025-05-22 22:00:17 - [Commands] Received from C-3 (ELENOR Lv.2) @ ip:172.16.0.30:49632 (version=DC_V1 command=60 flag=00) 0000 | 60 00 1C 00 20 06 00 00 00 00 00 00 CE FE 64 43 | ` dC 0010 | 00 00 00 00 B8 FF 7D 43 00 C0 FF FF | }C diff --git a/tests/GC-TradeWindow.test.txt b/tests/GC-TradeWindow.test.txt index 1fa39646..89d589ee 100644 --- a/tests/GC-TradeWindow.test.txt +++ b/tests/GC-TradeWindow.test.txt @@ -3132,8 +3132,6 @@ I 24986 2026-03-02 22:34:26 - [C-3] [PlayerInventory] 0 items I 24986 2026-03-02 22:34:26 - [Commands] Sending to C-3 (Tali Lv.1) @ ipss:N-2:127.0.0.1:50375 (version=GC_V3 command=B1 flag=00) 0000 | B1 00 20 00 32 30 32 36 3A 30 33 3A 30 33 3A 20 | 2026:03:03: 0010 | 30 36 3A 33 34 3A 32 36 2E 30 30 30 00 01 00 00 | 06:34:26.000 -I 24986 2026-03-02 22:34:26 - [Commands] Sending to C-2 (Jess Lv.70) @ ipss:N-1:127.0.0.1:50351 (version=GC_V3 command=60 flag=00) -0000 | 60 00 08 00 72 01 00 00 | ` r I 24986 2026-03-02 22:34:26 - [Commands] Received from C-2 (Jess Lv.70) @ ipss:N-1:127.0.0.1:50351 (version=GC_V3 command=60 flag=00) 0000 | 60 00 1C 00 20 06 00 00 00 00 00 00 CE FE 64 43 | ` dC 0010 | 00 00 E8 34 B8 FF 7D 43 00 C0 FF FF | 4 }C diff --git a/tests/GC-XB-CrossplayForestGame.test.txt b/tests/GC-XB-CrossplayForestGame.test.txt index 29ffa25f..9f21f84b 100644 --- a/tests/GC-XB-CrossplayForestGame.test.txt +++ b/tests/GC-XB-CrossplayForestGame.test.txt @@ -4277,7 +4277,7 @@ I 56583 2025-05-23 21:20:33 - [Commands] Sending to C-5 (Tali Lv.1) @ ip:127.0.0 0490 | 00 00 00 00 00 00 00 00 00 00 00 AE 1E B1 05 17 | 04A0 | 00 00 00 00 | I 56583 2025-05-23 21:20:33 - [Commands] Sending to C-5 (Tali Lv.1) @ ip:127.0.0.1:49311 (version=XB_V3 command=60 flag=00) -0000 | 60 00 08 00 72 01 60 CA | ` r ` +0000 | 60 00 08 00 72 01 00 00 | ` r ` I 56583 2025-05-23 21:20:33 - [Commands] Received from C-5 (Tali Lv.1) @ ip:127.0.0.1:49311 (version=XB_V3 command=8A flag=00) 0000 | 8A 00 04 00 | I 56583 2025-05-23 21:20:33 - [Commands] Sending to C-5 (Tali Lv.1) @ ip:127.0.0.1:49311 (version=XB_V3 command=8A flag=00) @@ -4319,8 +4319,6 @@ I 56583 2025-05-23 21:20:38 - [C-5] Bank is empty I 56583 2025-05-23 21:20:38 - [Commands] Sending to C-5 (Tali Lv.1) @ ip:127.0.0.1:49311 (version=XB_V3 command=B1 flag=00) 0000 | B1 00 20 00 32 30 32 35 3A 30 35 3A 32 34 3A 20 | 2025:05:24: 0010 | 30 34 3A 32 30 3A 33 38 2E 30 30 30 00 01 00 00 | 04:20:38.000 -I 56583 2025-05-23 21:20:38 - [Commands] Sending to C-4 (Jess Lv.51) @ ipss:N-1:127.0.0.1:49211 (version=GC_V3 command=60 flag=00) -0000 | 60 00 08 00 72 01 00 00 | ` r I 56583 2025-05-23 21:20:38 - [Commands] Received from C-5 (Tali Lv.1) @ ip:127.0.0.1:49311 (version=XB_V3 command=62 flag=00) 0000 | 62 00 0C 00 B4 02 01 00 00 00 00 00 | b I 56583 2025-05-23 21:20:39 - [Commands] Received from C-5 (Tali Lv.1) @ ip:127.0.0.1:49311 (version=XB_V3 command=99 flag=00) @@ -7136,7 +7134,7 @@ I 56583 2025-05-23 21:21:04 - [Commands] Sending to C-4 (Jess Lv.51) @ ipss:N-1: I 56583 2025-05-23 21:21:04 - [Commands] Received from C-5 (Tali Lv.1) @ ip:127.0.0.1:49311 (version=XB_V3 command=60 flag=00) 0000 | 60 00 08 00 72 01 13 FD | ` r I 56583 2025-05-23 21:21:04 - [Commands] Sending to C-4 (Jess Lv.51) @ ipss:N-1:127.0.0.1:49211 (version=GC_V3 command=60 flag=00) -0000 | 60 00 08 00 72 01 13 FD | ` r +0000 | 60 00 08 00 72 01 00 00 | ` r I 56583 2025-05-23 21:21:04 - [Commands] Received from C-5 (Tali Lv.1) @ ip:127.0.0.1:49311 (version=XB_V3 command=62 flag=00) 0000 | 62 00 0C 00 B4 02 01 00 00 00 00 00 | b I 56583 2025-05-23 21:21:05 - [Commands] Received from C-4 (Jess Lv.51) @ ipss:N-1:127.0.0.1:49211 (version=GC_V3 command=60 flag=00) @@ -7168,8 +7166,6 @@ I 56583 2025-05-23 21:21:05 - [C-4] Bank is empty I 56583 2025-05-23 21:21:05 - [Commands] Sending to C-4 (Jess Lv.51) @ ipss:N-1:127.0.0.1:49211 (version=GC_V3 command=B1 flag=00) 0000 | B1 00 20 00 32 30 32 35 3A 30 35 3A 32 34 3A 20 | 2025:05:24: 0010 | 30 34 3A 32 31 3A 30 35 2E 30 30 30 00 01 00 00 | 04:21:05.000 -I 56583 2025-05-23 21:21:05 - [Commands] Sending to C-5 (Tali Lv.1) @ ip:127.0.0.1:49311 (version=XB_V3 command=60 flag=00) -0000 | 60 00 08 00 72 01 00 00 | ` r I 56583 2025-05-23 21:21:06 - [Commands] Received from C-5 (Tali Lv.1) @ ip:127.0.0.1:49311 (version=XB_V3 command=60 flag=00) 0000 | 60 00 1C 00 20 06 01 00 00 00 00 00 3D 00 67 43 | ` = gC 0010 | 00 00 AC B4 66 80 85 43 00 C6 FF FF | f C @@ -9941,9 +9937,9 @@ I 56583 2025-05-23 21:21:38 - [Commands] Sending to C-2 (NN Lv.1) @ ipss:N-2:127 I 56583 2025-05-23 21:21:38 - [Commands] Received from C-5 (Tali Lv.1) @ ip:127.0.0.1:49311 (version=XB_V3 command=60 flag=00) 0000 | 60 00 08 00 72 01 13 FD | ` r I 56583 2025-05-23 21:21:38 - [Commands] Sending to C-4 (Jess Lv.51) @ ipss:N-1:127.0.0.1:49211 (version=GC_V3 command=60 flag=00) -0000 | 60 00 08 00 72 01 13 FD | ` r +0000 | 60 00 08 00 72 01 00 00 | ` r I 56583 2025-05-23 21:21:38 - [Commands] Sending to C-2 (NN Lv.1) @ ipss:N-2:127.0.0.1:49220 (version=GC_V3 command=60 flag=00) -0000 | 60 00 08 00 72 01 13 FD | ` r +0000 | 60 00 08 00 72 01 00 00 | ` r I 56583 2025-05-23 21:21:38 - [Commands] Received from C-5 (Tali Lv.1) @ ip:127.0.0.1:49311 (version=XB_V3 command=62 flag=02) 0000 | 62 02 0C 00 B4 02 01 00 00 00 00 00 | b I 56583 2025-05-23 21:21:39 - [Commands] Received from C-4 (Jess Lv.51) @ ipss:N-1:127.0.0.1:49211 (version=GC_V3 command=60 flag=00) @@ -10067,10 +10063,6 @@ I 56583 2025-05-23 21:21:42 - [C-2] Bank is empty I 56583 2025-05-23 21:21:42 - [Commands] Sending to C-2 (NN Lv.1) @ ipss:N-2:127.0.0.1:49220 (version=GC_V3 command=B1 flag=00) 0000 | B1 00 20 00 32 30 32 35 3A 30 35 3A 32 34 3A 20 | 2025:05:24: 0010 | 30 34 3A 32 31 3A 34 32 2E 30 30 30 00 01 00 00 | 04:21:42.000 -I 56583 2025-05-23 21:21:42 - [Commands] Sending to C-4 (Jess Lv.51) @ ipss:N-1:127.0.0.1:49211 (version=GC_V3 command=60 flag=00) -0000 | 60 00 08 00 72 01 00 00 | ` r -I 56583 2025-05-23 21:21:42 - [Commands] Sending to C-5 (Tali Lv.1) @ ip:127.0.0.1:49311 (version=XB_V3 command=60 flag=00) -0000 | 60 00 08 00 72 01 00 00 | ` r I 56583 2025-05-23 21:21:42 - [Commands] Received from C-4 (Jess Lv.51) @ ipss:N-1:127.0.0.1:49211 (version=GC_V3 command=60 flag=00) 0000 | 60 00 1C 00 20 06 00 00 01 00 00 00 CA AB DE 43 | ` C 0010 | EF 8D 47 41 36 DD 01 44 00 D0 00 00 | GA6 D @@ -16610,9 +16602,9 @@ I 56583 2025-05-23 21:24:52 - [Commands] Sending to C-4 (Jess Lv.51) @ ipss:N-1: I 56583 2025-05-23 21:24:52 - [Commands] Received from C-5 (Tali Lv.1) @ ip:127.0.0.1:49311 (version=XB_V3 command=60 flag=00) 0000 | 60 00 08 00 72 01 13 FD | ` r I 56583 2025-05-23 21:24:52 - [Commands] Sending to C-4 (Jess Lv.51) @ ipss:N-1:127.0.0.1:49211 (version=GC_V3 command=60 flag=00) -0000 | 60 00 08 00 72 01 13 FD | ` r +0000 | 60 00 08 00 72 01 00 00 | ` r I 56583 2025-05-23 21:24:52 - [Commands] Sending to C-2 (NN Lv.1) @ ipss:N-2:127.0.0.1:49220 (version=GC_V3 command=60 flag=00) -0000 | 60 00 08 00 72 01 13 FD | ` r +0000 | 60 00 08 00 72 01 00 00 | ` r I 56583 2025-05-23 21:24:52 - [Commands] Received from C-5 (Tali Lv.1) @ ip:127.0.0.1:49311 (version=XB_V3 command=62 flag=00) 0000 | 62 00 0C 00 B4 02 01 00 00 00 00 00 | b I 56583 2025-05-23 21:24:52 - [Commands] Received from C-4 (Jess Lv.51) @ ipss:N-1:127.0.0.1:49211 (version=GC_V3 command=60 flag=00) @@ -16651,10 +16643,6 @@ I 56583 2025-05-23 21:24:52 - [C-4] Bank is empty I 56583 2025-05-23 21:24:52 - [Commands] Sending to C-4 (Jess Lv.51) @ ipss:N-1:127.0.0.1:49211 (version=GC_V3 command=B1 flag=00) 0000 | B1 00 20 00 32 30 32 35 3A 30 35 3A 32 34 3A 20 | 2025:05:24: 0010 | 30 34 3A 32 34 3A 35 32 2E 30 30 30 00 01 00 00 | 04:24:52.000 -I 56583 2025-05-23 21:24:52 - [Commands] Sending to C-5 (Tali Lv.1) @ ip:127.0.0.1:49311 (version=XB_V3 command=60 flag=00) -0000 | 60 00 08 00 72 01 00 00 | ` r -I 56583 2025-05-23 21:24:52 - [Commands] Sending to C-2 (NN Lv.1) @ ipss:N-2:127.0.0.1:49220 (version=GC_V3 command=60 flag=00) -0000 | 60 00 08 00 72 01 00 00 | ` r I 56583 2025-05-23 21:24:52 - [Commands] Received from C-2 (NN Lv.1) @ ipss:N-2:127.0.0.1:49220 (version=GC_V3 command=60 flag=00) 0000 | 60 00 1C 00 20 06 02 00 01 00 00 00 48 99 58 43 | ` H XC 0010 | 83 12 CD 41 B9 98 F9 43 B5 83 FF FF | A C diff --git a/tests/GC-XB-EnemyDamageSyncSwitch.test.txt b/tests/GC-XB-EnemyDamageSyncSwitch.test.txt index 3d12ff01..1a1e3751 100644 --- a/tests/GC-XB-EnemyDamageSyncSwitch.test.txt +++ b/tests/GC-XB-EnemyDamageSyncSwitch.test.txt @@ -3606,8 +3606,6 @@ I 33336 2025-07-21 23:41:21 - [C-3] Bank is empty I 33336 2025-07-21 23:41:21 - [Commands] Sending to C-3 (Jess Lv.51) @ ip:127.0.0.1:53992 (version=XB_V3 command=B1 flag=00) 0000 | B1 00 20 00 32 30 32 35 3A 30 37 3A 32 32 3A 20 | 2025:07:22: 0010 | 30 36 3A 34 31 3A 32 31 2E 30 30 30 00 01 00 00 | 06:41:21.000 -I 33336 2025-07-21 23:41:21 - [Commands] Sending to C-2 (Tali Lv.60) @ ipss:N-1:127.0.0.1:53977 (version=GC_V3 command=60 flag=00) -0000 | 60 00 08 00 72 01 00 00 | ` r I 33336 2025-07-21 23:41:22 - [Commands] Received from C-2 (Tali Lv.60) @ ipss:N-1:127.0.0.1:53977 (version=GC_V3 command=60 flag=00) 0000 | 60 00 14 00 40 04 00 00 2E 3D FF 43 05 2E F3 43 | ` @ .= C . C 0010 | 00 00 00 00 | diff --git a/tests/GCEp3-BattleWithSpectatorTeam.test.txt b/tests/GCEp3-BattleWithSpectatorTeam.test.txt index 27b81259..fcfc1d39 100644 --- a/tests/GCEp3-BattleWithSpectatorTeam.test.txt +++ b/tests/GCEp3-BattleWithSpectatorTeam.test.txt @@ -12815,7 +12815,7 @@ I 22567 2025-05-23 20:26:56 - [Commands] Sending to C-3 (Tali Lv.1) @ ipss:N-3:1 I 22567 2025-05-23 20:26:56 - [Commands] Received from C-1 (Tali Lv.5) @ ipss:N-1:127.0.0.1:60555 (version=GC_EP3 command=60 flag=00) 0000 | 60 00 08 00 72 01 28 F8 | ` r ( I 22567 2025-05-23 20:26:56 - [Commands] Sending to C-3 (Tali Lv.1) @ ipss:N-3:127.0.0.1:60595 (version=GC_EP3 command=60 flag=00) -0000 | 60 00 08 00 72 01 28 F8 | ` r ( +0000 | 60 00 08 00 72 01 00 00 | ` r ( I 22567 2025-05-23 20:26:57 - [Commands] Received from C-2 (Tali Lv.1) @ ipss:N-2:127.0.0.1:60573 (version=GC_EP3_NTE command=1D flag=00) 0000 | 1D 00 04 00 | I 22567 2025-05-23 20:26:58 - [Commands] Received from C-3 (Tali Lv.1) @ ipss:N-3:127.0.0.1:60595 (version=GC_EP3 command=C9 flag=00) @@ -12922,8 +12922,6 @@ I 22567 2025-05-23 20:27:03 - [C-3] Bank is empty I 22567 2025-05-23 20:27:03 - [Commands] Sending to C-3 (Tali Lv.1) @ ipss:N-3:127.0.0.1:60595 (version=GC_EP3 command=B1 flag=00) 0000 | B1 00 20 00 32 30 32 35 3A 30 35 3A 32 34 3A 20 | 2025:05:24: 0010 | 30 33 3A 32 37 3A 30 33 2E 30 30 30 00 01 00 00 | 03:27:03.000 -I 22567 2025-05-23 20:27:03 - [Commands] Sending to C-1 (Tali Lv.5) @ ipss:N-1:127.0.0.1:60555 (version=GC_EP3 command=60 flag=00) -0000 | 60 00 08 00 72 01 00 00 | ` r I 22567 2025-05-23 20:27:03 - [Commands] Received from C-3 (Tali Lv.1) @ ipss:N-3:127.0.0.1:60595 (version=GC_EP3 command=99 flag=00) 0000 | 99 00 04 00 | I 22567 2025-05-23 20:27:04 - [Commands] Received from C-1 (Tali Lv.5) @ ipss:N-1:127.0.0.1:60555 (version=GC_EP3 command=60 flag=00) diff --git a/tests/GCEp3-CardTrade.test.txt b/tests/GCEp3-CardTrade.test.txt index 09edd0a6..5cdc1c24 100644 --- a/tests/GCEp3-CardTrade.test.txt +++ b/tests/GCEp3-CardTrade.test.txt @@ -8600,7 +8600,7 @@ I 40093 2025-05-20 22:23:57 - [Commands] Sending to C-2 (Dono Lv.1) @ ipss:N-2:1 I 40093 2025-05-20 22:23:57 - [Commands] Received from C-1 (Tali Lv.5) @ ipss:N-1:127.0.0.1:61866 (version=GC_EP3 command=60 flag=00) 0000 | 60 00 08 00 72 01 00 55 | ` r U I 40093 2025-05-20 22:23:57 - [Commands] Sending to C-2 (Dono Lv.1) @ ipss:N-2:127.0.0.1:61901 (version=GC_EP3 command=60 flag=00) -0000 | 60 00 08 00 72 01 00 55 | ` r U +0000 | 60 00 08 00 72 01 00 00 | ` r U I 40093 2025-05-20 22:23:58 - [Commands] Received from C-2 (Dono Lv.1) @ ipss:N-2:127.0.0.1:61901 (version=GC_EP3 command=C9 flag=00) 0000 | C9 00 10 00 B5 03 00 00 47 01 00 00 00 00 00 00 | G I 40093 2025-05-20 22:23:58 - [Commands] Sending to C-1 (Tali Lv.5) @ ipss:N-1:127.0.0.1:61866 (version=GC_EP3 command=C9 flag=00) @@ -8699,8 +8699,6 @@ I 40093 2025-05-20 22:24:03 - [C-2] Bank is empty I 40093 2025-05-20 22:24:03 - [Commands] Sending to C-2 (Dono Lv.1) @ ipss:N-2:127.0.0.1:61901 (version=GC_EP3 command=B1 flag=00) 0000 | B1 00 20 00 32 30 32 35 3A 30 35 3A 7B 7D 3A 20 | 2025:05:{}: 0010 | 30 35 3A 32 34 3A 30 33 2E 30 30 30 00 01 00 00 | 05:24:03.000 -I 40093 2025-05-20 22:24:03 - [Commands] Sending to C-1 (Tali Lv.5) @ ipss:N-1:127.0.0.1:61866 (version=GC_EP3 command=60 flag=00) -0000 | 60 00 08 00 72 01 00 00 | ` r I 40093 2025-05-20 22:24:03 - [Commands] Received from C-2 (Dono Lv.1) @ ipss:N-2:127.0.0.1:61901 (version=GC_EP3 command=99 flag=00) 0000 | 99 00 04 00 | I 40093 2025-05-20 22:24:03 - [Commands] Received from C-1 (Tali Lv.5) @ ipss:N-1:127.0.0.1:61866 (version=GC_EP3 command=60 flag=00) diff --git a/tests/XB-GC-EnemyDamageSyncSwitch.test.txt b/tests/XB-GC-EnemyDamageSyncSwitch.test.txt index 258ef3cf..8c60a2ac 100644 --- a/tests/XB-GC-EnemyDamageSyncSwitch.test.txt +++ b/tests/XB-GC-EnemyDamageSyncSwitch.test.txt @@ -3771,8 +3771,6 @@ I 34886 2025-07-21 23:47:09 - [C-2] Bank is empty I 34886 2025-07-21 23:47:09 - [Commands] Sending to C-2 (Tali Lv.60) @ ipss:N-1:127.0.0.1:54394 (version=GC_V3 command=B1 flag=00) 0000 | B1 00 20 00 32 30 32 35 3A 30 37 3A 32 32 3A 20 | 2025:07:22: 0010 | 30 36 3A 34 37 3A 30 39 2E 30 30 30 00 01 00 00 | 06:47:09.000 -I 34886 2025-07-21 23:47:09 - [Commands] Sending to C-3 (Jess Lv.51) @ ip:127.0.0.1:54521 (version=XB_V3 command=60 flag=00) -0000 | 60 00 08 00 72 01 00 00 | ` r I 34886 2025-07-21 23:47:09 - [Commands] Received from C-2 (Tali Lv.60) @ ipss:N-1:127.0.0.1:54394 (version=GC_V3 command=99 flag=00) 0000 | 99 00 04 00 | I 34886 2025-07-21 23:47:10 - [Commands] Received from C-3 (Jess Lv.51) @ ip:127.0.0.1:54521 (version=XB_V3 command=6D flag=01) @@ -3928,6 +3926,8 @@ I 34886 2025-07-21 23:47:10 - [Commands] Sending to C-2 (Tali Lv.60) @ ipss:N-1: 0490 | 00 00 00 00 01 00 00 00 | I 34886 2025-07-21 23:47:10 - [Commands] Received from C-3 (Jess Lv.51) @ ip:127.0.0.1:54521 (version=XB_V3 command=60 flag=00) 0000 | 60 00 08 00 72 01 41 8C | ` r A +I 34886 2025-07-21 23:47:10 - [Commands] Sending to C-2 (Tali Lv.60) @ peer:0x9effeaa18->0x9effe8198 (version=GC_V3 command=60 flag=00) +0000 | 60 00 08 00 72 01 00 00 | ` r A I 34886 2025-07-21 23:47:10 - [Commands] Received from C-3 (Jess Lv.51) @ ip:127.0.0.1:54521 (version=XB_V3 command=62 flag=01) 0000 | 62 01 0C 00 B4 02 00 00 00 00 00 00 | b I 34886 2025-07-21 23:47:10 - [Commands] Received from C-3 (Jess Lv.51) @ ip:127.0.0.1:54521 (version=XB_V3 command=60 flag=00)