diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 2a338d41..4ad2e75f 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -1220,7 +1220,7 @@ struct C_CharacterData_BB_61_98 { // Header flag = entry count template -struct S_JoinGame { +struct S_JoinGame_DC_PC { // Note: It seems Sega servers sent uninitialized memory in the variations // field when sending this command to start an Episode 3 tournament game. This // can be misleading when reading old logs from those days, but the Episode 3 @@ -1239,15 +1239,6 @@ struct S_JoinGame { uint8_t section_id = 0; uint8_t challenge_mode = 0; le_uint32_t rare_seed = 0; - // Note: The 64 command for PSO DC ends here (the next 4 fields are ignored). - // newserv sends them anyway for code simplicity reasons. - uint8_t episode = 0; - // Similarly, PSO GC ignores the values in the following fields. - uint8_t unused2 = 1; // Should be 1 for PSO PC? - // Note: Only BB uses this field; it's unused on all other versions (since - // only BB has solo mode). - uint8_t solo_mode = 0; - uint8_t unused3 = 0; } __packed__; struct S_JoinGame_DCNTE_64 { @@ -1259,12 +1250,17 @@ struct S_JoinGame_DCNTE_64 { parray lobby_data; } __packed__; -struct S_JoinGame_PC_64 : S_JoinGame { +struct S_JoinGame_DC_64 : S_JoinGame_DC_PC { } __packed__; -struct S_JoinGame_DC_GC_64 : S_JoinGame { +struct S_JoinGame_PC_64 : S_JoinGame_DC_PC { } __packed__; -struct S_JoinGame_GC_Ep3_64 : S_JoinGame_DC_GC_64 { +struct S_JoinGame_GC_64 : S_JoinGame_DC_PC { + uint8_t episode = 0; + parray unused; +} __packed__; + +struct S_JoinGame_GC_Ep3_64 : S_JoinGame_GC_64 { // This field is only present if the game (and client) is Episode 3. Similarly // to lobby_data in the base struct, all four of these are always present and // they are filled in in slot positions. @@ -1274,11 +1270,17 @@ struct S_JoinGame_GC_Ep3_64 : S_JoinGame_DC_GC_64 { } __packed__ players_ep3[4]; } __packed__; -struct S_JoinGame_XB_64 : S_JoinGame { +struct S_JoinGame_XB_64 : S_JoinGame_DC_PC { + uint8_t episode = 0; + parray unused; parray unknown_a1; } __packed__; -struct S_JoinGame_BB_64 : S_JoinGame { +struct S_JoinGame_BB_64 : S_JoinGame_DC_PC { + uint8_t episode = 0; + uint8_t unused1 = 1; + uint8_t solo_mode = 0; + uint8_t unused2 = 0; } __packed__; // 65 (S->C): Add player to game @@ -3120,9 +3122,7 @@ struct S_JoinSpectatorTeam_GC_Ep3_E8 { /* 117B */ uint8_t challenge_mode = 0; /* 117C */ le_uint32_t rare_seed = 0; /* 1180 */ uint8_t episode = 0; - /* 1181 */ uint8_t unused2 = 1; - /* 1182 */ uint8_t solo_mode = 0; - /* 1183 */ uint8_t unused3 = 0; + /* 1181 */ parray unused; struct SpectatorEntry { // It seems that at some point Sega intended to show each player's rank in // spectator teams. The unused1 and unused3 fields are intended for the diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index b403583e..635ce99f 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -1421,8 +1421,9 @@ static HandlerResult S_64(shared_ptr ses, uint16_t, return modified ? HandlerResult::Type::MODIFIED : HandlerResult::Type::FORWARD; } -constexpr on_command_t S_DG_64 = &S_64; +constexpr on_command_t S_D_64 = &S_64; constexpr on_command_t S_P_64 = &S_64; +constexpr on_command_t S_G_64 = &S_64; constexpr on_command_t S_X_64 = &S_64; constexpr on_command_t S_B_64 = &S_64; @@ -1805,7 +1806,7 @@ static on_command_t handlers[0x100][6][2] = { /* 61 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, C_GXB_61}, {S_invalid, C_GXB_61}, {S_invalid, C_GXB_61}}, /* 62 */ {{S_invalid, nullptr}, {S_6x, C_D_6x}, {S_6x, C_P_6x}, {S_6x, C_GX_6x}, {S_6x, C_GX_6x}, {S_6x, C_B_6x}}, /* 63 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, -/* 64 */ {{S_invalid, nullptr}, {S_DG_64, nullptr}, {S_P_64, nullptr}, {S_DG_64, nullptr}, {S_X_64, nullptr}, {S_B_64, nullptr}}, +/* 64 */ {{S_invalid, nullptr}, {S_D_64, nullptr}, {S_P_64, nullptr}, {S_G_64, nullptr}, {S_X_64, nullptr}, {S_B_64, nullptr}}, /* 65 */ {{S_invalid, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_P_65_67_68, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_X_65_67_68, nullptr}, {S_B_65_67_68, nullptr}}, /* 66 */ {{S_invalid, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}}, /* 67 */ {{S_invalid, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_P_65_67_68, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_X_65_67_68, nullptr}, {S_B_65_67_68, nullptr}}, diff --git a/src/ReplaySession.cc b/src/ReplaySession.cc index 91947d51..cb0cd678 100644 --- a/src/ReplaySession.cc +++ b/src/ReplaySession.cc @@ -277,7 +277,7 @@ void ReplaySession::apply_default_mask(shared_ptr ev) { mask.variations.clear(0); mask.rare_seed = 0; } else { // V3 - auto& mask = check_size_t( + auto& mask = check_size_t( mask_data, mask_size, sizeof(S_JoinGame_GC_Ep3_64)); mask.variations.clear(0); mask.rare_seed = 0; diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 380b13ce..ef7d62ee 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -1427,7 +1427,7 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) cmd.event = l->event; cmd.section_id = l->section_id; cmd.rare_seed = l->random_seed; - cmd.episode = 3; + cmd.episode = 0xFF; uint8_t player_count = 0; auto watched_lobby = l->watched_lobby.lock(); @@ -1543,107 +1543,125 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) send_command_t(c, 0xE8, player_count, cmd); } -template -void send_join_game_t(shared_ptr c, shared_ptr l) { +void send_join_game(shared_ptr c, shared_ptr l) { if (l->flags & Lobby::Flag::IS_SPECTATOR_TEAM) { send_join_spectator_team(c, l); return; } - bool is_ep3 = l->is_ep3(); - string data(is_ep3 ? sizeof(S_JoinGame_GC_Ep3_64) : sizeof(S_JoinGame), '\0'); - - // TODO: This is a terrible way to handle the different Ep3 format within the - // template. Find a way to make this cleaner. - auto* cmd = reinterpret_cast*>(data.data()); - S_JoinGame_GC_Ep3_64* cmd_ep3 = nullptr; - if (is_ep3) { - cmd_ep3 = reinterpret_cast(data.data()); - new (cmd_ep3) S_JoinGame_GC_Ep3_64(); - } else { - new (cmd) S_JoinGame(); - } - - cmd->variations = l->variations; - - size_t player_count = 0; - for (size_t x = 0; x < 4; x++) { - if (l->clients[x]) { - cmd->lobby_data[x].player_tag = 0x00010000; - cmd->lobby_data[x].guild_card = l->clients[x]->license->serial_number; - cmd->lobby_data[x].client_id = l->clients[x]->lobby_client_id; - cmd->lobby_data[x].name = l->clients[x]->game_data.player()->disp.name; - if (cmd_ep3) { - cmd_ep3->players_ep3[x].inventory = l->clients[x]->game_data.player()->inventory; - for (size_t z = 0; z < 30; z++) { - cmd_ep3->players_ep3[x].inventory.items[z].data.bswap_data2_if_mag(); - } - cmd_ep3->players_ep3[x].disp = convert_player_disp_data( - l->clients[x]->game_data.player()->disp); + auto populate_lobby_data = [&](auto& cmd) -> size_t { + size_t player_count = 0; + for (size_t x = 0; x < 4; x++) { + if (l->clients[x]) { + cmd.lobby_data[x].player_tag = 0x00010000; + cmd.lobby_data[x].guild_card = l->clients[x]->license->serial_number; + cmd.lobby_data[x].client_id = l->clients[x]->lobby_client_id; + cmd.lobby_data[x].name = l->clients[x]->game_data.player()->disp.name; + player_count++; + } else { + cmd.lobby_data[x].clear(); } - player_count++; - } else { - cmd->lobby_data[x].clear(); } - } + return player_count; + }; + auto populate_base_cmd = [&](auto& cmd) -> size_t { + cmd.variations = l->variations; + cmd.client_id = c->lobby_client_id; + cmd.leader_id = l->leader_id; + cmd.disable_udp = 0x01; // Unused on PC/XB/BB + cmd.difficulty = l->difficulty; + cmd.battle_mode = (l->mode == GameMode::BATTLE) ? 1 : 0; + cmd.event = l->event; + cmd.section_id = l->section_id; + cmd.challenge_mode = (l->mode == GameMode::CHALLENGE) ? 1 : 0; + cmd.rare_seed = l->random_seed; + return populate_lobby_data(cmd); + }; + auto populate_v3_cmd = [&](auto& cmd) -> size_t { + switch (l->episode) { + case Episode::EP1: + cmd.episode = 1; + break; + case Episode::EP2: + cmd.episode = 2; + break; + case Episode::EP3: + cmd.episode = 0xFF; + break; + case Episode::EP4: + cmd.episode = 3; + break; + default: + throw logic_error("invalid episode number in game"); + } + return populate_base_cmd(cmd); + }; - cmd->client_id = c->lobby_client_id; - cmd->leader_id = l->leader_id; - cmd->disable_udp = 0x01; // Unused on PC/XB/BB - cmd->difficulty = l->difficulty; - cmd->battle_mode = (l->mode == GameMode::BATTLE) ? 1 : 0; - cmd->event = l->event; - cmd->section_id = l->section_id; - cmd->challenge_mode = (l->mode == GameMode::CHALLENGE) ? 1 : 0; - cmd->rare_seed = l->random_seed; - switch (l->episode) { - case Episode::EP1: - cmd->episode = 1; + switch (c->version()) { + case GameVersion::DC: { + if (c->flags & Client::Flag::IS_DC_TRIAL_EDITION) { + S_JoinGame_DCNTE_64 cmd; + cmd.client_id = c->lobby_client_id; + cmd.leader_id = l->leader_id; + cmd.disable_udp = 0x01; + cmd.variations = l->variations; + size_t player_count = populate_lobby_data(cmd); + send_command_t(c, 0x64, player_count, cmd); + } else { + S_JoinGame_DC_64 cmd; + size_t player_count = populate_base_cmd(cmd); + send_command_t(c, 0x64, player_count, cmd); + } break; - case Episode::EP2: - cmd->episode = 2; + } + case GameVersion::PC: { + S_JoinGame_PC_64 cmd; + size_t player_count = populate_base_cmd(cmd); + send_command_t(c, 0x64, player_count, cmd); break; - case Episode::EP3: - cmd->episode = 0xFF; + } + case GameVersion::GC: { + if (c->flags & Client::Flag::IS_EPISODE_3) { + S_JoinGame_GC_Ep3_64 cmd; + size_t player_count = populate_v3_cmd(cmd); + for (size_t x = 0; x < 4; x++) { + if (l->clients[x]) { + cmd.players_ep3[x].inventory = l->clients[x]->game_data.player()->inventory; + for (size_t z = 0; z < 30; z++) { + cmd.players_ep3[x].inventory.items[z].data.bswap_data2_if_mag(); + } + cmd.players_ep3[x].disp = convert_player_disp_data( + l->clients[x]->game_data.player()->disp); + } + } + send_command_t(c, 0x64, player_count, cmd); + } else { + S_JoinGame_GC_64 cmd; + size_t player_count = populate_v3_cmd(cmd); + send_command_t(c, 0x64, player_count, cmd); + } break; - case Episode::EP4: - cmd->episode = 3; + } + case GameVersion::XB: { + S_JoinGame_XB_64 cmd; + size_t player_count = populate_v3_cmd(cmd); + send_command_t(c, 0x64, player_count, cmd); break; + } + case GameVersion::BB: { + S_JoinGame_BB_64 cmd; + size_t player_count = populate_v3_cmd(cmd); + cmd.unused1 = 0; + cmd.solo_mode = (l->mode == GameMode::SOLO) ? 1 : 0; + cmd.unused2 = 0; + send_command_t(c, 0x64, player_count, cmd); + break; + } + case GameVersion::PATCH: + throw logic_error("patch server clients cannot join games"); default: - throw logic_error("invalid episode number in game"); + throw logic_error("invalid game version"); } - cmd->unused2 = 0x01; - cmd->solo_mode = (l->mode == GameMode::SOLO) ? 1 : 0; - cmd->unused3 = 0x00; - - send_command(c, 0x64, player_count, data); -} - -void send_join_game_dc_nte(shared_ptr c, shared_ptr l) { - if (l->flags & Lobby::Flag::IS_SPECTATOR_TEAM) { - throw runtime_error("DC NTE players cannot join spectator teams"); - } - - S_JoinGame_DCNTE_64 cmd; - cmd.client_id = c->lobby_client_id; - cmd.leader_id = l->leader_id; - cmd.disable_udp = 0x01; - cmd.variations = l->variations; - - size_t player_count = 0; - for (size_t x = 0; x < 4; x++) { - if (l->clients[x]) { - cmd.lobby_data[x].player_tag = 0x00010000; - cmd.lobby_data[x].guild_card = l->clients[x]->license->serial_number; - cmd.lobby_data[x].client_id = l->clients[x]->lobby_client_id; - cmd.lobby_data[x].name = l->clients[x]->game_data.player()->disp.name; - player_count++; - } else { - cmd.lobby_data[x].clear(); - } - } - - send_command_t(c, 0x64, player_count, cmd); } template @@ -1779,28 +1797,7 @@ void send_join_lobby_dc_nte(shared_ptr c, shared_ptr l, void send_join_lobby(shared_ptr c, shared_ptr l) { if (l->is_game()) { - switch (c->version()) { - case GameVersion::PC: - send_join_game_t(c, l); - break; - case GameVersion::DC: - if (c->flags & (Client::Flag::IS_DC_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE)) { - send_join_game_dc_nte(c, l); - break; - } - [[fallthrough]]; - case GameVersion::GC: - send_join_game_t(c, l); - break; - case GameVersion::XB: - send_join_game_t(c, l); - break; - case GameVersion::BB: - send_join_game_t(c, l); - break; - default: - throw logic_error("unimplemented versioned command"); - } + send_join_game(c, l); } else { switch (c->version()) { case GameVersion::DC: diff --git a/tests/DCv1-GameSmokeTest.test.txt b/tests/DCv1-GameSmokeTest.test.txt index ea00f958..7bca47ee 100644 --- a/tests/DCv1-GameSmokeTest.test.txt +++ b/tests/DCv1-GameSmokeTest.test.txt @@ -512,7 +512,7 @@ I 40469 2023-05-26 10:41:30 - [Lobby/15] Created lobby [PlayerInventory] 3 (00010003): 030000 (Monomate x4) [PlayerInventory] 4 (00010004): 030100 (Monofluid x4) I 40469 2023-05-26 10:41:30 - [Commands] Sending to C-2 (Tali) (version=DC command=64 flag=01) -0000 | 64 01 14 01 00 00 00 00 00 00 00 00 00 00 00 00 | d +0000 | 64 01 10 01 00 00 00 00 00 00 00 00 00 00 00 00 | d 0010 | 03 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 | 0020 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0030 | 01 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 | @@ -529,7 +529,6 @@ I 40469 2023-05-26 10:41:30 - [Commands] Sending to C-2 (Tali) (version=DC comma 00E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0100 | 00 00 00 00 00 00 01 00 00 00 04 00 94 1B EE 22 | " -0110 | 01 01 00 00 | I 40469 2023-05-26 10:41:41 - [Commands] Received from C-2 (Tali) (version=DC command=60 flag=00) 0000 | 60 00 1C 00 3F 06 00 00 00 00 00 C0 00 00 00 00 | ` ? 0010 | CE FE 64 43 00 00 00 00 B8 FF 7D 43 | dC }C diff --git a/tests/DCv2-GameSmokeTest.test.txt b/tests/DCv2-GameSmokeTest.test.txt index c21f39c0..8891df08 100644 --- a/tests/DCv2-GameSmokeTest.test.txt +++ b/tests/DCv2-GameSmokeTest.test.txt @@ -640,7 +640,7 @@ I 40992 2023-05-26 10:53:41 - [Lobby/15] Created lobby [PlayerInventory] 4 (00010004): 030100 (Monofluid x4) [PlayerInventory] 5 (00010005): 000600 (Handgun 0/0/0/0/0) I 40992 2023-05-26 10:53:41 - [Commands] Sending to C-2 (Tali) (version=DC command=64 flag=01) -0000 | 64 01 14 01 00 00 00 00 00 00 00 00 00 00 00 00 | d +0000 | 64 01 10 01 00 00 00 00 00 00 00 00 00 00 00 00 | d 0010 | 01 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 | 0020 | 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 | 0030 | 00 00 00 00 02 00 00 00 00 00 00 00 02 00 00 00 | @@ -657,7 +657,6 @@ I 40992 2023-05-26 10:53:41 - [Commands] Sending to C-2 (Tali) (version=DC comma 00E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0100 | 00 00 00 00 00 00 01 00 00 00 04 00 21 07 72 30 | ! r0 -0110 | 01 01 00 00 | I 40992 2023-05-26 10:53:42 - [Commands] Received from C-2 (Tali) (version=DC command=8A flag=00) 0000 | 8A 00 04 00 | I 40992 2023-05-26 10:53:42 - [Commands] Sending to C-2 (Tali) (version=DC command=8A flag=00) diff --git a/tests/GC-Episode3Battle.test.txt b/tests/GC-Episode3Battle.test.txt index d8076eee..70fb669f 100644 --- a/tests/GC-Episode3Battle.test.txt +++ b/tests/GC-Episode3Battle.test.txt @@ -3675,7 +3675,7 @@ I 16332 2023-09-17 10:14:43 - [Commands] Sending to C-2 (Tali) (version=GC comma 00E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0100 | 00 00 00 00 00 00 01 00 00 00 04 00 B8 A1 56 EC | V -0110 | FF 01 00 00 05 00 00 01 01 00 00 00 08 00 00 00 | +0110 | FF 00 00 00 05 00 00 01 01 00 00 00 08 00 00 00 | 0120 | 00 06 00 00 00 00 00 00 00 00 00 00 00 00 01 00 | 0130 | 00 00 00 00 01 00 00 00 08 00 00 00 01 01 00 00 | 0140 | 00 00 00 00 00 00 00 00 01 00 01 00 00 00 00 00 | diff --git a/tests/GC-Episode3BattleWithSpectator.test.txt b/tests/GC-Episode3BattleWithSpectator.test.txt index 261025c7..19c3aa91 100644 --- a/tests/GC-Episode3BattleWithSpectator.test.txt +++ b/tests/GC-Episode3BattleWithSpectator.test.txt @@ -3675,7 +3675,7 @@ I 17097 2023-09-19 21:52:56 - [Commands] Sending to C-2 (Tali) (version=GC comma 00E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0100 | 00 00 00 00 00 00 01 00 00 00 04 00 DE 23 36 56 | #6V -0110 | FF 01 00 00 05 00 00 01 01 00 00 00 08 00 00 00 | +0110 | FF 00 00 00 05 00 00 01 01 00 00 00 08 00 00 00 | 0120 | 00 06 00 00 00 00 00 00 00 00 00 00 00 00 01 00 | 0130 | 00 00 00 00 01 00 00 00 08 00 00 00 01 01 00 00 | 0140 | 00 00 00 00 00 00 00 00 01 00 01 00 00 00 00 00 | @@ -9764,7 +9764,7 @@ I 17097 2023-09-19 21:54:07 - [Commands] Sending to C-4 (Tali) (version=GC comma 1150 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 1160 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 1170 | 00 00 00 00 04 00 01 00 00 00 04 00 94 F3 96 0C | -1180 | 03 01 00 00 00 00 01 00 11 11 11 11 54 61 6C 69 | Tali +1180 | FF 00 00 00 00 00 01 00 11 11 11 11 54 61 6C 69 | Tali 1190 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 11A0 | 00 00 00 00 00 00 00 00 00 00 00 00 01 00 03 00 | 11B0 | FF FF FF FF 6E F5 DF FF 00 00 00 00 00 00 00 00 | n diff --git a/tests/GC-ForestGame.test.txt b/tests/GC-ForestGame.test.txt index ee5f9b36..810e193c 100644 --- a/tests/GC-ForestGame.test.txt +++ b/tests/GC-ForestGame.test.txt @@ -653,7 +653,7 @@ I 49108 2023-05-26 16:18:32 - [Commands] Sending to C-2 (Jess) (version=GC comma 00E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0100 | 00 00 00 00 00 00 01 00 00 00 05 00 61 49 35 3D | aI5= -0110 | 01 01 00 00 | +0110 | 01 00 00 00 | I 49108 2023-05-26 16:18:33 - [Commands] Received from C-2 (Jess) (version=GC command=8A flag=00) 0000 | 8A 00 04 00 | I 49108 2023-05-26 16:18:33 - [Commands] Sending to C-2 (Jess) (version=GC command=8A flag=00) diff --git a/tests/PC-BasicGame.test.txt b/tests/PC-BasicGame.test.txt index 97c8175f..fa3f4aa7 100644 --- a/tests/PC-BasicGame.test.txt +++ b/tests/PC-BasicGame.test.txt @@ -477,7 +477,7 @@ I 49484 2023-05-26 16:35:40 - [Lobby/15] Created lobby [PlayerInventory] 3 (00010003): 030000 (Monomate x4) [PlayerInventory] 4 (00010004): 030100 (Monofluid x4) I 49484 2023-05-26 16:35:40 - [Commands] Sending to C-3 (Tali) (version=PC command=64 flag=01) -0000 | 54 01 64 01 00 00 00 00 00 00 00 00 00 00 00 00 | T d +0000 | 50 01 64 01 00 00 00 00 00 00 00 00 00 00 00 00 | T d 0010 | 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 | 0020 | 00 00 00 00 02 00 00 00 01 00 00 00 02 00 00 00 | 0030 | 00 00 00 00 02 00 00 00 01 00 00 00 00 00 00 00 | @@ -498,7 +498,6 @@ I 49484 2023-05-26 16:35:40 - [Commands] Sending to C-3 (Tali) (version=PC comma 0120 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0130 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0140 | 00 00 00 00 00 00 01 00 00 00 04 00 DB 6E A2 8C | n -0150 | 01 01 00 00 | I 49484 2023-05-26 16:35:40 - [Commands] Received from C-3 (Tali) (version=PC command=8A flag=00) 0000 | 04 00 8A 00 | I 49484 2023-05-26 16:35:40 - [Commands] Sending to C-3 (Tali) (version=PC command=8A flag=00)