diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 3f455b85..f054ce22 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -4520,7 +4520,8 @@ struct G_ClearTemporaryPhotonBlastStateFlags_6x3B { // This appears to be a base class for 6x46, 6x47, and 6x49 (and possibly other // subcommands), but it is never sent on the wire. Its likely purpose is to // provide the TargetEntry structure and related functions to derived classes, -// but it does still have a structure of its own, as described here. +// but it does still have a subcommand number and a structure of its own, as +// described here. struct TargetEntry { le_uint16_t entity_id = 0; @@ -4591,17 +4592,16 @@ struct G_Attack_6x43_6x44_6x45 { // targets is too large, the client will byteswap the function's return address // on the stack, and it will crash. -struct G_AttackFinished_6x46 { +struct G_AttackFinished_Header_6x46 { G_ClientIDHeader header; le_uint32_t target_count = 0; - // The client may send a shorter command if not all of these are used. - parray targets; -} __packed_ws__(G_AttackFinished_6x46, 0x30); + // Up to 10 TargetEntries are sent here +} __packed_ws__(G_AttackFinished_Header_6x46, 8); // 6x47: Cast technique (protected on V3/V4) // On GC, this command has the same bounds-check bug as 6x46. -struct G_CastTechnique_6x47 { +struct G_CastTechnique_Header_6x47 { G_ClientIDHeader header; uint8_t technique_number = 0; uint8_t unused = 0; // Must not be negative @@ -4612,9 +4612,8 @@ struct G_CastTechnique_6x47 { // cleaned it up. uint8_t level = 0; uint8_t target_count = 0; // Must be in [0, 10] - // The client may send a shorter command if not all of these are used. - parray targets; -} __packed_ws__(G_CastTechnique_6x47, 0x30); + // Up to 10 TargetEntries are sent here +} __packed_ws__(G_CastTechnique_Header_6x47, 8); // 6x48: Cast technique complete (protected on V3/V4) @@ -4629,16 +4628,15 @@ struct G_CastTechniqueComplete_6x48 { // 6x49: Execute Photon Blast (protected on V3/V4) // On GC, this command has the same bounds-check bug as 6x46. -struct G_ExecutePhotonBlast_6x49 { +struct G_ExecutePhotonBlast_Header_6x49 { G_ClientIDHeader header; uint8_t unknown_a1 = 0; uint8_t unknown_a2 = 0; le_uint16_t target_count = 0; le_uint16_t unknown_a3 = 0; le_uint16_t unknown_a4 = 0; - // The client may send a shorter command if not all of these are used. - parray targets; -} __packed_ws__(G_ExecutePhotonBlast_6x49, 0x34); + // Up to 10 TargetEntries are sent here +} __packed_ws__(G_ExecutePhotonBlast_Header_6x49, 0x0C); // 6x4A: Fully shield attack (protected on V3/V4) diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index ab1c6f1f..3821c287 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -916,9 +916,8 @@ static asio::awaitable S_6x(shared_ptr c, Channel::Messag co_return HandlerResult::SUPPRESS; case 0x46: { - const auto& cmd = msg.check_size_t( - offsetof(G_AttackFinished_6x46, targets), sizeof(G_AttackFinished_6x46)); - if (cmd.target_count > min(cmd.header.size - 2, cmd.targets.size())) { + const auto& header = msg.check_size_t(0xFFFF); + if (header.target_count > min(header.header.size - sizeof(G_AttackFinished_Header_6x46) / 4, 10)) { c->log.warning_f("Blocking subcommand 6x46 with invalid count"); co_return HandlerResult::SUPPRESS; } @@ -926,9 +925,8 @@ static asio::awaitable S_6x(shared_ptr c, Channel::Messag } case 0x47: { - const auto& cmd = msg.check_size_t( - offsetof(G_CastTechnique_6x47, targets), sizeof(G_CastTechnique_6x47)); - if (cmd.target_count > min(cmd.header.size - 2, cmd.targets.size())) { + const auto& header = msg.check_size_t(0xFFFF); + if (header.target_count > min(header.header.size - sizeof(G_CastTechnique_Header_6x47) / 4, 10)) { c->log.warning_f("Blocking subcommand 6x47 with invalid count"); co_return HandlerResult::SUPPRESS; } @@ -936,9 +934,8 @@ static asio::awaitable S_6x(shared_ptr c, Channel::Messag } case 0x49: { - const auto& cmd = msg.check_size_t( - offsetof(G_ExecutePhotonBlast_6x49, targets), sizeof(G_ExecutePhotonBlast_6x49)); - if (cmd.target_count > min(cmd.header.size - 3, cmd.targets.size())) { + const auto& header = msg.check_size_t(0xFFFF); + if (header.target_count > min(header.header.size - sizeof(G_ExecutePhotonBlast_Header_6x49) / 4, 10)) { c->log.warning_f("Blocking subcommand 6x49 with invalid count"); co_return HandlerResult::SUPPRESS; } diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 05ac7675..d33df62b 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -414,18 +414,23 @@ asio::awaitable forward_subcommand_with_entity_id_transcode_t(shared_ptr -void forward_subcommand_with_entity_targets_transcode_t(shared_ptr c, SubcommandMessage& msg) { +template +asio::awaitable forward_subcommand_with_entity_targets_transcode_t(shared_ptr c, SubcommandMessage& msg) { // I'm lazy and this should never happen for item commands (since all players // need to stay in sync) if (command_is_private(msg.command)) { throw runtime_error("entity subcommand sent via private command"); } - const auto& cmd = msg.check_size_t(offsetof(CmdT, targets), sizeof(CmdT)); - if (cmd.target_count > min(cmd.header.size - offsetof(CmdT, targets) / 4, cmd.targets.size())) { - throw runtime_error("invalid attack finished command"); + phosg::StringReader r(msg.data, msg.size); + const auto& header = r.get(); + if (header.target_count > 10) { + throw runtime_error("invalid target count"); } + if (header.target_count > std::min(header.header.size - sizeof(HeaderT) / 4, 10)) { + throw runtime_error("invalid target list command"); + } + const auto* targets = r.get_array(header.target_count); auto l = c->require_lobby(); if (!l->is_game()) { @@ -438,8 +443,8 @@ void forward_subcommand_with_entity_targets_transcode_t(shared_ptr c, Su uint16_t entity_id; }; vector resolutions; - for (size_t z = 0; z < cmd.target_count; z++) { - auto& res = resolutions.emplace_back(TargetResolution{nullptr, nullptr, cmd.targets[z].entity_id}); + for (size_t z = 0; z < header.target_count; z++) { + auto& res = resolutions.emplace_back(TargetResolution{nullptr, nullptr, targets[z].entity_id}); if ((res.entity_id >= 0x1000) && (res.entity_id < 0x4000)) { res.ene_st = l->map_state->enemy_state_for_index(c->version(), c->floor, res.entity_id - 0x1000); } else if ((res.entity_id >= 0x4000) && (res.entity_id < 0xFFFF)) { @@ -452,32 +457,29 @@ void forward_subcommand_with_entity_targets_transcode_t(shared_ptr c, Su continue; } if (c->version() != lc->version()) { - // NOTE: We can't just do `CmdT out_cmd = cmd` here because cmd may not - // point to a full command; it is likely shorter than the full structure - CmdT out_cmd; - memcpy(&out_cmd, &cmd, msg.size); - out_cmd.header.subcommand = translate_subcommand_number(lc->version(), c->version(), cmd.header.subcommand); - if (out_cmd.header.subcommand) { - size_t valid_targets = 0; - for (size_t z = 0; z < cmd.target_count; z++) { + HeaderT out_header = header; + vector out_targets; + out_header.header.subcommand = translate_subcommand_number(lc->version(), c->version(), header.header.subcommand); + out_header.target_count = 0; + if (out_header.header.subcommand) { + for (size_t z = 0; z < header.target_count; z++) { + uint16_t entity_id; const auto& res = resolutions[z]; - auto& target = out_cmd.targets[valid_targets]; if (res.ene_st) { - target.entity_id = 0x1000 | l->map_state->index_for_enemy_state(lc->version(), res.ene_st); + entity_id = 0x1000 | l->map_state->index_for_enemy_state(lc->version(), res.ene_st); } else if (res.obj_st) { - target.entity_id = 0x4000 | l->map_state->index_for_object_state(lc->version(), res.obj_st); + entity_id = 0x4000 | l->map_state->index_for_object_state(lc->version(), res.obj_st); } else { - target.entity_id = res.entity_id; + entity_id = res.entity_id; } - if (target.entity_id != 0xFFFF) { - target.unknown_a2 = cmd.targets[z].unknown_a2; - valid_targets++; + if (entity_id != 0xFFFF) { + out_targets.emplace_back(TargetEntry{entity_id, targets[z].unknown_a2}); } } - size_t out_size = offsetof(CmdT, targets) + sizeof(TargetEntry) * valid_targets; - out_cmd.header.size = out_size >> 2; - out_cmd.target_count = valid_targets; - send_command(lc, msg.command, msg.flag, &out_cmd, out_size); + size_t out_size = sizeof(HeaderT) + sizeof(TargetEntry) * out_targets.size(); + out_header.header.size = out_size >> 2; + out_header.target_count = out_targets.size(); + send_command_t_vt(lc, msg.command, msg.flag, out_header, out_targets); } else { lc->log.info_f("Subcommand cannot be translated to client\'s version"); } @@ -485,6 +487,7 @@ void forward_subcommand_with_entity_targets_transcode_t(shared_ptr c, Su send_command(lc, msg.command, msg.flag, msg.data, msg.size); } } + co_return; } static shared_ptr get_sync_target(shared_ptr sender_c, uint8_t command, uint8_t flag, bool allow_if_not_loading) { @@ -1734,36 +1737,6 @@ static asio::awaitable on_cast_technique_finished(shared_ptr c, Su co_return; } -static asio::awaitable on_attack_finished(shared_ptr c, SubcommandMessage& msg) { - const auto& cmd = msg.check_size_t( - offsetof(G_AttackFinished_6x46, targets), sizeof(G_AttackFinished_6x46)); - if (cmd.target_count > min(cmd.header.size - 2, cmd.targets.size())) { - throw runtime_error("invalid attack finished command"); - } - forward_subcommand_with_entity_targets_transcode_t(c, msg); - co_return; -} - -static asio::awaitable on_cast_technique(shared_ptr c, SubcommandMessage& msg) { - const auto& cmd = msg.check_size_t( - offsetof(G_CastTechnique_6x47, targets), sizeof(G_CastTechnique_6x47)); - if (cmd.target_count > min(cmd.header.size - 2, cmd.targets.size())) { - throw runtime_error("invalid cast technique command"); - } - forward_subcommand_with_entity_targets_transcode_t(c, msg); - co_return; -} - -static asio::awaitable on_execute_photon_blast(shared_ptr c, SubcommandMessage& msg) { - const auto& cmd = msg.check_size_t( - offsetof(G_ExecutePhotonBlast_6x49, targets), sizeof(G_ExecutePhotonBlast_6x49)); - if (cmd.target_count > min(cmd.header.size - 3, cmd.targets.size())) { - throw runtime_error("invalid subtract PB energy command"); - } - forward_subcommand_with_entity_targets_transcode_t(c, msg); - co_return; -} - static asio::awaitable on_npc_control(shared_ptr c, SubcommandMessage& msg) { const auto& cmd = msg.check_size_t(); // Don't allow NPC control commands if there is a player in the relevant slot @@ -5272,10 +5245,10 @@ const vector subcommand_definitions{ /* 6x43 */ {0x3A, 0x3F, 0x43, on_forward_check_game_client}, /* 6x44 */ {0x3B, 0x40, 0x44, on_forward_check_game_client}, /* 6x45 */ {0x3C, 0x41, 0x45, on_forward_check_game_client}, - /* 6x46 */ {NONE, 0x42, 0x46, on_attack_finished}, - /* 6x47 */ {0x3D, 0x43, 0x47, on_cast_technique}, + /* 6x46 */ {NONE, 0x42, 0x46, forward_subcommand_with_entity_targets_transcode_t}, + /* 6x47 */ {0x3D, 0x43, 0x47, forward_subcommand_with_entity_targets_transcode_t}, /* 6x48 */ {NONE, NONE, 0x48, on_cast_technique_finished}, - /* 6x49 */ {0x3E, 0x44, 0x49, on_execute_photon_blast}, + /* 6x49 */ {0x3E, 0x44, 0x49, forward_subcommand_with_entity_targets_transcode_t}, /* 6x4A */ {0x3F, 0x45, 0x4A, on_change_hp}, /* 6x4B */ {0x40, 0x46, 0x4B, on_change_hp}, /* 6x4C */ {0x41, 0x47, 0x4C, on_change_hp}, diff --git a/src/SendCommands.hh b/src/SendCommands.hh index 0d29ea13..b53d0d61 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -118,8 +118,12 @@ void send_command_vt(std::shared_ptr ch, uint16_t command, uint32_t fla } template -void send_command_t_vt(std::shared_ptr c, uint16_t command, - uint32_t flag, const StructT& data, const std::vector& array_data) { +void send_command_t_vt( + std::shared_ptr c, + uint16_t command, + uint32_t flag, + const StructT& data, + const std::vector& array_data) { std::string all_data(reinterpret_cast(&data), sizeof(StructT)); all_data.append(reinterpret_cast(array_data.data()), array_data.size() * sizeof(EntryT));