diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 2ddf27f2..dd4a0080 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -5024,29 +5024,25 @@ struct G_Unknown_6x7B { G_ClientIDHeader header; } __packed__; -// 6x7C: Set challenge mode data (not valid on Episode 3) +// 6x7C: Set Challenge records (not valid on Episode 3) -struct G_SetChallengeModeData_6x7C { +struct G_SetChallengeRecordsBase_6x7C { G_UnusedHeader header; le_uint16_t client_id = 0; parray unknown_a1; - le_uint16_t unknown_a2 = 0; - parray unknown_a3; - parray unknown_a4; - parray unknown_a5; - le_uint16_t unknown_a6 = 0; - parray unknown_a7; - le_uint32_t unknown_a8 = 0; - le_uint32_t unknown_a9 = 0; - le_uint32_t unknown_a10 = 0; - le_uint32_t unknown_a11 = 0; - le_uint32_t unknown_a12 = 0; - parray unknown_a13; - struct Entry { - le_uint32_t unknown_a1 = 0; - le_uint32_t unknown_a2 = 0; - } __packed__; - parray entries; +} __packed__; + +struct G_SetChallengeRecords_DC_6x7C : G_SetChallengeRecordsBase_6x7C { + PlayerRecordsDC_Challenge records; +} __packed__; +struct G_SetChallengeRecords_PC_6x7C : G_SetChallengeRecordsBase_6x7C { + PlayerRecordsPC_Challenge records; +} __packed__; +struct G_SetChallengeRecords_V3_6x7C : G_SetChallengeRecordsBase_6x7C { + PlayerRecordsV3_Challenge records; +} __packed__; +struct G_SetChallengeRecords_BB_6x7C : G_SetChallengeRecordsBase_6x7C { + PlayerRecordsBB_Challenge records; } __packed__; // 6x7D: Set battle mode data (not valid on Episode 3) diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 5d3a3609..1eba48bb 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -3212,6 +3212,132 @@ static void on_challenge_mode_retry_or_quit(shared_ptr c, uint8_t comman forward_subcommand(c, command, flag, data, size); } +static void on_challenge_update_records(shared_ptr c, uint8_t command, uint8_t flag, void* data, size_t size) { + auto l = c->lobby.lock(); + if (!l) { + c->log.warning("Not in any lobby; dropping command"); + return; + } + + const auto& cmd = check_size_t(data, size, 0xFFFF); + if (cmd.client_id != c->lobby_client_id) { + return; + } + + auto p = c->character(true, false); + Version c_version = c->version(); + switch (c_version) { + case Version::DC_V2: { + const auto& cmd = check_size_t(data, size); + p->challenge_records = cmd.records; + break; + } + case Version::PC_V2: { + const auto& cmd = check_size_t(data, size); + p->challenge_records = cmd.records; + break; + } + case Version::GC_V3: + case Version::XB_V3: { + const auto& cmd = check_size_t(data, size); + p->challenge_records = cmd.records; + break; + } + case Version::BB_V4: { + const auto& cmd = check_size_t(data, size); + p->challenge_records = cmd.records; + break; + } + case Version::GC_NTE: + // TODO: DOes GC NTE ever send this command? + throw runtime_error("format is unknown for this game version"); + default: + throw runtime_error("game version cannot send 6x7C"); + } + + string dc_data; + string pc_data; + string v3_data; + string bb_data; + auto send_to_client = [&](shared_ptr lc) -> void { + Version lc_version = lc->version(); + const void* data_to_send = nullptr; + size_t size_to_send = 0; + if ((lc_version == c_version) || (is_v3(lc_version) && is_v3(c_version))) { + data_to_send = data; + size_to_send = size; + } else if (lc->version() == Version::DC_V2) { + if (dc_data.empty()) { + dc_data.resize(sizeof(G_SetChallengeRecords_DC_6x7C)); + auto& dc_cmd = check_size_t(dc_data); + dc_cmd.header = cmd.header; + dc_cmd.client_id = cmd.client_id; + dc_cmd.unknown_a1 = cmd.unknown_a1; + dc_cmd.records = p->challenge_records; + } + data_to_send = dc_data.data(); + size_to_send = dc_data.size(); + } else if (lc->version() == Version::PC_V2) { + if (pc_data.empty()) { + pc_data.resize(sizeof(G_SetChallengeRecords_PC_6x7C)); + auto& pc_cmd = check_size_t(pc_data); + pc_cmd.header = cmd.header; + pc_cmd.client_id = cmd.client_id; + pc_cmd.unknown_a1 = cmd.unknown_a1; + pc_cmd.records = p->challenge_records; + } + data_to_send = pc_data.data(); + size_to_send = pc_data.size(); + } else if (is_v3(lc->version())) { + if (v3_data.empty()) { + v3_data.resize(sizeof(G_SetChallengeRecords_V3_6x7C)); + auto& v3_cmd = check_size_t(v3_data); + v3_cmd.header = cmd.header; + v3_cmd.client_id = cmd.client_id; + v3_cmd.unknown_a1 = cmd.unknown_a1; + v3_cmd.records = p->challenge_records; + } + data_to_send = v3_data.data(); + size_to_send = v3_data.size(); + } else if (is_v4(lc->version())) { + if (bb_data.empty()) { + bb_data.resize(sizeof(G_SetChallengeRecords_BB_6x7C)); + auto& bb_cmd = check_size_t(bb_data); + bb_cmd.header = cmd.header; + bb_cmd.client_id = cmd.client_id; + bb_cmd.unknown_a1 = cmd.unknown_a1; + bb_cmd.records = p->challenge_records; + } + data_to_send = bb_data.data(); + size_to_send = bb_data.size(); + } + + if (!data_to_send || !size_to_send) { + lc->log.info("Command cannot be translated to client\'s version"); + } else { + send_command(lc, command, flag, data_to_send, size_to_send); + } + }; + + if (command_is_private(command)) { + if (flag >= l->max_clients) { + return; + } + auto target = l->clients[flag]; + if (!target) { + return; + } + send_to_client(target); + + } else { + for (auto& lc : l->clients) { + if (lc && (lc != c)) { + send_to_client(lc); + } + } + } +} + static void on_quest_exchange_item_bb(shared_ptr c, uint8_t, uint8_t, void* data, size_t size) { auto l = c->require_lobby(); if (l->is_game() && @@ -3740,7 +3866,7 @@ const SubcommandDefinition subcommand_definitions[0x100] = { /* 6x79 */ {0x00, 0x00, 0x79, on_forward_check_lobby}, /* 6x7A */ {0x00, 0x00, 0x7A, on_forward_check_game_client}, /* 6x7B */ {0x00, 0x00, 0x7B, forward_subcommand_m}, - /* 6x7C */ {0x00, 0x00, 0x7C, on_forward_check_game}, + /* 6x7C */ {0x00, 0x00, 0x7C, on_challenge_update_records}, /* 6x7D */ {0x00, 0x00, 0x7D, on_forward_check_game}, /* 6x7E */ {0x00, 0x00, 0x7E, forward_subcommand_m}, /* 6x7F */ {0x00, 0x00, 0x7F, on_battle_scores},