From c2008f1f9c124ff5e8733c791f42497c3d2f374b Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sat, 11 Oct 2025 17:37:09 -0700 Subject: [PATCH] handle Ep1&2 NTE protected commands properly --- src/CommandFormats.hh | 84 ++++++++++--------- src/ProxyCommands.cc | 2 +- src/ReceiveSubcommands.cc | 4 +- src/SendCommands.cc | 36 +++++--- src/SendCommands.hh | 3 +- .../CallProtectedHandler.3___.patch.s | 6 +- 6 files changed, 76 insertions(+), 59 deletions(-) diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 27bd8f3d..898400e4 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -4236,13 +4236,14 @@ struct G_DarkFalzActions_6x19 { // 6x1A: Invalid subcommand -// 6x1B: Enable PK mode for player (not valid on Episode 3) (protected on V3/V4) +// 6x1B: Enable PK mode for player (not valid on Episode 3) (protected on GC +// NTE/V3/V4) struct G_EnablePKModeForPlayer_6x1B { G_ClientIDHeader header; } __packed_ws__(G_EnablePKModeForPlayer_6x1B, 4); -// 6x1C: Disable PK mode for player (protected on V3/V4) +// 6x1C: Disable PK mode for player (protected on GC NTE/V3/V4) struct G_DisablePKModeForPlayer_6x1C { G_ClientIDHeader header; @@ -4322,7 +4323,7 @@ struct G_TeleportPlayer_6x24 { VectorXYZF pos; } __packed_ws__(G_TeleportPlayer_6x24, 0x14); -// 6x25: Equip item (protected on V3/V4) +// 6x25: Equip item (protected on GC NTE/V3/V4) struct G_EquipItem_6x25 { G_ClientIDHeader header; @@ -4331,7 +4332,7 @@ struct G_EquipItem_6x25 { le_uint32_t equip_slot = 0; } __packed_ws__(G_EquipItem_6x25, 0x0C); -// 6x26: Unequip item (protected on V3/V4) +// 6x26: Unequip item (protected on GC NTE/V3/V4) struct G_UnequipItem_6x26 { G_ClientIDHeader header; @@ -4339,14 +4340,14 @@ struct G_UnequipItem_6x26 { le_uint32_t unused = 0; } __packed_ws__(G_UnequipItem_6x26, 0x0C); -// 6x27: Use item (protected on V3/V4) +// 6x27: Use item (protected on GC NTE/V3/V4) struct G_UseItem_6x27 { G_ClientIDHeader header; le_uint32_t item_id = 0; } __packed_ws__(G_UseItem_6x27, 8); -// 6x28: Feed MAG (protected on V3/V4) +// 6x28: Feed MAG (protected on GC NTE/V3/V4) struct G_FeedMag_6x28 { G_ClientIDHeader header; @@ -4355,7 +4356,7 @@ struct G_FeedMag_6x28 { } __packed_ws__(G_FeedMag_6x28, 0x0C); // 6x29: Delete inventory item (via bank deposit / sale / feeding MAG) -// (protected on V3 but not on V4) +// (protected on GC NTE/V3 but not on V4) // This subcommand is also used for reducing the size of stacks - if amount is // less than the stack count, the item is not deleted and its ID remains valid. @@ -4365,7 +4366,7 @@ struct G_DeleteInventoryItem_6x29 { le_uint32_t amount = 0; } __packed_ws__(G_DeleteInventoryItem_6x29, 0x0C); -// 6x2A: Drop item (protected on V3/V4) +// 6x2A: Drop item (protected on GC NTE/V3/V4) struct G_DropItem_6x2A { G_ClientIDHeader header; @@ -4375,7 +4376,7 @@ struct G_DropItem_6x2A { VectorXYZF pos; } __packed_ws__(G_DropItem_6x2A, 0x18); -// 6x2B: Create item in inventory (tekker/bank) (protected on V3/V4) +// 6x2B: Create item in inventory (tekker/bank) (protected on GC NTE/V3/V4) // On BB, the 6xBE command is used instead of 6x2B to create inventory items. // If equip_item is nonzero, the item is equipped immediately. @@ -4390,7 +4391,7 @@ struct G_CreateInventoryItem_PC_V3_BB_6x2B : G_CreateInventoryItem_DC_6x2B { parray unused2 = 0; } __packed_ws__(G_CreateInventoryItem_PC_V3_BB_6x2B, 0x1C); -// 6x2C: Impose hold (protected on V3/V4) +// 6x2C: Impose hold (protected on GC NTE/V3/V4) // This updates PlayerHoldState in the TObjPlayer struct, but the format is not // the same. The names here match the fields in PlayerHoldState. A player hold // prevents the player from moving further than the trigger radius from the @@ -4405,13 +4406,13 @@ struct G_ImposeHold_6x2C { le_float trigger_radius2 = 0.0f; // "2" here means "squared" } __packed_ws__(G_ImposeHold_6x2C, 0x14); -// 6x2D: Release hold (protected on V3/V4) +// 6x2D: Release hold (protected on GC NTE/V3/V4) struct G_ReleaseHold_6x2D { G_ClientIDHeader header; } __packed_ws__(G_ReleaseHold_6x2D, 4); -// 6x2E: Set and/or clear player flags (protected on V3/V4) +// 6x2E: Set and/or clear player flags (protected on GC NTE/V3/V4) struct G_SetOrClearPlayerFlags_6x2E { G_ClientIDHeader header; @@ -4419,7 +4420,7 @@ struct G_SetOrClearPlayerFlags_6x2E { le_uint32_t or_mask = 0; } __packed_ws__(G_SetOrClearPlayerFlags_6x2E, 0x0C); -// 6x2F: Change player HP +// 6x2F: Change player HP (protected on GC NTE only) struct G_ChangePlayerHP_6x2F { G_ClientIDHeader header; @@ -4428,7 +4429,7 @@ struct G_ChangePlayerHP_6x2F { le_uint16_t client_id = 0; } __packed_ws__(G_ChangePlayerHP_6x2F, 0x0C); -// 6x30: Change player level +// 6x30: Change player level (protected on GC NTE/V3 but not V4) // On DC NTE, the updated stats aren't sent, and the client may only gain a // single level at once. On other versions, this is not the case. @@ -4561,7 +4562,7 @@ struct G_TargetBase_6x3D { G_UnusedHeader header; } __packed_ws__(G_TargetBase_6x3D, 4); -// 6x3E: Stop moving (protected on V3/V4) +// 6x3E: Stop moving (protected on GC NTE/V3/V4) struct G_StopAtPosition_6x3E { G_ClientIDHeader header; @@ -4572,7 +4573,7 @@ struct G_StopAtPosition_6x3E { VectorXYZF pos; } __packed_ws__(G_StopAtPosition_6x3E, 0x18); -// 6x3F: Set position (protected on V3/V4) +// 6x3F: Set position (protected on GC NTE/V3/V4) struct G_SetPosition_6x3F { G_ClientIDHeader header; @@ -4583,7 +4584,7 @@ struct G_SetPosition_6x3F { VectorXYZF pos; } __packed_ws__(G_SetPosition_6x3F, 0x18); -// 6x40: Walk (protected on V3/V4) +// 6x40: Walk (protected on GC NTE/V3/V4) // If UDP mode is enabled, this command is sent via UDP. struct G_WalkToPosition_6x40 { @@ -4593,7 +4594,7 @@ struct G_WalkToPosition_6x40 { } __packed_ws__(G_WalkToPosition_6x40, 0x10); // 6x41: Move to position (v1) -// 6x42: Run (protected on V3/V4) +// 6x42: Run (protected on GC NTE/V3/V4) // Command 6x41 is completely ignored by v2 and later. // If UDP mode is enabled, this command is sent via UDP. // TODO: Should newserv translate 6x41 to 6x42? Is there any difference in how @@ -4604,9 +4605,9 @@ struct G_MoveToPosition_6x41_6x42 { VectorXZF pos; } __packed_ws__(G_MoveToPosition_6x41_6x42, 0x0C); -// 6x43: First attack (protected on V3/V4) -// 6x44: Second attack (protected on V3/V4) -// 6x45: Third attack (protected on V3/V4) +// 6x43: First attack (protected on GC NTE/V3/V4) +// 6x44: Second attack (protected on GC NTE/V3/V4) +// 6x45: Third attack (protected on GC NTE/V3/V4) // If UDP mode is enabled, these commands are sent via UDP. struct G_Attack_6x43_6x44_6x45 { @@ -4615,7 +4616,8 @@ struct G_Attack_6x43_6x44_6x45 { le_uint16_t unknown_a2 = 0; } __packed_ws__(G_Attack_6x43_6x44_6x45, 8); -// 6x46: Attack finished (sent after each of 43, 44, and 45) (protected on V3/V4) +// 6x46: Attack finished (sent after each of 43, 44, and 45) (protected on GC +// NTE/V3/V4) // The number of targets is not bounds-checked during byteswapping on GC // clients. The client only expects up to 10 entries here, so if the number of // targets is too large, the client will byteswap the function's return address @@ -4627,7 +4629,7 @@ struct G_AttackFinished_Header_6x46 { // Up to 10 TargetEntries are sent here } __packed_ws__(G_AttackFinished_Header_6x46, 8); -// 6x47: Cast technique (protected on V3/V4) +// 6x47: Cast technique (protected on GC NTE/V3/V4) // On GC, this command has the same bounds-check bug as 6x46. struct G_CastTechnique_Header_6x47 { @@ -4644,7 +4646,7 @@ struct G_CastTechnique_Header_6x47 { // Up to 10 TargetEntries are sent here } __packed_ws__(G_CastTechnique_Header_6x47, 8); -// 6x48: Cast technique complete (protected on V3/V4) +// 6x48: Cast technique complete (protected on GC NTE/V3/V4) struct G_CastTechniqueComplete_6x48 { G_ClientIDHeader header; @@ -4654,7 +4656,7 @@ struct G_CastTechniqueComplete_6x48 { le_uint16_t level = 0; } __packed_ws__(G_CastTechniqueComplete_6x48, 8); -// 6x49: Execute Photon Blast (protected on V3/V4) +// 6x49: Execute Photon Blast (protected on GC NTE/V3/V4) // On GC, this command has the same bounds-check bug as 6x46. struct G_ExecutePhotonBlast_Header_6x49 { @@ -4673,8 +4675,8 @@ struct G_ShieldAttack_6x4A { G_ClientIDHeader header; } __packed_ws__(G_ShieldAttack_6x4A, 4); -// 6x4B: Hit by enemy (protected on V3/V4) -// 6x4C: Hit by enemy (protected on V3/V4) +// 6x4B: Hit by enemy (protected on GC NTE/V3/V4) +// 6x4C: Hit by enemy (protected on GC NTE/V3/V4) struct G_HitByEnemy_6x4B_6x4C { G_ClientIDHeader header; @@ -4683,14 +4685,14 @@ struct G_HitByEnemy_6x4B_6x4C { VectorXZF velocity; } __packed_ws__(G_HitByEnemy_6x4B_6x4C, 0x10); -// 6x4D: Player died (protected on V3/V4) +// 6x4D: Player died (protected on GC NTE/V3/V4) struct G_PlayerDied_6x4D { G_ClientIDHeader header; le_uint32_t death_flags = 0; // Same as 6x70's death_flags field } __packed_ws__(G_PlayerDied_6x4D, 8); -// 6x4E: Player is dead can be revived (protected on V3/V4) +// 6x4E: Player is dead can be revived (protected on GC NTE/V3/V4) // This command creates the particle effect that Reverser and Moon Atomizers // can target. @@ -4724,7 +4726,7 @@ struct G_SetPlayerAngle_6x51 { parray unused; } __packed_ws__(G_SetPlayerAngle_6x51, 8); -// 6x52: Set animation state (protected on V3/V4) +// 6x52: Set animation state (protected on GC NTE/V3/V4) struct G_SetAnimationState_6x52 { G_ClientIDHeader header; @@ -4733,7 +4735,7 @@ struct G_SetAnimationState_6x52 { le_uint32_t angle = 0; } __packed_ws__(G_SetAnimationState_6x52, 0x0C); -// 6x53: Unknown (supported; game only) (protected on V3/V4) +// 6x53: Unknown (supported; game only) (protected on GC NTE/V3/V4) struct G_Unknown_6x53 { G_ClientIDHeader header; @@ -4748,7 +4750,7 @@ struct G_Unknown_6x54 { G_ClientIDHeader header; } __packed_ws__(G_Unknown_6x54, 4); -// 6x55: Intra-map warp (protected on V3/V4) +// 6x55: Intra-map warp (protected on GC NTE/V3/V4) struct G_IntraMapWarp_6x55 { G_ClientIDHeader header; @@ -4757,7 +4759,7 @@ struct G_IntraMapWarp_6x55 { VectorXYZF to_pos; } __packed_ws__(G_IntraMapWarp_6x55, 0x20); -// 6x56: Set player position and angle (protected on V3/V4) +// 6x56: Set player position and angle (protected on GC NTE/V3/V4) struct G_SetPlayerPositionAndAngle_6x56 { G_ClientIDHeader header; @@ -4771,7 +4773,7 @@ struct G_Unknown_6x57 { G_ClientIDHeader header; } __packed_ws__(G_Unknown_6x57, 4); -// 6x58: Lobby animation (protected on V3/V4) +// 6x58: Lobby animation (protected on GC NTE/V3/V4) // If UDP mode is enabled, this command is sent via UDP. struct G_LobbyAnimation_6x58 { @@ -4821,7 +4823,7 @@ struct G_DropStackedItem_PC_V3_BB_6x5D : G_DropStackedItem_DC_6x5D { le_uint32_t unused3 = 0; } __packed_ws__(G_DropStackedItem_PC_V3_BB_6x5D, 0x28); -// 6x5E: Buy item at shop +// 6x5E: Buy item at shop (protected on GC NTE/V3) struct G_BuyShopItem_6x5E { G_ClientIDHeader header; @@ -5305,13 +5307,13 @@ struct G_GogoBall_6x79 { parray unused; } __packed_ws__(G_GogoBall_6x79, 0x18); -// 6x7A: Enable Stealth Suit effect (protected on V3/V4) +// 6x7A: Enable Stealth Suit effect (protected on GC NTE/V3/V4) struct G_EnableStealthSuitEffect_6x7A { G_ClientIDHeader header; } __packed_ws__(G_EnableStealthSuitEffect_6x7A, 4); -// 6x7B: Disable Stealth Suit effect (protected on V3/V4) +// 6x7B: Disable Stealth Suit effect (protected on GC NTE/V3/V4) struct G_DisableStealthSuitEffect_6x7B { G_ClientIDHeader header; @@ -5394,19 +5396,19 @@ struct G_TriggerTrap_6x80 { le_uint16_t what = 0; // Must be 0, 1, or 2 } __packed_ws__(G_TriggerTrap_6x80, 8); -// 6x81: Disable drop weapon on death (protected on V3/V4) +// 6x81: Disable drop weapon on death (protected on GC NTE/V3/V4) struct G_DisableDropWeaponOnDeath_6x81 { G_ClientIDHeader header; } __packed_ws__(G_DisableDropWeaponOnDeath_6x81, 4); -// 6x82: Enable drop weapon on death (protected on V3/V4) +// 6x82: Enable drop weapon on death (protected on GC NTE/V3/V4) struct G_EnableDropWeaponOnDeath_6x82 { G_ClientIDHeader header; } __packed_ws__(G_EnableDropWeaponOnDeath_6x82, 4); -// 6x83: Place trap (protected on V3/V4) +// 6x83: Place trap (protected on GC NTE/V3/V4) struct G_PlaceTrap_6x83 { G_ClientIDHeader header; @@ -5603,7 +5605,7 @@ struct G_UpdateEntityStat_6x9A { uint8_t amount = 0; } __packed_ws__(G_UpdateEntityStat_6x9A, 8); -// 6x9B: Level up all techniques (protected on V3/V4) +// 6x9B: Level up all techniques (protected on GC NTE/V3/V4) // Used in battle mode if the rules specify that techniques should level up // upon character death. diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index 1c59939b..e9f39794 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -2017,7 +2017,7 @@ asio::awaitable C_6x(shared_ptr c, Channel::Message& msg) case 0x4B: case 0x4C: if (c->check_flag(Client::Flag::INFINITE_HP_ENABLED)) { - send_change_player_hp(c->channel, c->lobby_client_id, PlayerHPChange::MAXIMIZE_HP, 0); + co_await send_change_player_hp(c, c->lobby_client_id, PlayerHPChange::MAXIMIZE_HP, 0); send_change_player_hp(c->proxy_session->server_channel, c->lobby_client_id, PlayerHPChange::MAXIMIZE_HP, 0); } break; diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 317ed2a1..6632e205 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -1684,7 +1684,7 @@ static asio::awaitable on_player_revived(shared_ptr c, SubcommandM forward_subcommand(c, msg); if ((l->check_flag(Lobby::Flag::CHEATS_ENABLED) || (c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE))) && c->check_flag(Client::Flag::INFINITE_HP_ENABLED)) { - send_change_player_hp(l, c->lobby_client_id, PlayerHPChange::MAXIMIZE_HP, 0); + co_await send_change_player_hp(l, c->lobby_client_id, PlayerHPChange::MAXIMIZE_HP, 0); } } co_return; @@ -1718,7 +1718,7 @@ static asio::awaitable on_change_hp(shared_ptr c, SubcommandMessag forward_subcommand(c, msg); if ((l->check_flag(Lobby::Flag::CHEATS_ENABLED) || c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE)) && c->check_flag(Client::Flag::INFINITE_HP_ENABLED)) { - send_change_player_hp(l, c->lobby_client_id, PlayerHPChange::MAXIMIZE_HP, 0); + co_await send_change_player_hp(l, c->lobby_client_id, PlayerHPChange::MAXIMIZE_HP, 0); } } diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 9ee5af42..1fa6450f 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -502,16 +502,14 @@ void send_function_call( ch->send(0xB2, 0x00, data); } -asio::awaitable send_protected_command( - std::shared_ptr c, const void* data, size_t size, bool echo_to_lobby) { +asio::awaitable send_protected_command(std::shared_ptr c, const void* data, size_t size, bool echo_to_lobby) { switch (c->version()) { case Version::DC_NTE: case Version::DC_11_2000: case Version::DC_V1: case Version::DC_V2: case Version::PC_NTE: - case Version::PC_V2: - case Version::GC_NTE: { + case Version::PC_V2: { auto l = echo_to_lobby ? c->lobby.lock() : nullptr; if (l) { send_command(l, 0x60, 0x00, data, size); @@ -521,6 +519,7 @@ asio::awaitable send_protected_command( co_return true; } + case Version::GC_NTE: case Version::GC_V3: case Version::XB_V3: case Version::GC_EP3_NTE: @@ -2624,25 +2623,40 @@ void send_player_stats_change(std::shared_ptr ch, uint16_t client_id, P send_command_vt(ch, (subs.size() > 0x400 / sizeof(G_UpdateEntityStat_6x9A)) ? 0x6C : 0x60, 0x00, subs); } -void send_change_player_hp(std::shared_ptr ch, uint16_t client_id, PlayerHPChange what, int16_t amount) { +static G_ChangePlayerHP_6x2F generate_hp_restore_command( + Version version, uint8_t client_id, PlayerHPChange what, int16_t amount) { uint8_t subcommand_number; - if (ch->version == Version::DC_NTE) { + if (version == Version::DC_NTE) { subcommand_number = 0x2B; - } else if (ch->version == Version::DC_11_2000) { + } else if (version == Version::DC_11_2000) { subcommand_number = 0x2D; } else { subcommand_number = 0x2F; } - G_ChangePlayerHP_6x2F cmd = { + return G_ChangePlayerHP_6x2F{ {subcommand_number, sizeof(G_ChangePlayerHP_6x2F) / 4, client_id}, static_cast(what), amount, client_id}; - send_command_t(ch, 0x60, 0x00, cmd); } -void send_change_player_hp(std::shared_ptr l, uint16_t client_id, PlayerHPChange what, int16_t amount) { +void send_change_player_hp( + std::shared_ptr ch, uint16_t client_id, PlayerHPChange what, int16_t amount) { + send_command_t(ch, 0x60, 0x00, generate_hp_restore_command(ch->version, client_id, what, amount)); +} + +asio::awaitable send_change_player_hp( + std::shared_ptr c, uint16_t client_id, PlayerHPChange what, int16_t amount) { + if ((c->version() == Version::GC_NTE) && (client_id == c->lobby_client_id)) { + auto cmd = generate_hp_restore_command(c->version(), client_id, what, amount); + co_await send_protected_command(c, &cmd, sizeof(cmd), false); + } else { + send_change_player_hp(c->channel, client_id, what, amount); + } +} + +asio::awaitable send_change_player_hp(std::shared_ptr l, uint16_t client_id, PlayerHPChange what, int16_t amount) { for (const auto& lc : l->clients) { if (lc) { - send_change_player_hp(lc->channel, client_id, what, amount); + co_await send_change_player_hp(lc, client_id, what, amount); } } } diff --git a/src/SendCommands.hh b/src/SendCommands.hh index 08daadb5..eaf7bc1b 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -353,7 +353,8 @@ enum class PlayerHPChange { }; void send_change_player_hp(std::shared_ptr ch, uint16_t client_id, PlayerHPChange what, int16_t amount); -void send_change_player_hp(std::shared_ptr l, uint16_t client_id, PlayerHPChange what, int16_t amount); +asio::awaitable send_change_player_hp(std::shared_ptr c, uint16_t client_id, PlayerHPChange what, int16_t amount); +asio::awaitable send_change_player_hp(std::shared_ptr l, uint16_t client_id, PlayerHPChange what, int16_t amount); asio::awaitable send_remove_negative_conditions(std::shared_ptr c); void send_remove_negative_conditions(std::shared_ptr ch, uint16_t client_id); diff --git a/system/client-functions/CallProtectedHandler/CallProtectedHandler.3___.patch.s b/system/client-functions/CallProtectedHandler/CallProtectedHandler.3___.patch.s index e729bd36..230561b8 100644 --- a/system/client-functions/CallProtectedHandler/CallProtectedHandler.3___.patch.s +++ b/system/client-functions/CallProtectedHandler/CallProtectedHandler.3___.patch.s @@ -2,7 +2,7 @@ .meta name="CallProtectedHandler" .meta description="" -.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0 +.versions 3OJT 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0 entry_ptr: reloc0: @@ -40,8 +40,8 @@ resume: get_data_addr: bl resume - .data - .data + .data + .data size: .data 0x00000000