fix memcpy call that gcc is unhappy with

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