From 8a0c075c2aa068bd0b877e22024ba3e3ef255277 Mon Sep 17 00:00:00 2001 From: KiloOnline Date: Thu, 11 Dec 2025 15:35:53 -0800 Subject: [PATCH 1/4] feat: enable toggling promo coop items Linux support is needed, as well as adding multiple arguments for the command autofill. --- src/Cheats.cpp | 52 ++++++++++++++++++++++++++++++++++- src/Cheats.hpp | 3 ++ src/Modules/Server.cpp | 3 ++ src/Offsets/Portal 2 9568.hpp | 4 +++ 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/Cheats.cpp b/src/Cheats.cpp index 3cd1769d..885d5630 100644 --- a/src/Cheats.cpp +++ b/src/Cheats.cpp @@ -50,7 +50,7 @@ Variable sar_patch_cfg("sar_patch_cfg", "0", 0, 1, "Patches Crouch Flying Glitch Variable sar_prevent_ehm("sar_prevent_ehm", "0", 0, 1, "Prevents Entity Handle Misinterpretation (EHM) from happening.\n"); Variable sar_disable_weapon_sway("sar_disable_weapon_sway", "0", 0, 1, "Disables the viewmodel lagging behind.\n"); Variable sar_disable_viewmodel_shadows("sar_disable_viewmodel_shadows", "0", 0, 1, "Disables the shadows on the viewmodel.\n"); -Variable sar_floor_reportals("sar_floor_reportals", "0", "Toggles floor reportals. Requires cheats.\n", FCVAR_CHEAT); +Variable sar_floor_reportals("sar_floor_reportals", "0", "Toggles floor reportals.\n"); Variable sar_loads_coop_dots("sar_loads_coop_dots", "0", "Toggles the loading screen dots during map transitions in coop.\n"); Variable sar_disable_autograb("sar_disable_autograb", "0", 0, 1, "Disables the auto-grab in coop. Requires host to enable it for everyone that also enables it.\n"); @@ -65,6 +65,9 @@ Variable mm_session_sys_delay_create_host; Variable hide_gun_when_holding; Variable r_flashlightbrightness; +int origPortal2PromoFlagsValue = 0; // By default, nothing is loaded. +int *g_nPortal2PromoFlags = nullptr; + // TSP only void IN_BhopDown(const CCommand& args) { if (!client->KeyDown || !client->in_jump) return; @@ -399,6 +402,8 @@ void Cheats::Init() { g_autoGrabPatchClient->Restore(); } + g_nPortal2PromoFlags = *reinterpret_cast(Memory::Scan(MODULE("server"), Offsets::Portal2PromoFlags_ADDR, 2)); // Note: Has to be active before map loads. + Variable::RegisterAll(); Command::RegisterAll(); } @@ -589,3 +594,48 @@ void Cheats::CheckAutoGrab() { g_autoGrabPatchClient->Restore(); } } + +DECL_AUTO_COMMAND_COMPLETION(sar_set_promo_items_state, ({"skins", "helmet", "antenna"})) // TODO: Add support for autofilling multiple args. +CON_COMMAND_F_COMPLETION(sar_set_promo_items_state, "Enables coop promotional items on spawn.", FCVAR_CHEAT, AUTOCOMPLETION_FUNCTION(sar_set_promo_items_state)) { + if (!g_nPortal2PromoFlags) { + console->Print("Could not find PromoFlags global!\n"); + return; + } + static bool hasCheckedOriginalValue = false; + if (!hasCheckedOriginalValue) { + origPortal2PromoFlagsValue = *g_nPortal2PromoFlags; + hasCheckedOriginalValue = true; + } + bool bSkin = (_stricmp(args[1], "skins") == 0); + bool bHelmet = (_stricmp(args[1], "helmet") == 0); + bool bAntenna = (_stricmp(args[1], "antenna") == 0); + if (!bSkin && !bHelmet && !bAntenna) { + console->Print("Invalid first argument.\n"); + return; + } + bool bAdding = (_stricmp(args[2], "add") == 0); + bool bRemoving = (_stricmp(args[2], "remove") == 0); + if (!bAdding && !bRemoving) { + console->Print("Invalid second argument.\n"); + return; + } + if (bSkin) { + if (bAdding) { + *g_nPortal2PromoFlags |= (1 << 0); + } else if (bRemoving) { + *g_nPortal2PromoFlags &= ~(1 << 0); + } + } else if (bHelmet) { + if (bAdding) { + *g_nPortal2PromoFlags |= (1 << 1); + } else if (bRemoving) { + *g_nPortal2PromoFlags &= ~(1 << 1); + } + } else if (bAntenna) { + if (bAdding) { + *g_nPortal2PromoFlags |= (1 << 2); + } else if (bRemoving) { + *g_nPortal2PromoFlags &= ~(1 << 2); + } + } +} diff --git a/src/Cheats.hpp b/src/Cheats.hpp index 28141e5e..5284bc3d 100644 --- a/src/Cheats.hpp +++ b/src/Cheats.hpp @@ -50,3 +50,6 @@ extern Variable hide_gun_when_holding; extern Variable r_flashlightbrightness; extern Command sar_togglewait; + +extern int origPortal2PromoFlagsValue; +extern int *g_nPortal2PromoFlags; diff --git a/src/Modules/Server.cpp b/src/Modules/Server.cpp index 2bfa3623..08650e75 100644 --- a/src/Modules/Server.cpp +++ b/src/Modules/Server.cpp @@ -285,6 +285,9 @@ DETOUR(Server::PlayerRunCommand, CUserCmd *cmd, void *moveHelper) { Cheats::AutoStrafe(slot, thisptr, cmd); Cheats::CheckFloorReportals(); + if (!sv_cheats.GetBool()) { + *g_nPortal2PromoFlags = origPortal2PromoFlagsValue; // We only want to check this once per map load, to preserve the intended behavior. + } inputHud.SetInputInfo(slot, cmd->buttons, {cmd->sidemove, cmd->forwardmove, cmd->upmove}); diff --git a/src/Offsets/Portal 2 9568.hpp b/src/Offsets/Portal 2 9568.hpp index e616cf66..f6ba2594 100644 --- a/src/Offsets/Portal 2 9568.hpp +++ b/src/Offsets/Portal 2 9568.hpp @@ -523,6 +523,10 @@ SIGSCAN_DEFAULT(FloorReportalBranch, "75 7D 8B 8E C0 04 00 00", SIGSCAN_DEFAULT(CPortal_Player__PollForUseEntity_CheckMP, "74 ? ? ? 8B 82 ? ? ? ? FF D0 84 C0 74 ? 8B CE", "74 ? 8B 10 83 EC 0C 50 FF 92 88 00 00 00 83 C4 10 84 C0 ? ? ? ? ? ? ? ? ? ? ? ? ? 00 00") // "OnJump" xref -> CPortal_Player:PreThink -> CBasePlayer::PreThink -> CBasePlayer::ItemPreFrame -> CBasePlayer::PlayerUse -> CPortal_Player vtable offset -> CPortal_Player::PlayerUse -> Second function call from disassembly -> CPortal_Player::PollForUseEntity -> jz instruction + // TODO:: Linux support. +SIGSCAN_DEFAULT(Portal2PromoFlags_ADDR, "F6 05 ? ? ? ? ? 74 6D", + "") // CPortal_Player::GiveDefaultItems -> portal2 promo flag check + // VPhysics OFFSET_EMPTY(DestroyEnvironment) OFFSET_EMPTY(GetActiveEnvironmentByIndex) From 1a1b11e24425325cca9899afa329eb8af1305f9c Mon Sep 17 00:00:00 2001 From: KiloOnline Date: Sun, 14 Dec 2025 21:08:58 -0800 Subject: [PATCH 2/4] fix: Various fixes --- src/Cheats.cpp | 2 +- src/Modules/Server.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Cheats.cpp b/src/Cheats.cpp index 885d5630..3731b276 100644 --- a/src/Cheats.cpp +++ b/src/Cheats.cpp @@ -65,7 +65,7 @@ Variable mm_session_sys_delay_create_host; Variable hide_gun_when_holding; Variable r_flashlightbrightness; -int origPortal2PromoFlagsValue = 0; // By default, nothing is loaded. +int origPortal2PromoFlagsValue; int *g_nPortal2PromoFlags = nullptr; // TSP only diff --git a/src/Modules/Server.cpp b/src/Modules/Server.cpp index 08650e75..2eed6608 100644 --- a/src/Modules/Server.cpp +++ b/src/Modules/Server.cpp @@ -285,7 +285,7 @@ DETOUR(Server::PlayerRunCommand, CUserCmd *cmd, void *moveHelper) { Cheats::AutoStrafe(slot, thisptr, cmd); Cheats::CheckFloorReportals(); - if (!sv_cheats.GetBool()) { + if (!sv_cheats.GetBool() && origPortal2PromoFlagsValue) { *g_nPortal2PromoFlags = origPortal2PromoFlagsValue; // We only want to check this once per map load, to preserve the intended behavior. } From d7bd24e381a6016b6971248546e7eb72c7d1f37e Mon Sep 17 00:00:00 2001 From: ThisAMJ <69196954+ThisAMJ@users.noreply.github.com> Date: Mon, 15 Dec 2025 20:20:13 +1100 Subject: [PATCH 3/4] fixup: unintended change --- src/Cheats.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cheats.cpp b/src/Cheats.cpp index 3731b276..0118b9d0 100644 --- a/src/Cheats.cpp +++ b/src/Cheats.cpp @@ -50,7 +50,7 @@ Variable sar_patch_cfg("sar_patch_cfg", "0", 0, 1, "Patches Crouch Flying Glitch Variable sar_prevent_ehm("sar_prevent_ehm", "0", 0, 1, "Prevents Entity Handle Misinterpretation (EHM) from happening.\n"); Variable sar_disable_weapon_sway("sar_disable_weapon_sway", "0", 0, 1, "Disables the viewmodel lagging behind.\n"); Variable sar_disable_viewmodel_shadows("sar_disable_viewmodel_shadows", "0", 0, 1, "Disables the shadows on the viewmodel.\n"); -Variable sar_floor_reportals("sar_floor_reportals", "0", "Toggles floor reportals.\n"); +Variable sar_floor_reportals("sar_floor_reportals", "0", "Toggles floor reportals. Requires cheats.\n", FCVAR_CHEAT); Variable sar_loads_coop_dots("sar_loads_coop_dots", "0", "Toggles the loading screen dots during map transitions in coop.\n"); Variable sar_disable_autograb("sar_disable_autograb", "0", 0, 1, "Disables the auto-grab in coop. Requires host to enable it for everyone that also enables it.\n"); From 3734d366d68e7c2205a79987d20f0a54bcf8a3fd Mon Sep 17 00:00:00 2001 From: ThisAMJ <69196954+ThisAMJ@users.noreply.github.com> Date: Mon, 15 Dec 2025 21:55:24 +1100 Subject: [PATCH 4/4] fix: linux, use patch class complete redesign of how the command works lol idk what/how to test this so lmk if i broke it --- docs/cvars.md | 3 +- src/Cheats.cpp | 75 ++++++++++++++++------------------- src/Cheats.hpp | 3 +- src/Modules/Server.cpp | 4 +- src/Offsets/Portal 2 9568.hpp | 6 +-- src/Utils/Memory.cpp | 1 + src/Utils/Memory.hpp | 7 ++++ 7 files changed, 50 insertions(+), 49 deletions(-) diff --git a/docs/cvars.md b/docs/cvars.md index f7692ed3..d9c7d80a 100644 --- a/docs/cvars.md +++ b/docs/cvars.md @@ -262,7 +262,7 @@ |sar_hud_ghost_spec|0|Show the name of the ghost you're currently spectating.| |sar_hud_grounded|0|Draws the state of player being on ground.| |sar_hud_groundframes|0|Draws the number of ground frames since last landing. Setting it to 2 preserves the value.| -|sar_hud_groundspeed|0|Draw the speed of the player upon leaving the ground.
0 = Default,
1 = Groundspeed,
2 = Groundspeed (Gain)| +|sar_hud_groundspeed|0|Draw the speed of the player upon leaving the ground.
0 = Default
1 = Groundspeed
2 = Groundspeed (Gain)| |sar_hud_hide_text|cmd|sar_hud_hide_text \ - hides the nth text value in the HUD| |sar_hud_inspection|0|Draws entity inspection data.| |sar_hud_jump|0|Draws current jump distance.| @@ -503,6 +503,7 @@ |sar_scrollspeed_y|210|Scroll speed HUD y offset.| |sar_seamshot_finder|0|Enables or disables seamshot finder overlay.| |sar_session|cmd|sar_session - prints the current tick of the server since it has loaded| +|sar_set_promo_items_state|cmd|sar_set_promo_items_state \... - enables coop promotional items on spawn.| |sar_show_entinp|0|Print all entity inputs to console.| |sar_skiptodemo|cmd|sar_skiptodemo \ - skip demos in demo queue to this demo| |sar_speedrun_autoreset_clear|cmd|sar_speedrun_autoreset_clear - stop using the autoreset file| diff --git a/src/Cheats.cpp b/src/Cheats.cpp index 0118b9d0..511fb323 100644 --- a/src/Cheats.cpp +++ b/src/Cheats.cpp @@ -65,9 +65,6 @@ Variable mm_session_sys_delay_create_host; Variable hide_gun_when_holding; Variable r_flashlightbrightness; -int origPortal2PromoFlagsValue; -int *g_nPortal2PromoFlags = nullptr; - // TSP only void IN_BhopDown(const CCommand& args) { if (!client->KeyDown || !client->in_jump) return; @@ -334,6 +331,7 @@ Memory::Patch *g_floorReportalPatch; Memory::Patch *g_coopLoadingDotsPatch; Memory::Patch *g_autoGrabPatchServer; Memory::Patch *g_autoGrabPatchClient; +Memory::Patch *g_promoFlagsPatch; void Cheats::Init() { sv_laser_cube_autoaim = Variable("sv_laser_cube_autoaim"); @@ -402,7 +400,13 @@ void Cheats::Init() { g_autoGrabPatchClient->Restore(); } - g_nPortal2PromoFlags = *reinterpret_cast(Memory::Scan(MODULE("server"), Offsets::Portal2PromoFlags_ADDR, 2)); // Note: Has to be active before map loads. + g_promoFlagsPatch = new Memory::Patch(); + auto portal2PromoFlags = Memory::Scan(MODULE("server"), Offsets::Portal2PromoFlagsSig, Offsets::Portal2PromoFlagsOff); + if (portal2PromoFlags) { + unsigned char promoFlagsByte = 0x00; + g_promoFlagsPatch->Execute(portal2PromoFlags, &promoFlagsByte, 1); // Note: Has to be active before map loads. + g_promoFlagsPatch->Restore(); + } Variable::RegisterAll(); Command::RegisterAll(); @@ -427,6 +431,8 @@ void Cheats::Shutdown() { SAFE_DELETE(g_autoGrabPatchServer); g_autoGrabPatchClient->Restore(); SAFE_DELETE(g_autoGrabPatchClient); + g_promoFlagsPatch->Restore(); + SAFE_DELETE(g_promoFlagsPatch); } @@ -596,46 +602,35 @@ void Cheats::CheckAutoGrab() { } DECL_AUTO_COMMAND_COMPLETION(sar_set_promo_items_state, ({"skins", "helmet", "antenna"})) // TODO: Add support for autofilling multiple args. -CON_COMMAND_F_COMPLETION(sar_set_promo_items_state, "Enables coop promotional items on spawn.", FCVAR_CHEAT, AUTOCOMPLETION_FUNCTION(sar_set_promo_items_state)) { - if (!g_nPortal2PromoFlags) { - console->Print("Could not find PromoFlags global!\n"); - return; +CON_COMMAND_F_COMPLETION(sar_set_promo_items_state, "sar_set_promo_items_state ... - enables coop promotional items on spawn.\n", FCVAR_CHEAT, AUTOCOMPLETION_FUNCTION(sar_set_promo_items_state)) { + if (!g_promoFlagsPatch || !g_promoFlagsPatch->IsInit()) { + return console->Print("sar_set_promo_items_state is not available.\n"); } - static bool hasCheckedOriginalValue = false; - if (!hasCheckedOriginalValue) { - origPortal2PromoFlagsValue = *g_nPortal2PromoFlags; - hasCheckedOriginalValue = true; - } - bool bSkin = (_stricmp(args[1], "skins") == 0); - bool bHelmet = (_stricmp(args[1], "helmet") == 0); - bool bAntenna = (_stricmp(args[1], "antenna") == 0); - if (!bSkin && !bHelmet && !bAntenna) { - console->Print("Invalid first argument.\n"); - return; - } - bool bAdding = (_stricmp(args[2], "add") == 0); - bool bRemoving = (_stricmp(args[2], "remove") == 0); - if (!bAdding && !bRemoving) { - console->Print("Invalid second argument.\n"); - return; + + if (args.ArgC() < 2) { + return console->Print(sar_set_promo_items_state.ThisPtr()->m_pszHelpString); } - if (bSkin) { - if (bAdding) { - *g_nPortal2PromoFlags |= (1 << 0); - } else if (bRemoving) { - *g_nPortal2PromoFlags &= ~(1 << 0); + + unsigned char targetFlags = 0; + for (int i = 1; i < args.ArgC(); i++) { + if (strcasecmp(args[i], "off") == 0) { + g_promoFlagsPatch->Restore(); + return; } - } else if (bHelmet) { - if (bAdding) { - *g_nPortal2PromoFlags |= (1 << 1); - } else if (bRemoving) { - *g_nPortal2PromoFlags &= ~(1 << 1); + if (strcasecmp(args[i], "all") == 0) { + targetFlags = 0b111; + break; } - } else if (bAntenna) { - if (bAdding) { - *g_nPortal2PromoFlags |= (1 << 2); - } else if (bRemoving) { - *g_nPortal2PromoFlags &= ~(1 << 2); + if (strcasecmp(args[i], "skins") == 0) {; + targetFlags |= 0b001; + } else if (strcasecmp(args[i], "helmet") == 0) { + targetFlags |= 0b010; + } else if (strcasecmp(args[i], "antenna") == 0) { + targetFlags |= 0b100; + } else { + return console->Print(sar_set_promo_items_state.ThisPtr()->m_pszHelpString); } } + g_promoFlagsPatch->Restore(); + g_promoFlagsPatch->Execute(&targetFlags, 1); } diff --git a/src/Cheats.hpp b/src/Cheats.hpp index 5284bc3d..95a055c1 100644 --- a/src/Cheats.hpp +++ b/src/Cheats.hpp @@ -51,5 +51,4 @@ extern Variable r_flashlightbrightness; extern Command sar_togglewait; -extern int origPortal2PromoFlagsValue; -extern int *g_nPortal2PromoFlags; +extern Memory::Patch *g_promoFlagsPatch; diff --git a/src/Modules/Server.cpp b/src/Modules/Server.cpp index 2eed6608..164a4744 100644 --- a/src/Modules/Server.cpp +++ b/src/Modules/Server.cpp @@ -285,9 +285,7 @@ DETOUR(Server::PlayerRunCommand, CUserCmd *cmd, void *moveHelper) { Cheats::AutoStrafe(slot, thisptr, cmd); Cheats::CheckFloorReportals(); - if (!sv_cheats.GetBool() && origPortal2PromoFlagsValue) { - *g_nPortal2PromoFlags = origPortal2PromoFlagsValue; // We only want to check this once per map load, to preserve the intended behavior. - } + if (!sv_cheats.GetBool()) g_promoFlagsPatch->Restore(); // We only want to check this once per map load, to preserve the intended behavior. inputHud.SetInputInfo(slot, cmd->buttons, {cmd->sidemove, cmd->forwardmove, cmd->upmove}); diff --git a/src/Offsets/Portal 2 9568.hpp b/src/Offsets/Portal 2 9568.hpp index f6ba2594..d132ec06 100644 --- a/src/Offsets/Portal 2 9568.hpp +++ b/src/Offsets/Portal 2 9568.hpp @@ -523,9 +523,9 @@ SIGSCAN_DEFAULT(FloorReportalBranch, "75 7D 8B 8E C0 04 00 00", SIGSCAN_DEFAULT(CPortal_Player__PollForUseEntity_CheckMP, "74 ? ? ? 8B 82 ? ? ? ? FF D0 84 C0 74 ? 8B CE", "74 ? 8B 10 83 EC 0C 50 FF 92 88 00 00 00 83 C4 10 84 C0 ? ? ? ? ? ? ? ? ? ? ? ? ? 00 00") // "OnJump" xref -> CPortal_Player:PreThink -> CBasePlayer::PreThink -> CBasePlayer::ItemPreFrame -> CBasePlayer::PlayerUse -> CPortal_Player vtable offset -> CPortal_Player::PlayerUse -> Second function call from disassembly -> CPortal_Player::PollForUseEntity -> jz instruction - // TODO:: Linux support. -SIGSCAN_DEFAULT(Portal2PromoFlags_ADDR, "F6 05 ? ? ? ? ? 74 6D", - "") // CPortal_Player::GiveDefaultItems -> portal2 promo flag check +SIGSCAN_DEFAULT(Portal2PromoFlagsSig, "F6 05 ? ? ? ? 02 74 ? 8B CE", + "A1 ? ? ? ? A8 02") // "#P2_WearableType_Flag" xref -> CPortal_Player::GiveDefaultItems -> bitwise & -> portal2 promo flag +OFFSET_DEFAULT(Portal2PromoFlagsOff, 2, 1) // VPhysics OFFSET_EMPTY(DestroyEnvironment) diff --git a/src/Utils/Memory.cpp b/src/Utils/Memory.cpp index 8c883161..c58d57ab 100644 --- a/src/Utils/Memory.cpp +++ b/src/Utils/Memory.cpp @@ -245,6 +245,7 @@ Memory::Patch::~Patch() { } bool Memory::Patch::Execute() { if (this->isPatched) return true; // already executed + if (!this->IsInit()) return false; unsigned char *tmpPatch = new unsigned char[this->size]; // We create another patch, because this->patch is gonna be deleted memcpy(tmpPatch, this->patch, this->size); diff --git a/src/Utils/Memory.hpp b/src/Utils/Memory.hpp index b281f9a3..593506e5 100644 --- a/src/Utils/Memory.hpp +++ b/src/Utils/Memory.hpp @@ -50,6 +50,13 @@ namespace Memory { bool Execute(uintptr_t location, unsigned char (&bytes)[size]) { return Execute(location, bytes, size); } + bool Execute(unsigned char *bytes, size_t size) { + return Execute(location, bytes, size); + } + template + bool Execute(unsigned char (&bytes)[size]) { + return Execute(location, bytes, size); + } bool Restore(); bool IsPatched(); bool IsInit();