From d740d4850467ad7edb2052ac75540d6acaa9791e Mon Sep 17 00:00:00 2001 From: KiloOnline Date: Thu, 11 Dec 2025 15:35:53 -0800 Subject: [PATCH 1/5] 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 885e5b9d..263369e4 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 56ec981d..da46a656 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 1b4227e7..b9c64652 100644 --- a/src/Offsets/Portal 2 9568.hpp +++ b/src/Offsets/Portal 2 9568.hpp @@ -537,6 +537,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 36ecd48e9f1b9ad2eeec31abe5ed5b8cd4b3ae2b Mon Sep 17 00:00:00 2001 From: KiloOnline Date: Sun, 14 Dec 2025 21:08:58 -0800 Subject: [PATCH 2/5] 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 263369e4..4f8358f7 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 da46a656..782cc3bc 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 c738ec5e81e0051090ff1a49bd38642e0de04fea 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/5] 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 4f8358f7..94f74e26 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 f2a2788c30cbec2544bb04e5af2c9a245eba1e98 Mon Sep 17 00:00:00 2001 From: ThisAMJ <69196954+ThisAMJ@users.noreply.github.com> Date: Mon, 9 Feb 2026 21:46:43 +1100 Subject: [PATCH 4/5] 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 | 1 + src/Cheats.cpp | 75 ++++++++++++++++------------------- src/Cheats.hpp | 3 +- src/Modules/Server.cpp | 4 +- src/Offsets/Portal 2 9568.hpp | 6 +-- src/SourceAutoRecord.vcxproj | 2 +- src/Utils/Memory.cpp | 1 + src/Utils/Memory.hpp | 7 ++++ 8 files changed, 50 insertions(+), 49 deletions(-) diff --git a/docs/cvars.md b/docs/cvars.md index e39d272f..f605c189 100644 --- a/docs/cvars.md +++ b/docs/cvars.md @@ -514,6 +514,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 94f74e26..79d3eb9e 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 782cc3bc..b687276a 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 b9c64652..820e0826 100644 --- a/src/Offsets/Portal 2 9568.hpp +++ b/src/Offsets/Portal 2 9568.hpp @@ -537,9 +537,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/SourceAutoRecord.vcxproj b/src/SourceAutoRecord.vcxproj index 4c28a686..640a7c01 100644 --- a/src/SourceAutoRecord.vcxproj +++ b/src/SourceAutoRecord.vcxproj @@ -14,7 +14,7 @@ {3F6459A9-566E-4CAC-A412-C03FF51D67E9} SourceAutoRecord SourceAutoRecord - 10.0.19041.0 + 10.0 v142 DynamicLibrary false diff --git a/src/Utils/Memory.cpp b/src/Utils/Memory.cpp index 838d4bf9..0c8d9626 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(); From 86f2ff75492c3d067f7244c3826538897a2f455b Mon Sep 17 00:00:00 2001 From: ThisAMJ <69196954+ThisAMJ@users.noreply.github.com> Date: Mon, 9 Feb 2026 21:50:58 +1100 Subject: [PATCH 5/5] fix: make the thing actually work again --- src/Cheats.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cheats.cpp b/src/Cheats.cpp index 79d3eb9e..d50febd4 100644 --- a/src/Cheats.cpp +++ b/src/Cheats.cpp @@ -404,7 +404,7 @@ void Cheats::Init() { 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->Execute(Memory::Deref(portal2PromoFlags), &promoFlagsByte, 1); // Note: Has to be active before map loads. g_promoFlagsPatch->Restore(); }