diff --git a/.gitmodules b/.gitmodules index 77548434b..096b58f94 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,7 @@ [submodule "sdk"] path = sdk url = https://github.com/alliedmodders/hl2sdk.git + branch = cs2 [submodule "hl2sdk-manifests"] path = hl2sdk-manifests url = https://github.com/alliedmodders/hl2sdk-manifests diff --git a/AMBuildScript b/AMBuildScript index 7e67b9ba4..eb9bcac73 100644 --- a/AMBuildScript +++ b/AMBuildScript @@ -438,7 +438,7 @@ const char *PLUGIN_FULL_VERSION = \"{version}\"; cxx.cxxincludes += [ os.path.join(context.currentSourcePath), os.path.join(mms_core_path), - os.path.join(mms_core_path, 'sourcehook'), + os.path.join(self.mms_root, 'third_party', 'khook', 'include'), ] for other_sdk in self.sdk_manifests: diff --git a/AMBuilder b/AMBuilder index 7fb0fa26f..66d4347d9 100644 --- a/AMBuilder +++ b/AMBuilder @@ -13,23 +13,16 @@ for sdk_target in MMSPlugin.sdk_targets: os.path.join(builder.sourcePath, 'src', 'utils'), os.path.join(builder.sourcePath, 'public'), os.path.join(builder.sourcePath, 'sdk', 'thirdparty', 'protobuf-3.21.8', 'src'), - os.path.join(builder.sourcePath, 'vendor', 'funchook', 'include'), ] - target_folder = 'Debug' if builder.options.debug else 'Release' - if binary.compiler.target.platform == 'linux': binary.compiler.postlink += [ - os.path.join(builder.sourcePath, 'vendor', 'funchook', 'lib', target_folder, 'libfunchook.a'), - os.path.join(builder.sourcePath, 'vendor', 'funchook', 'lib', target_folder, 'libdistorm.a'), os.path.join(builder.sourcePath, 'sdk', 'lib', 'linux64', 'release', 'libprotobuf.a'), ] binary.sources += ['src/utils/plat_unix.cpp'] elif binary.compiler.target.platform == 'windows': binary.compiler.postlink += [ os.path.join('psapi.lib'), - os.path.join(builder.sourcePath, 'vendor', 'funchook', 'lib', target_folder, 'funchook.lib'), - os.path.join(builder.sourcePath, 'vendor', 'funchook', 'lib', target_folder, 'distorm.lib'), os.path.join(builder.sourcePath, 'sdk', 'lib' ,'public', 'win64', '2015', 'libprotobuf.lib'), os.path.join(builder.sourcePath, 'sdk', 'lib', 'public', 'win64', 'steam_api64.lib') ] @@ -44,6 +37,7 @@ for sdk_target in MMSPlugin.sdk_targets: 'src/commands.cpp', 'src/addresses.cpp', 'src/detours.cpp', + 'src/virtual_hooks.cpp', 'src/entities.cpp', 'src/events.cpp', 'src/utils/entity.cpp', @@ -52,6 +46,7 @@ for sdk_target in MMSPlugin.sdk_targets: 'src/utils/utils.cpp', 'src/utils/vscript_function.cpp', 'src/cs2_sdk/entity/services.cpp', + 'src/cs2_sdk/entity/customhudlayout.cpp', 'src/cs2_sdk/schema.cpp', 'src/ctimer.cpp', 'src/panoramavote.cpp', diff --git a/CS2Fixes.vcxproj b/CS2Fixes.vcxproj index 85d63b9b7..797abd0e6 100644 --- a/CS2Fixes.vcxproj +++ b/CS2Fixes.vcxproj @@ -73,12 +73,12 @@ cs2fixes - sdk\lib\public\win64;vendor/funchook/lib;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) + sdk\lib\public\win64;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) $(SolutionDir)/package/addons/cs2fixes/bin/win64/ cs2fixes - sdk\lib\public\win64;vendor/funchook/lib;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) + sdk\lib\public\win64;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) $(SolutionDir)/package/addons/cs2fixes/bin/win64/ @@ -119,12 +119,12 @@ Level3 true - __STDC_LIMIT_MACROS;X64BITS;WIN32;COMPILER_MSVC;COMPILER_MSVC64;_CRT_NO_VA_START_VALIDATION;_ITERATOR_DEBUG_LEVEL=0;_DEBUG;CS2Fixes_EXPORTS;_WINDOWS;_USRDLL;META_IS_SOURCE2;SUBHOOK_STATIC;%(PreprocessorDefinitions) + __STDC_LIMIT_MACROS;X64BITS;WIN32;COMPILER_MSVC;COMPILER_MSVC64;_CRT_NO_VA_START_VALIDATION;_ITERATOR_DEBUG_LEVEL=0;_DEBUG;CS2Fixes_EXPORTS;_WINDOWS;_USRDLL;META_IS_SOURCE2;%(PreprocessorDefinitions) true NotUsing pch.h stdcpp20 - build/cs2fixes.cs2/windows-x86_64;src/utils;src/cs2_sdk;sdk/public;sdk/public/tier0;sdk/game/shared;sdk/game/server;sdk/public/tier1;sdk/public/mathlib;minhook/include;$(MMSOURCE112)/core;$(MMSOURCE112)/core/sourcehook;$(MMSOURCE_DEV)/core;$(MMSOURCE_DEV)/core/sourcehook;vendor/subhook;vendor/funchook/include;sdk/public/entity2;sdk/public/game/server;sdk/thirdparty/protobuf-3.21.8/src;$(SolutionDir);%(AdditionalIncludeDirectories) + build/cs2fixes.cs2/windows-x86_64;src/utils;src/cs2_sdk;sdk/public;sdk/public/tier0;sdk/game/shared;sdk/game/server;sdk/public/tier1;sdk/public/mathlib;$(MMSOURCE112)/core;$(MMSOURCE112)/third_party/khook/include;$(MMSOURCE_DEV)/core;$(MMSOURCE_DEV)/third_party/khook/include;sdk/public/entity2;sdk/public/game/server;sdk/thirdparty/protobuf-3.21.8/src;$(SolutionDir);%(AdditionalIncludeDirectories) %(UndefinePreprocessorDefinitions) MultiThreadedDebug true @@ -133,8 +133,8 @@ Windows true false - interfaces.lib;mathlib.lib;tier0.lib;tier1.lib;psapi.lib;funchook.lib;distorm.lib;steam_api64.lib;vendor/protobuf-lib/Debug/libprotobufd.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) - sdk/lib/public/win64;vendor/funchook/lib/Debug;%(AdditionalLibraryDirectories) + interfaces.lib;mathlib.lib;tier0.lib;tier1.lib;psapi.lib;steam_api64.lib;vendor/protobuf-lib/Debug/libprotobufd.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + sdk/lib/public/win64;%(AdditionalLibraryDirectories) @@ -145,12 +145,12 @@ true true true - __STDC_LIMIT_MACROS;X64BITS;WIN32;COMPILER_MSVC;COMPILER_MSVC64;_CRT_NO_VA_START_VALIDATION;_ITERATOR_DEBUG_LEVEL=0;NDEBUG;_WINDOWS;CS2Fixes_EXPORTS;_USRDLL;META_IS_SOURCE2;SUBHOOK_STATIC;%(PreprocessorDefinitions) + __STDC_LIMIT_MACROS;X64BITS;WIN32;COMPILER_MSVC;COMPILER_MSVC64;_CRT_NO_VA_START_VALIDATION;_ITERATOR_DEBUG_LEVEL=0;NDEBUG;_WINDOWS;CS2Fixes_EXPORTS;_USRDLL;META_IS_SOURCE2;%(PreprocessorDefinitions) Default NotUsing pch.h stdcpp20 - build/cs2fixes.cs2/windows-x86_64;src/utils;src/cs2_sdk;sdk/public;sdk/public/tier0;sdk/game/shared;sdk/game/server;sdk/public/tier1;sdk/public/mathlib;minhook/include;$(MMSOURCE112)/core;$(MMSOURCE112)/core/sourcehook;$(MMSOURCE_DEV)/core;$(MMSOURCE_DEV)/core/sourcehook;vendor/subhook;vendor/funchook/include;sdk/public/entity2;sdk/public/game/server;sdk/thirdparty/protobuf-3.21.8/src;$(SolutionDir);%(AdditionalIncludeDirectories) + build/cs2fixes.cs2/windows-x86_64;src/utils;src/cs2_sdk;sdk/public;sdk/public/tier0;sdk/game/shared;sdk/game/server;sdk/public/tier1;sdk/public/mathlib;$(MMSOURCE112)/core;$(MMSOURCE112)/third_party/khook/include;$(MMSOURCE_DEV)/core;$(MMSOURCE_DEV)/third_party/khook/include;sdk/public/entity2;sdk/public/game/server;sdk/thirdparty/protobuf-3.21.8/src;$(SolutionDir);%(AdditionalIncludeDirectories) MultiThreaded %(UndefinePreprocessorDefinitions) true @@ -161,8 +161,8 @@ true true false - interfaces.lib;mathlib.lib;tier0.lib;tier1.lib;psapi.lib;funchook.lib;distorm.lib;steam_api64.lib;vendor/protobuf-lib/Release/libprotobuf.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) - sdk/lib/public/win64;vendor/funchook/lib/Release;%(AdditionalLibraryDirectories) + interfaces.lib;mathlib.lib;tier0.lib;tier1.lib;psapi.lib;steam_api64.lib;vendor/protobuf-lib/Release/libprotobuf.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + sdk/lib/public/win64;%(AdditionalLibraryDirectories) @@ -194,6 +194,7 @@ + @@ -225,6 +226,7 @@ + @@ -258,6 +260,8 @@ + + @@ -267,10 +271,11 @@ - + + @@ -308,6 +313,7 @@ + diff --git a/CS2Fixes.vcxproj.filters b/CS2Fixes.vcxproj.filters index 12b823713..d03d6a9b1 100644 --- a/CS2Fixes.vcxproj.filters +++ b/CS2Fixes.vcxproj.filters @@ -68,6 +68,9 @@ Source Files + + Source Files + Source Files @@ -200,6 +203,9 @@ Source Files\cs2_sdk\entity + + Source Files\cs2_sdk\entity + Source Files @@ -268,15 +274,15 @@ Header Files - - Header Files - Header Files Header Files + + Header Files + Header Files @@ -406,6 +412,9 @@ Header Files\cs2_sdk + + Header Files\cs2_sdk + Header Files @@ -442,6 +451,9 @@ Header Files\utils + + Header Files\utils + Header Files\cs2_sdk\entity @@ -451,5 +463,11 @@ Header Files + + Header Files\cs2_sdk\entity + + + Header Files\cs2_sdk\entity + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index a68009bc3..e080113ac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM ghcr.io/source2ze/build-containers:steamrt3 WORKDIR /app -RUN git clone https://github.com/alliedmodders/metamod-source +RUN git clone --recurse-submodules https://github.com/alliedmodders/metamod-source RUN git config --global --add safe.directory /app COPY ./docker-entrypoint.sh ./ diff --git a/cfg/cs2fixes/cs2fixes.cfg b/cfg/cs2fixes/cs2fixes.cfg index 847f0545f..8d400411b 100644 --- a/cfg/cs2fixes/cs2fixes.cfg +++ b/cfg/cs2fixes/cs2fixes.cfg @@ -28,6 +28,7 @@ cs2f_cvarwhitelist_enable 0 // Whether to enable the custom ConVar whitelist f cs2f_block_particle_msgs 0 // Whether to block CUserMsg_ParticleManager messages to fix lag/crashes, experimental cs2f_disable_setmodel 0 // Whether to disable SetModel usage from maps (custom input, cs_script function) cs2f_allow_duck_spam 0 // Whether to allow duck spamming by removing the duck slowdown, clients will only partially predict [0 = disabled, 1 = both teams, 2 = T only, 3 = CT only] +cs2f_thirdperson_enable 0 // Whether to enable thirdperson cs2f_beacon_particle "particles/cs2fixes/admin_beacon.vpcf" // .vpcf file to be precached and used for player beacon cs2f_motd_url "" // Server MOTD URL, shows up as a "Server Website" button in scoreboard @@ -71,7 +72,7 @@ cs2f_topdefender_print 1 // Whether to print defender stats to console at rou cs2f_topdefender_score 5000 // Score given to the top defender cs2f_topdefender_threshold 1000 // Damage threshold for Top Defenders to be shown on round end cs2f_topdefender_rate 1.0 // How often TopDefender stats get updated -cs2f_topdefender_clantag "[Top Defender]" // Clan tag given to the top defender +cs2f_topdefender_clantag "Top Defender" // Clan tag given to the top defender // Chat flood settings cs2f_flood_interval 0.75 // Amount of time allowed between chat messages acquiring flood tokens @@ -155,14 +156,12 @@ cs2f_idle_kick_min_players 0 // Minimum amount of connected clients to kick id cs2f_idle_kick_admins 1 // Whether to kick idle players with ADMFLAG_GENERIC // Button watch -cs2f_enable_button_watch 0 // INCOMPATIBLE WITH CS#. Whether to enable button watch or not. +cs2f_enable_button_watch 0 // Whether to enable button watch or not. // EntWatch Settings -entwatch_enable 0 // INCOMPATIBLE WITH CS#. Whether to enable EntWatch features +entwatch_enable 0 // Whether to enable EntWatch features entwatch_auto_filter 1 // Whether to automatically block non-item holders from triggering uses entwatch_clantag 1 // Whether to set item holder's clantag and set 9999 score -entwatch_clantag_colour_mode 1 // Whether to set item clantag colours (0=No colour, 1=Use item colour, 2=Use entwatch_clantag_colour value) -entwatch_clantag_colour "FFFFFF" // Hex colour (RRGGBB) to use if entwatch_clantag_colour_mode 2 entwatch_hud 1 // Whether to enable the EntWatch hud and related commands entwatch_score 9999 // Score to give item holders (0 = dont change score at all) Requires entwatch_clantag 1 entwatch_glow 1000 // Distance that dropped item weapon glow will be visible (0 = glow disabled) @@ -175,4 +174,5 @@ cs2f_hud_duration_leeway 2 // Extra seconds duration to leave hud messages vis // Map migrations cs2f_mapmigrations_20260121 2 // Current mode for 2026-01-21 CS2 update map migrations. [0 = Force disabled, 1 = Force enabled, 2 = Automatically enabled for maps updated before 2026-01-21 & disabled if updated after] -cs2f_mapmigrations_20260420 2 // Current mode for 2026-04-20 CS2 update map migrations. [0 = Force disabled, 1 = Force enabled, 2 = Automatically enabled for maps updated before 2026-04-20 & disabled if updated after] \ No newline at end of file +cs2f_mapmigrations_20260420 2 // Current mode for 2026-04-20 CS2 update map migrations. [0 = Force disabled, 1 = Force enabled, 2 = Automatically enabled for maps updated before 2026-04-20 & disabled if updated after] +cs2f_mapmigrations_20260922 2 // Current mode for 2026-09-22 CS2 update map migrations. [0 = Force disabled, 1 = Force enabled, 2 = Automatically enabled for maps updated before 2026-09-22 & disabled if updated after] \ No newline at end of file diff --git a/gamedata/cs2fixes.jsonc b/gamedata/cs2fixes.jsonc index 5f4784bda..508ccd738 100644 --- a/gamedata/cs2fixes.jsonc +++ b/gamedata/cs2fixes.jsonc @@ -5,15 +5,15 @@ "UTIL_SayTextFilter": { "library": "server", - "windows": "48 89 5C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 55 41 56 41 57 48 8D 6C 24 ? 48 81 EC ? ? ? ? 49 8B F8", - "linux": "55 48 8D 05 ? ? ? ? 48 89 E5 41 57 41 56 4C 8D 75 ? 41 55 41 89 CD 41 54 49 89 D4" + "windows": "48 89 5C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 55 41 56 41 57 48 8D 6C 24 ? 48 81 EC ? ? ? ? 49 8B D8", + "linux": "55 48 8D 05 ? ? ? ? 48 89 E5 41 57 41 56 4C 8D 75 ? 41 55 41 89 CD" }, // Called from Host_Say "UTIL_SayText2Filter": { "library": "server", - "windows": "48 89 5C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 55 41 56 41 57 48 8D 6C 24 ? 48 81 EC ? ? ? ? 45 0F B6 F0", - "linux": "55 48 8D 05 ? ? ? ? 48 89 E5 41 57 41 56 41 89 D6 31 D2" + "windows": "48 89 5C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 55 41 56 41 57 48 8D 6C 24 ? 48 81 EC ? ? ? ? 41 8B D8", + "linux": "55 48 8D 05 ? ? ? ? 48 89 E5 41 57 41 89 D7 31 D2" }, // idk a good way to find this again, i just brute forced the vtable. offset is 136 on CTriggerPush "TriggerPush_Touch": @@ -27,14 +27,14 @@ { "library": "server", "windows": "48 89 5C 24 ? 55 56 57 41 55 41 57 48 83 EC ? 44 8B 89", - "linux": "55 48 89 E5 41 57 49 89 FF 41 56 41 55 41 54 49 89 D4 53 48 89 F3 48 81 EC" + "linux": "55 48 89 E5 41 57 49 89 FF 41 56 41 55 41 54 49 89 D4 53 48 89 F3 48 81 EC ? ? ? ? 8B 97" }, // Check vauff's pin in #scripting "ServerMovementUnlock": { "library": "server", "windows": "0F 86 AF ? ? ? 0F 57 C0 0F 2E C2", - "linux": "0F 87 ? ? ? ? F3 0F 10 25 ? ? ? ? F3 0F 11 A5 ? ? ? ? 48 89 DE" + "linux": "76 ? F3 0F 51 C0 F3 0F 7E DB 49 8B 06" }, // String: "CCSPlayerPawnBase::SwitchTeam", just keep in mind this is actually CCSPlayerController::SwitchTeam "CCSPlayerController_SwitchTeam": @@ -47,28 +47,28 @@ "CheckJumpButtonWater": { "library": "server", - "windows": "C8 42 EB ? 4C 8B 77 ? 4D 39 66", - "linux": "C8 42 41 C7 85 ? ? ? ? ? ? ? ? 41 C7 85 ? ? ? ? ? ? ? ? E9" + "windows": "C8 42 EB ? 4C 8B 73 ? 4D 39 6E", + "linux": "C8 42 41 C7 84 24 ? ? ? ? ? ? ? ? 41 C7 84 24 ? ? ? ? ? ? ? ? E9" }, // "SetPosition" is passed to this "CEntitySystem_AddEntityIOEvent": { "library": "server", - "windows": "48 89 5C 24 18 4C 89 4C 24 20 48 89 4C 24 08 55 56 57 41 54 41 55 41 56 41 57 48 83 EC 20", + "windows": "48 89 5C 24 ? 48 89 74 24 ? 57 48 83 EC ? 49 8B F1 48 8B F9", "linux": "55 48 89 E5 41 55 49 89 CD 41 54 49 89 FC 53 BB" }, // "BombPlanted" is passed to this "CEntityInstance_AcceptInput": { "library": "server", - "windows": "48 89 5C 24 ? 48 89 74 24 ? 57 48 83 EC ? 49 8B F0 48 8B D9 48 8B 0D", - "linux": "55 48 89 E5 41 56 49 89 FE 41 55 48 8D 7D" + "windows": "48 89 5C 24 ? 48 89 6C 24 ? 56 57 41 56 48 81 EC ? ? ? ? 4D 8B F0 48 8B F1", + "linux": "55 48 89 E5 41 57 49 89 FF 41 56 48 8D BD ? ? ? ? 41 55 4C 8D AD" }, // Called by CEntityInstance_AcceptInput "CEntityIdentity_AcceptInput": { "library": "server", - "windows": "48 89 5C 24 ? 48 89 54 24 ? 48 89 4C 24 ? 55 56 57 41 54 41 55 41 56 41 57 48 8D 6C 24 ? 48 81 EC ? ? ? ? 48 8B 02", + "windows": "48 89 54 24 ? 48 89 4C 24 ? 55 53 56 57 41 55 41 56 41 57 48 8D 6C 24", "linux": "55 48 89 E5 41 57 41 56 4C 8D BD ? ? ? ? 4D 89 CE" }, // func_pushable inside CTriggerBrush::Use calls CEntityIOOutput::FireOutputInternal @@ -78,8 +78,8 @@ "CEntityIOOutput_FireOutputInternal": { "library": "server", - "windows": "4C 89 4C 24 ? 48 89 4C 24 ? 53 56", - "linux": "55 48 89 E5 41 57 49 89 FF 41 56 41 55 41 54 49 89 D4 53 48 89 F3 48 83 EC ? 48 8D 05" + "windows": "48 8B C4 48 89 58 ? 48 89 70 ? 48 89 48 ? 55", + "linux": "55 48 89 E5 41 57 49 89 FF 41 56 41 55 41 54 49 89 D4 53 48 89 F3 48 81 EC ? ? ? ? 48 8D 05" }, // "multi_manager" is passed to this "CGameEntitySystem_FindEntityByClassName": @@ -160,13 +160,13 @@ { "library": "server", "windows": "48 8B C4 4C 89 48 ? 48 89 48 ? 55 41 54", - "linux": "55 48 89 E5 41 57 41 56 41 55 41 54 41 89 F4 53 48 8D 35 ? ? ? ? 48 89 FB" + "linux": "55 48 89 E5 41 57 41 56 49 89 FE 41 55 41 54 53 48 81 EC ? ? ? ? 48 8D 05 ? ? ? ? F3 0F 11 85 ? ? ? ? 89 B5" }, "CCSPlayer_WeaponServices_CanUse": { "library": "server", "windows": "48 89 5C 24 ? 48 89 6C 24 ? 56 57 41 54 41 56 41 57 48 83 EC ? 48 8B 01 48 8B FA", - "linux": "55 48 8D 15 ? ? ? ? 48 89 E5 41 55 41 54 49 89 FC 53 48 89 F3 48 83 EC ? 48 8B 07 48 8B 80" + "linux": "55 48 8D 15 ? ? ? ? 48 89 E5 41 55 41 54 49 89 F4 53 48 89 FB 48 83 EC ? 48 8B 07 48 8B 80 ? ? ? ? 48 39 D0 0F 85 ? ? ? ? 80 BF" }, "CCSPlayer_WeaponServices_EquipWeapon": { @@ -198,7 +198,7 @@ { "library": "server", "windows": "48 89 5C 24 08 48 89 74 24 10 48 89 7C 24 18 4C 89 74 24 20 55 48 8D 6C 24 D1", - "linux": "55 48 89 E5 41 57 41 56 41 55 41 89 CD 41 54 53 48 89 D3" + "linux": "55 48 89 E5 41 57 41 56 41 55 41 89 CD 41 54 53 48 81 EC" }, // search for "land_%s", this is called after that string is referenced (the one with 4 parameters). // (function that calls it also contains "T_Default.SuitLand"). @@ -222,12 +222,6 @@ "windows": "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 41 56 48 83 EC ? 41 0F B6 F0", "linux": "55 48 89 E5 41 57 49 89 FF 41 56 41 55 41 54 53 89 D3 48 81 EC ? ? ? ? 40 38 B7" }, - "CTakeDamageInfo": - { - "library": "server", - "windows": "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 41 56 48 83 EC ? 45 33 F6 48 C7 41", - "linux": "49 BB ? ? ? ? ? ? ? ? 55 48 89 E5 48 83 EC ? 4C 8D 15" - }, // "%sRecv usercmd %d. Margin:%5.1fms net +%2d queue =%5.1f total\n" "ProcessUsercmds": { @@ -244,8 +238,8 @@ "CGamePlayerEquip_InputTriggerForActivatedPlayer": { "library": "server", - "windows": "48 89 5C 24 18 56 48 83 EC 20 48 8B 1A", - "linux": "55 48 89 E5 41 56 41 55 41 54 53 48 83 EC ? 48 8B 1E" + "windows": "48 89 5C 24 ? 56 48 83 EC ? 48 8B 1A", + "linux": "55 48 89 E5 41 56 41 55 41 54 53 48 8B 1E 48 85 DB 74" }, // Return value of this function is used to determine whether "NETWORK_DISCONNECT_REJECT_SERVERFULL to %s: Cannot get free client\n" gets printed "GetFreeClient": @@ -267,7 +261,7 @@ { "library": "server", "windows": "4C 89 44 24 ? F3 0F 11 4C 24 ? 55 53 56", - "linux": "55 48 89 E5 41 57 41 56 41 55 41 54 49 89 FC 53 48 8D 3D ? ? ? ? 48 81 EC ? ? ? ? 48 89 B5 ? ? ? ? BE ? ? ? ? F3 0F 11 85" + "linux": "48 B9 ? ? ? ? ? ? ? ? 55 48 89 E5 41 57 49 89 FF" }, "TraceFunc": { @@ -279,8 +273,8 @@ "TraceShape": { "library": "server", - "windows": "48 89 54 24 ? 48 89 4C 24 ? 55 53 56 57 41 56 41 57 48 8D AC 24 ? ? ? ? B8", - "linux": "55 48 89 E5 41 57 49 89 CF 41 56 49 89 F6 41 55 4D 89 C5 41 54 49 89 D4" + "windows": "48 89 54 24 ? 48 89 4C 24 ? 55 53 56 57 41 54 41 56 41 57", + "linux": "55 48 8D 05 ? ? ? ? 48 89 E5 41 57 49 89 F7 41 56 48 8D 35 ? ? ? ? 49 89 CE" }, "CBasePlayerPawn_GetEyePosition": { @@ -294,19 +288,12 @@ "windows": "48 89 5C 24 ? 57 48 81 EC ? ? ? ? 48 8B F9 48 8B DA 48 8B 89 ? ? ? ? 48 85 C9", "linux": "55 48 89 E5 41 54 53 48 89 FB 48 83 C4 ? 48 8B BF ? ? ? ? 48 85 FF" }, - // Only ever referenced right next to string "TestActivator" - "CBaseFilter_InputTestActivator": - { - "library": "server", - "windows": "48 89 5C 24 ? 57 48 83 EC ? 4C 8B 02", - "linux": "55 48 89 E5 41 54 49 89 F4 53 48 89 FB 48 83 EC ? 48 8B 07 48 8B 16" - }, // "Kicking user %s (sv_kick_players_with_cooldown=%d)\n" "GameSystem_Think_CheckSteamBan": { "library": "server", - "windows": "41 54 48 81 EC ? ? ? ? BA ? ? ? ? 48 8D 0D ? ? ? ? E8 ? ? ? ? 48 85 C0", - "linux": "55 48 8D 3D ? ? ? ? BE ? ? ? ? 48 89 E5 41 57 41 56 41 55 41 54 53 48 83 EC" + "windows": "41 54 48 81 EC ? ? ? ? 48 8B 05 ? ? ? ? 44 8B 60", + "linux": "55 48 89 E5 41 57 41 56 41 55 41 54 53 48 83 EC ? 48 8B 05 ? ? ? ? 44 8B 68 ? 45 85 ED 0F 8E ? ? ? ? 8B 15" }, // Location to CUtlMap unk that is referenced on Windows by function with "Notification about user penalty: %u/%u (%u sec)\n" string // On Linux, a qword appears twice in GameSystem_Think_CheckSteamBan, and thrice in a sub-function of the function used for Windows (1 top, 2 bottom), the only other reference to this qword is some convar registration function with two unks above, sm_mapGcBanInformation is the unk further away @@ -328,8 +315,8 @@ "CCSPlayer_ItemServices_CanAcquire": { "library": "server", - "windows": "44 89 44 24 ? 48 89 54 24 ? 48 89 4C 24 ? 55 53 56 57 41 55 41 56 41 57 48 8B EC", - "linux": "55 48 89 E5 41 57 41 56 41 55 49 89 CD 41 54 49 89 FC 53 48 89 F3 48 83 EC" + "windows": "44 89 44 24 ? 48 89 54 24 ? 48 89 4C 24 ? 55 53 56 57 41 54 41 55 41 56 41 57 48 8B EC", + "linux": "55 48 89 E5 41 57 41 56 49 89 F6 41 55 41 54 49 89 CC 53 48 89 FB 48 83 EC ? 4C 8B 6F" }, "CTriggerGravity_GravityTouch": { @@ -349,13 +336,13 @@ { "library": "server", "windows": "48 89 5C 24 ? 55 56 57 41 54 41 55 41 56 41 57 48 8D AC 24 ? ? ? ? 48 81 EC ? ? ? ? 4C 8B E9", - "linux": "55 31 C0 48 89 E5 41 57 41 56 41 55 41 54 41 89 F4" + "linux": "55 31 C0 48 89 E5 41 57 41 56 41 55 41 54 53 48 89 FB 48 8D 3D ? ? ? ? 48 81 EC ? ? ? ? 89 B5" }, // Called in return by function with "Usage: setang_exact pitch yaw " string "CBasePlayerPawn_SnapViewAngles": { "library": "server", - "windows": "48 89 5C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 55 48 8D 6C 24 ? 48 81 EC ? ? ? ? 48 8B DA 48 8B F1 48 8D 55", + "windows": "48 89 7C 24 ? 55 48 8B EC 48 81 EC ? ? ? ? 48 8B F9 48 8D 4D ? E8 ? ? ? ? F2 0F 10 87", "linux": "55 48 89 E5 41 57 49 89 FF 48 89 F7 41 56 41 55 41 54 53 48 81 EC ? ? ? ? E8" }, // These two functions are called by CEnvBeam & CEnvLaser member functions, one of those functions has "env_beams cannot have the end entity be the beam itself" string @@ -378,6 +365,13 @@ "library": "server", "windows": "48 83 EC ? 4C 8B C2 48 8D 0D ? ? ? ? 48 8D 54 24 ? FF", "linux": "55 48 89 F2 48 8D 35 ? ? ? ? 48 89 E5 48 83 EC" + }, + // "CWorldRendererMgr::CreateWorld_Internal( %s ): Blocking load because marked for deletion during load" + "CWorldRendererMgr_CreateWorld_Internal": + { + "library": "worldrenderer", + "windows": "48 89 5C 24 ? 48 89 54 24 ? 55 56 57 48 81 EC", + "linux": "55 48 89 E5 41 56 41 55 41 54 49 89 FC 53 48 89 F3 48 83 EC ? F6 46" } }, "Offsets": @@ -389,63 +383,63 @@ }, "CBaseEntity::SetOwner": { - "windows": 53, - "linux": 52 + "windows": 54, + "linux": 53 }, "CBaseEntity::Use": { - "windows": 145, - "linux": 144 + "windows": 147, + "linux": 146 }, "CBaseEntity::StartTouch": { - "windows": 148, - "linux": 147 + "windows": 150, + "linux": 149 }, "CBaseEntity::Touch": { - "windows": 149, - "linux": 148 + "windows": 151, + "linux": 150 }, "CBaseEntity::EndTouch": { - "windows": 150, - "linux": 149 + "windows": 152, + "linux": 151 }, "Teleport": { - "windows": 163, - "linux": 162 + "windows": 165, + "linux": 164 }, "CollisionRulesChanged": { - "windows": 186, - "linux": 185 + "windows": 188, + "linux": 187 }, "CCSPlayerController_Respawn": { - "windows": 270, - "linux": 272 + "windows": 274, + "linux": 276 }, // CBaseTrigger "PassesTriggerFilters": { - "windows": 269, - "linux": 270 + "windows": 271, + "linux": 272 }, // Actually a bit of a wrapper function now? Eventually calls a long function with "player_hurt" in the middle and then inserts userid, health, priority, attacker strings "CCSPlayerPawn::OnTakeDamage_Alive": { - "windows": 252, - "linux": 253 + "windows": 255, + "linux": 256 }, // Look for the kill command, go through its callback and you should a find call like this, with v9 being a pawn pointer: - // return (*(*v9 + 2976LL))(v9, v27, 0LL); - // 2976 (372 * 8) is the offset + // return (*(*v9 + 3096LL))(v9, v27, 0LL); + // 3096 (387 * 8) is the offset "CBasePlayerPawn_CommitSuicide": { - "windows": 384, - "linux": 384 + "windows": 387, + "linux": 387 }, "GameEntitySystem": { @@ -455,15 +449,15 @@ // In the function with "[%03d] Found: %s, firing\n", you'll find a call into a pointer offset just a bit higher, that's the offset * 8 "CGameRules_FindPickerEntity": { - "windows": 26, - "linux": 27 + "windows": 25, + "linux": 26 }, // engine - // "tried to sprint to a non-client", there will be a check above like this: if ( a2 >= *(v5 + 632) ), note that this is a CUtlVector + // "tried to sprint to a non-client", there will be a check above like this: if ( a2 >= *(v5 + 616) ), note that this is a CUtlVector "CNetworkGameServer_ClientList": { - "windows": 73, - "linux": 73 + "windows": 77, + "linux": 77 }, // Right above "mapgroup workshop;" string there is a virtual call to this on g_pGameTypes using "workshop" string "IGameTypes_CreateWorkshopMapGroup": @@ -474,8 +468,8 @@ // There's no easy way to find this, but it's a function that checks entity flags (0x370) and ends by calling RemoveFlag with 0x800000 (FL_BASEVELOCITY) "CCSPlayer_MovementServices::CheckMovingGround": { - "windows": 44, - "linux": 45 + "windows": 45, + "linux": 46 }, "CCSPlayer_WeaponServices::DropWeapon": { @@ -490,8 +484,8 @@ // server.dll -> xref 'sv_phys_stop_at_collision' first __fastcall "CVPhys2World::GetTouchingList": { - "windows": 121, - "linux": 121 + "windows": 122, + "linux": 122 } }, "Patches": @@ -499,7 +493,7 @@ "ServerMovementUnlock": { "windows": "E9 B0 00 00 00 90", - "linux": "90 90 90 90 90 90" + "linux": "EB" }, "FixWaterFloorJump": { diff --git a/sdk b/sdk index e7ed969ea..625bfd4e3 160000 --- a/sdk +++ b/sdk @@ -1 +1 @@ -Subproject commit e7ed969ea8cb9afebd19ba6c5f262c9048890543 +Subproject commit 625bfd4e39816ad0e1eae3b3144a3b5425c5c7a0 diff --git a/src/addresses.cpp b/src/addresses.cpp index 5fae6ed6a..9b4ce84d8 100644 --- a/src/addresses.cpp +++ b/src/addresses.cpp @@ -1,4 +1,4 @@ -/** +/** * ============================================================================= * CS2Fixes * Copyright (C) 2023-2026 Source2ZE @@ -27,10 +27,10 @@ #include "tier0/memdbgon.h" -#define RESOLVE_SIG(gameConfig, name, variable) \ - variable = (decltype(variable))gameConfig->ResolveSignature(name); \ - if (!variable) \ - return false; \ +#define RESOLVE_SIG(name, variable) \ + variable = (decltype(variable))g_GameConfig->ResolveSignature(name); \ + if (!variable) \ + return false; \ Message("Found %s at 0x%p\n", name, variable); #define RESOLVE_SF(scriptDesc, funcName, variable) \ @@ -41,7 +41,7 @@ else \ Message("Found %s::%s at 0x%p\n", scriptDesc->m_pszClassname, funcName, variable.GetPtr()); -bool addresses::Initialize(CGameConfig* g_GameConfig) +bool addresses::Initialize() { modules::engine = new CModule(ROOTBIN, "engine2"); modules::tier0 = new CModule(ROOTBIN, "tier0"); @@ -51,6 +51,7 @@ bool addresses::Initialize(CGameConfig* g_GameConfig) modules::networksystem = new CModule(ROOTBIN, "networksystem"); modules::vphysics2 = new CModule(ROOTBIN, "vphysics2"); modules::matchmaking = new CModule(GAMEBIN, "matchmaking"); + modules::worldrenderer = new CModule(ROOTBIN, "worldrenderer"); modules::client = nullptr; if (!CommandLine()->HasParm("-dedicated")) @@ -62,29 +63,29 @@ bool addresses::Initialize(CGameConfig* g_GameConfig) modules::hammer = new CModule(ROOTBIN, "tools/hammer"); #endif - RESOLVE_SIG(g_GameConfig, "SetGroundEntity", addresses::SetGroundEntity); - RESOLVE_SIG(g_GameConfig, "CCSPlayerController_SwitchTeam", addresses::CCSPlayerController_SwitchTeam); - RESOLVE_SIG(g_GameConfig, "CBasePlayerController_SetPawn", addresses::CBasePlayerController_SetPawn); - RESOLVE_SIG(g_GameConfig, "CBaseModelEntity_SetModel", addresses::CBaseModelEntity_SetModel); - RESOLVE_SIG(g_GameConfig, "CEntitySystem_AddEntityIOEvent", addresses::CEntitySystem_AddEntityIOEvent); - RESOLVE_SIG(g_GameConfig, "CEntityInstance_AcceptInput", addresses::CEntityInstance_AcceptInput); - RESOLVE_SIG(g_GameConfig, "CGameEntitySystem_FindEntityByClassName", addresses::CGameEntitySystem_FindEntityByClassName); - RESOLVE_SIG(g_GameConfig, "CGameEntitySystem_FindEntityByName", addresses::CGameEntitySystem_FindEntityByName); - RESOLVE_SIG(g_GameConfig, "CGameRules_TerminateRound", addresses::CGameRules_TerminateRound); - RESOLVE_SIG(g_GameConfig, "CreateEntityByName", addresses::CreateEntityByName); - RESOLVE_SIG(g_GameConfig, "DispatchSpawn", addresses::DispatchSpawn); - RESOLVE_SIG(g_GameConfig, "DispatchParticleEffect", addresses::DispatchParticleEffect); - RESOLVE_SIG(g_GameConfig, "CBaseEntity_EmitSoundFilter", addresses::CBaseEntity_EmitSoundFilter); - RESOLVE_SIG(g_GameConfig, "CBaseEntity_SetMoveType", addresses::CBaseEntity_SetMoveType); - RESOLVE_SIG(g_GameConfig, "CTakeDamageInfo", addresses::CTakeDamageInfo_Constructor); - RESOLVE_SIG(g_GameConfig, "CCSPlayer_WeaponServices_EquipWeapon", addresses::CCSPlayer_WeaponServices_EquipWeapon); - RESOLVE_SIG(g_GameConfig, "GetSpawnGroups", addresses::GetSpawnGroups); - RESOLVE_SIG(g_GameConfig, "CBasePlayerPawn_SnapViewAngles", addresses::CBasePlayerPawn_SnapViewAngles); - - return InitializeBanMap(g_GameConfig); + RESOLVE_SIG("SetGroundEntity", addresses::SetGroundEntity); + RESOLVE_SIG("CCSPlayerController_SwitchTeam", addresses::CCSPlayerController_SwitchTeam); + RESOLVE_SIG("CBasePlayerController_SetPawn", addresses::CBasePlayerController_SetPawn); + RESOLVE_SIG("CBaseModelEntity_SetModel", addresses::CBaseModelEntity_SetModel); + RESOLVE_SIG("CEntitySystem_AddEntityIOEvent", addresses::CEntitySystem_AddEntityIOEvent); + RESOLVE_SIG("CEntityInstance_AcceptInput", addresses::CEntityInstance_AcceptInput); + RESOLVE_SIG("CGameEntitySystem_FindEntityByClassName", addresses::CGameEntitySystem_FindEntityByClassName); + RESOLVE_SIG("CGameEntitySystem_FindEntityByName", addresses::CGameEntitySystem_FindEntityByName); + RESOLVE_SIG("CGameRules_TerminateRound", addresses::CGameRules_TerminateRound); + RESOLVE_SIG("CreateEntityByName", addresses::CreateEntityByName); + RESOLVE_SIG("DispatchSpawn", addresses::DispatchSpawn); + RESOLVE_SIG("DispatchParticleEffect", addresses::DispatchParticleEffect); + RESOLVE_SIG("CBaseEntity_EmitSoundFilter", addresses::CBaseEntity_EmitSoundFilter); + RESOLVE_SIG("CBaseEntity_SetMoveType", addresses::CBaseEntity_SetMoveType); + RESOLVE_SIG("CCSPlayer_WeaponServices_EquipWeapon", addresses::CCSPlayer_WeaponServices_EquipWeapon); + RESOLVE_SIG("GetSpawnGroups", addresses::GetSpawnGroups); + RESOLVE_SIG("CBasePlayerPawn_SnapViewAngles", addresses::CBasePlayerPawn_SnapViewAngles); + RESOLVE_SIG("CBaseEntity_TakeDamageOld", addresses::CBaseEntity_TakeDamageOld); + + return InitializeBanMap(); } -bool addresses::InitializeBanMap(CGameConfig* g_GameConfig) +bool addresses::InitializeBanMap() { // This signature directly points to the instruction referencing sm_mapGcBanInformation uintptr_t pAddr = (uintptr_t)g_GameConfig->ResolveSignature("CCSGameRules__sm_mapGcBanInformation"); diff --git a/src/addresses.h b/src/addresses.h index d70fb3b61..a168a8b17 100644 --- a/src/addresses.h +++ b/src/addresses.h @@ -1,4 +1,4 @@ -/** +/** * ============================================================================= * CS2Fixes * Copyright (C) 2023-2026 Source2ZE @@ -36,6 +36,7 @@ namespace modules inline CModule* networksystem; inline CModule* vphysics2; inline CModule* matchmaking; + inline CModule* worldrenderer; #ifdef _WIN32 inline CModule* hammer; #endif @@ -59,8 +60,11 @@ class CTakeDamageInfo; class CCSPlayer_WeaponServices; class CBasePlayerWeapon; class CSpawnGroupMgrGameSystem; +struct CPulseArgumentPack; +struct CPulseInputParamMap; struct EmitSound_t; struct StartSoundEventInfo; +struct CTakeDamageResult; // Can't be forward-declared, can't include cgamerules.h.. just define it here struct CGcBanInformation_t @@ -73,8 +77,8 @@ struct CGcBanInformation_t namespace addresses { - bool Initialize(CGameConfig* g_GameConfig); - bool InitializeBanMap(CGameConfig* g_GameConfig); + bool Initialize(); + bool InitializeBanMap(); bool InitializeVScriptFunctions(); inline CUtlOrderedMap* sm_mapGcBanInformation; @@ -86,7 +90,7 @@ namespace addresses inline void(FASTCALL* UTIL_Remove)(CEntityInstance*); inline void(FASTCALL* CEntitySystem_AddEntityIOEvent)(CEntitySystem* pEntitySystem, CEntityInstance* pTarget, const char* pszInput, - CEntityInstance* pActivator, CEntityInstance* pCaller, variant_t* value, float flDelay, void*, void*); + CEntityInstance* pActivator, CEntityInstance* pCaller, variant_t* value, float flDelay, CPulseArgumentPack* pArgumentPack, CPulseInputParamMap* pParamMap); inline void(FASTCALL* CEntityInstance_AcceptInput)(CEntityInstance* pThis, const char* pInputName, CEntityInstance* pActivator, CEntityInstance* pCaller, variant_t* value); @@ -102,11 +106,10 @@ namespace addresses char iAttachmentPoint, CUtlSymbolLarge iAttachmentName, bool bResetAllParticlesOnEntity, int nSplitScreenPlayerSlot, IRecipientFilter* a7, byte* a8); inline StartSoundEventInfo(FASTCALL* CBaseEntity_EmitSoundFilter)(IRecipientFilter& filter, CEntityIndex ent, const EmitSound_t& params); inline void(FASTCALL* CBaseEntity_SetMoveType)(CBaseEntity* pThis, MoveType_t nMoveType, MoveCollide_t nMoveCollide); - inline void(FASTCALL* CTakeDamageInfo_Constructor)(CTakeDamageInfo* pThis, CBaseEntity* pInflictor, CBaseEntity* pAttacker, CBaseEntity* pAbility, - const Vector* vecDamageForce, const Vector* vecDamagePosition, float flDamage, int bitsDamageType, int iCustomDamage, void* a10); inline void(FASTCALL* CCSPlayer_WeaponServices_EquipWeapon)(CCSPlayer_WeaponServices* pWeaponServices, CBasePlayerWeapon* pPlayerWeapon); inline void(FASTCALL* GetSpawnGroups)(CSpawnGroupMgrGameSystem* pSpawnGroupMgr, CUtlVector* pList); inline void(FASTCALL* CBasePlayerPawn_SnapViewAngles)(CBasePlayerPawn* pPawn, QAngle* pAngles); + inline int64(FASTCALL* CBaseEntity_TakeDamageOld)(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult); inline CVScriptFunction SetGravityScale; inline CVScriptFunction ScriptSetEntityName; diff --git a/src/buttonwatch.cpp b/src/buttonwatch.cpp index 3f3ae9f81..d92d1968b 100644 --- a/src/buttonwatch.cpp +++ b/src/buttonwatch.cpp @@ -32,21 +32,11 @@ #include "entity/clogiccase.h" #include "entity/cpointviewcontrol.h" -CConVar g_cvarEnableButtonWatch( - "cs2f_enable_button_watch", FCVAR_NONE, "INCOMPATIBLE WITH CS#. Whether to enable button watch or not.", false, - [](CConVar* cvar, CSplitScreenSlot slot, const bool* new_val, const bool* old_val) { - if (!(*new_val) || !SetupFireOutputInternalDetour()) - { - mapIOFunctions.erase("buttonwatch"); - cvar->Set(false); - } - else if (!IsButtonWatchEnabled()) - mapIOFunctions["buttonwatch"] = ButtonWatch; - }); +CConVar g_cvarEnableButtonWatch("cs2f_enable_button_watch", FCVAR_NONE, "Whether to enable button watch or not.", false); CON_COMMAND_CHAT_FLAGS(bw, "- Toggle button watch display", ADMFLAG_GENERIC) { - if (!IsButtonWatchEnabled()) + if (!g_cvarEnableButtonWatch.Get()) { ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Button watch is disabled on this server."); return; @@ -84,17 +74,10 @@ CON_COMMAND_CHAT_FLAGS(bw, "- Toggle button watch display", ADMFLAG_GENERIC) } } -bool IsButtonWatchEnabled() -{ - return std::any_of(mapIOFunctions.begin(), mapIOFunctions.end(), [](const auto& p) { - return p.first == "buttonwatch"; - }); -} - std::map mapRecentEnts; -void ButtonWatch(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEntityInstance* pCaller, const CVariant* value, float flDelay) +void ButtonWatch(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEntityInstance* pCaller, float flDelay) { - if (!IsButtonWatchEnabled() || !GetGlobals() || V_stricmp(pThis->m_pDesc->m_pName, "OnPressed") || !pActivator || !((CBaseEntity*)pActivator)->IsPawn() || !pCaller || mapRecentEnts.contains(pCaller->GetEntityIndex().Get())) + if (!GetGlobals() || V_stricmp(pThis->m_pDesc->m_pName, "OnPressed") || !pActivator || !((CBaseEntity*)pActivator)->IsPawn() || !pCaller || mapRecentEnts.contains(pCaller->GetEntityIndex().Get())) return; CCSPlayerController* ccsPlayer = CCSPlayerController::FromPawn(static_cast(pActivator)); diff --git a/src/buttonwatch.h b/src/buttonwatch.h index 1d22e9b4c..bb72ffa26 100644 --- a/src/buttonwatch.h +++ b/src/buttonwatch.h @@ -20,5 +20,6 @@ #pragma once #include "detours.h" -bool IsButtonWatchEnabled(); -void ButtonWatch(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEntityInstance* pCaller, const CVariant* value, float flDelay); \ No newline at end of file +extern CConVar g_cvarEnableButtonWatch; + +void ButtonWatch(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEntityInstance* pCaller, float flDelay); \ No newline at end of file diff --git a/src/cdetour.h b/src/cdetour.h deleted file mode 100644 index 2c34db3cf..000000000 --- a/src/cdetour.h +++ /dev/null @@ -1,153 +0,0 @@ -/** - * ============================================================================= - * CS2Fixes - * Copyright (C) 2023-2026 Source2ZE - * ============================================================================= - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License, version 3.0, as published by the - * Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - */ - -#pragma once -#include "addresses.h" -#include "funchook.h" -#include "gameconfig.h" -#include "module.h" -#include "plat.h" -#include "utlvector.h" -#include - -class CDetourBase -{ -public: - virtual const char* GetName() = 0; - virtual bool CreateDetour(CGameConfig* gameConfig) = 0; - virtual void FreeDetour() = 0; - virtual void EnableDetour() = 0; - virtual void DisableDetour() = 0; -}; - -extern CUtlVector g_vecDetours; - -template -class CDetour : public CDetourBase -{ -public: - CDetour(T* pfnDetour, const char* pszName); - - ~CDetour() - { - FreeDetour(); - } - - bool CreateDetour(CGameConfig* gameConfig) override; - void EnableDetour(); - void DisableDetour(); - void FreeDetour() override; - const char* GetName() override { return m_pszName; } - T* GetFunc() { return m_pfnFunc; } - - // Shorthand for calling original. - template - auto operator()(Args&&... args) - { - return std::invoke(m_pfnFunc, std::forward(args)...); - } - -private: - CModule** m_pModule; - T* m_pfnDetour; - const char* m_pszName; - byte* m_pSignature; - const char* m_pSymbol; - T* m_pfnFunc; - funchook_t* m_hook; - bool m_bInstalled; -}; - -template -CDetour::CDetour(T* pfnDetour, const char* pszName) : - m_pfnDetour(pfnDetour), m_pszName(pszName) -{ - m_hook = nullptr; - m_bInstalled = false; - m_pSignature = nullptr; - m_pSymbol = nullptr; - m_pModule = nullptr; - - g_vecDetours.AddToTail(this); -} - -template -bool CDetour::CreateDetour(CGameConfig* gameConfig) -{ - m_pfnFunc = (T*)gameConfig->ResolveSignature(m_pszName); - if (!m_pfnFunc) - return false; - - T* pFunc = m_pfnFunc; - - m_hook = funchook_create(); - funchook_prepare(m_hook, (void**)&m_pfnFunc, (void*)m_pfnDetour); - - Message("Detoured %s at 0x%p\n", m_pszName, pFunc); - return true; -} - -template -void CDetour::EnableDetour() -{ - if (!m_hook) - return; - - int error = funchook_install(m_hook, 0); - - if (!error) - m_bInstalled = true; - else - Warning("funchook_install error for %s: %d %s\n", m_pszName, error, funchook_error_message(m_hook)); -} - -template -void CDetour::DisableDetour() -{ - if (!m_hook) - return; - - int error = funchook_uninstall(m_hook, 0); - - if (!error) - m_bInstalled = false; - else - Warning("funchook_uninstall error for %s: %d %s\n", m_pszName, error, funchook_error_message(m_hook)); -} - -template -void CDetour::FreeDetour() -{ - if (!m_hook) - return; - - if (m_bInstalled) - DisableDetour(); - - int error = funchook_destroy(m_hook); - m_hook = nullptr; - - if (error == 0) - Message("Removed detour %s\n", m_pszName); - else - Warning("funchook_destroy error for %s: %d %s\n", m_pszName, error, funchook_error_message(m_hook)); -} - -#define DECLARE_DETOUR(name, detour) \ - CDetour name(detour, #name) diff --git a/src/cfgparser.cpp b/src/cfgparser.cpp index adfe3adf0..906e18066 100644 --- a/src/cfgparser.cpp +++ b/src/cfgparser.cpp @@ -24,18 +24,19 @@ CCfgParser* g_pCfgParser = nullptr; -CON_COMMAND_F(exec_custom, " - Execute a cfg through the custom cfg parser", FCVAR_SPONLY | FCVAR_LINKED_CONCOMMAND) +void CCfgParser::PreLevelLoad(const char* pszMapName) { - if (args.ArgC() < 2) - { - Message("Usage: exec_custom \n"); - return; - } - - g_pCfgParser->ParseCfg(args[1]); + // Make sure convars are set before map load, currently used by map migrations + ExecuteConfigs(pszMapName); } void CCfgParser::ApplyGameSettings(const char* pszMapName) +{ + // Execute again for good measure, because this is when configs would normally execute + ExecuteConfigs(pszMapName); +} + +void CCfgParser::ExecuteConfigs(const char* pszMapName) { // Run plugin cfg g_pCfgParser->ParseCfg("cs2fixes/cs2fixes"); @@ -47,10 +48,9 @@ void CCfgParser::ApplyGameSettings(const char* pszMapName) return; // Run map cfg (if present) - // We call ParseCfg indirectly through exec_custom, so any commands within the map cfg will be added to the command buffer after nested executes in previous configs - char cmd[MAX_PATH]; - V_snprintf(cmd, sizeof(cmd), "exec_custom cs2fixes/maps/%s", pszMapName); - g_pEngineServer2->ServerCommand(cmd); + char szCfgPath[MAX_PATH]; + V_snprintf(szCfgPath, sizeof(szCfgPath), "cs2fixes/maps/%s", pszMapName); + ParseCfg(szCfgPath); } void CCfgParser::ParseCfg(const char* pszCfgPath) @@ -71,10 +71,42 @@ void CCfgParser::ParseCfg(const char* pszCfgPath) while (std::getline(cfgFile, strCommand)) { - if (!strCommand.empty() && strCommand.back() == '\r') - strCommand.pop_back(); + CCommand args; + + if (!args.Tokenize(strCommand.c_str()) || !args.ArgC()) + continue; + + if (!V_strcasecmp(args[0], "exec_custom")) + { + if (args.ArgC() < 2) + Message("Usage: exec_custom \n"); + else + ParseCfg(args[1]); + + continue; + } + + ConCommandRef command(args[0], true); + ConVarRefAbstract convar(args[0], true); + + if (command.IsValidRef()) + { + CCommandContext context(CT_FIRST_SPLITSCREEN_CLIENT, -1); + command.Dispatch(context, args); + continue; + } + + if (convar.IsValidRef()) + { + // Hold on to ConVarRefAbstract's so they don't get destroyed on next iteration loop, this could cause memory issues with deferred FCVAR_PERFORMING_CALLBACKS cvar's + ConVarRefAbstract& cachedConvar = m_mapConVars.insert_or_assign(convar.GetAccessIndex(), convar).first->second; + + if (args.ArgC() > 1 && !cachedConvar.SetString(args[1])) + Message("Failed to execute \"%s %s\"\n", args[0], args[1]); + + continue; + } - if (!strCommand.empty()) - g_pEngineServer2->ServerCommand(strCommand.c_str()); + Message("Unknown command \"%s\"\n", args[0]); } } diff --git a/src/cfgparser.h b/src/cfgparser.h index 78433b7d7..baee9275d 100644 --- a/src/cfgparser.h +++ b/src/cfgparser.h @@ -19,11 +19,19 @@ #pragma once +#include "convar.h" +#include + class CCfgParser { public: + void PreLevelLoad(const char* pszMapName); void ApplyGameSettings(const char* pszMapName); + void ExecuteConfigs(const char* pszMapName); void ParseCfg(const char* pszCfgPath); + +private: + std::map m_mapConVars; }; extern CCfgParser* g_pCfgParser; diff --git a/src/commands.cpp b/src/commands.cpp index 402198b5b..57df67cbb 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -30,6 +30,7 @@ #include "entity/ccsplayerpawn.h" #include "entity/ccsweaponbase.h" #include "entity/cparticlesystem.h" +#include "entity/customhudlayout.h" #include "entity/lights.h" #include "httpmanager.h" #include "leader.h" @@ -832,6 +833,21 @@ CON_COMMAND_CHAT(vc, "- Display players that are using voice chat") VoiceChatPrintCmd(args, player); } +CConVar g_cvarThirdPersonEnable("cs2f_thirdperson_enable", FCVAR_NONE, "Whether to enable thirdperson", false); + +CON_COMMAND_CHAT(tp, "- Toggle thirdperson") +{ + if (!g_cvarThirdPersonEnable.Get() || !player) + return; + + ZEPlayer* pPlayer = player->GetZEPlayer(); + + if (!pPlayer) + return; + + pPlayer->ToggleThirdPerson(); +} + #if _DEBUG CON_COMMAND_CHAT(myuid, "- Test") { @@ -1161,4 +1177,40 @@ CON_COMMAND_CHAT(discordbot, " - Send a message to a discord webh g_pDiscordBotManager->PostDiscordMessage(args[1], args[2]); } + +CON_COMMAND_CHAT(uitest, " () - Spawn UI panel") +{ + if (args.ArgC() < 2) + { + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Usage: !uitest ()"); + return; + } + + static CHandle hLayout = nullptr; + + if (hLayout.Get()) + hLayout->Remove(); + + hLayout = CCSCustomHudLayout::Create(args[1], "plugin_custom_hud"); + hLayout->SetHasClass("dialog", "Dismissed", false, player); + hLayout->SetInputCaptureEnabled(true, player); + + if (args.ArgC() > 2) + hLayout->SetDialogVariableString("MyLabel", "CustomText", args[2], player); + + hLayout->SetDisconnectCallback([](CCSCustomHudLayout* pHud, int slot) { + CCSCustomHudLayout::DefaultOnDisconnect(pHud, slot); + Message("Custom disconnect callback called\n"); + }); + + hLayout->SetClickCallback([](CCSPlayerController* pController, CCSCustomHudLayout* pHud, std::string sButtonId) { + ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "%s clicked %s on %s\n", pController->GetPlayerName().c_str(), sButtonId.c_str(), pHud->GetName()); + + if (sButtonId == "dismiss_button") + { + pHud->SetHasClass("dialog", "Dismissed", true, pController); + pHud->SetInputCaptureEnabled(false, pController); + } + }); +} #endif // _DEBUG diff --git a/src/cs2_sdk/entity/cbaseentity.h b/src/cs2_sdk/entity/cbaseentity.h index 6d3b3a936..f4685cfd9 100644 --- a/src/cs2_sdk/entity/cbaseentity.h +++ b/src/cs2_sdk/entity/cbaseentity.h @@ -170,7 +170,7 @@ class CBaseEntity : public CEntityInstance void TakeDamage(CTakeDamageInfo& info) { - Detour_CBaseEntity_TakeDamageOld(this, &info, 0); + addresses::CBaseEntity_TakeDamageOld(this, &info, 0); } void Teleport(const Vector* pPosition, const QAngle* pAngles, const Vector* pVelocity) diff --git a/src/cs2_sdk/entity/ccsplayerpawn.h b/src/cs2_sdk/entity/ccsplayerpawn.h index f418c2309..b0b3c71a4 100644 --- a/src/cs2_sdk/entity/ccsplayerpawn.h +++ b/src/cs2_sdk/entity/ccsplayerpawn.h @@ -62,6 +62,7 @@ class CCSPlayerPawn : public CCSPlayerPawnBase SCHEMA_FIELD(float, m_flVelocityModifier) SCHEMA_FIELD(CCSPlayer_ActionTrackingServices*, m_pActionTrackingServices) SCHEMA_FIELD(QAngle, m_angEyeAngles) + SCHEMA_FIELD(bool, m_bLeftHanded) [[nodiscard]] CCSPlayer_CameraServices* GetCameraService() { diff --git a/src/cs2_sdk/entity/ctakedamageinfo.h b/src/cs2_sdk/entity/ctakedamageinfo.h index ebc907b97..644d6d65f 100644 --- a/src/cs2_sdk/entity/ctakedamageinfo.h +++ b/src/cs2_sdk/entity/ctakedamageinfo.h @@ -89,28 +89,35 @@ class CTakeDamageInfo [[maybe_unused]] uint8_t __pad0000[0x8]; public: + DECLARE_SCHEMA_CLASS(CTakeDamageInfo) + CTakeDamageInfo() { - addresses::CTakeDamageInfo_Constructor(this, nullptr, nullptr, nullptr, &vec3_origin, &vec3_origin, 0.f, 0, 0, nullptr); + Construct(); } CTakeDamageInfo(CBaseEntity* pInflictor, CBaseEntity* pAttacker, CBaseEntity* pAbility, float flDamage, DamageTypes_t bitsDamageType) { - addresses::CTakeDamageInfo_Constructor(this, pInflictor, pAttacker, pAbility, &vec3_origin, &vec3_origin, flDamage, bitsDamageType, 0, nullptr); + Construct(); + m_hInflictor = pInflictor; + m_hAttacker = pAttacker; + m_hAbility = pAbility; + m_flDamage = flDamage; + m_bitsDamageType = bitsDamageType; } - Vector m_vecDamageForce; // 0x8 | 8 - Vector m_vecDamagePosition; // 0x14 | 20 - Vector m_vecReportedPosition; // 0x20 | 32 - Vector m_vecDamageDirection; // 0x2c | 44 - CBaseHandle m_hInflictor; // 0x38 | 56 - CBaseHandle m_hAttacker; // 0x3c | 60 - CBaseHandle m_hAbility; // 0x40 | 64 - float m_flDamage; // 0x44 | 68 - float m_flTotalledDamage; // 0x48 | 72 - int32_t m_bitsDamageType; // 0x4c | 76 - int32_t m_iDamageCustom; // 0x50 | 80 - int8_t m_iAmmoType; // 0x54 | 84 + Vector m_vecDamageForce; // 0x8 | 8 + Vector m_vecDamagePosition; // 0x14 | 20 + Vector m_vecReportedPosition; // 0x20 | 32 + Vector m_vecDamageDirection; // 0x2c | 44 + CHandle m_hInflictor; // 0x38 | 56 + CHandle m_hAttacker; // 0x3c | 60 + CHandle m_hAbility; // 0x40 | 64 + float m_flDamage; // 0x44 | 68 + float m_flTotalledDamage; // 0x48 | 72 + int32_t m_bitsDamageType; // 0x4c | 76 + int32_t m_iDamageCustom; // 0x50 | 80 + int8_t m_iAmmoType; // 0x54 | 84 private: [[maybe_unused]] uint8_t m_nUnknown0[0xb]; // 0x55 | 85 diff --git a/src/cs2_sdk/entity/customhudlayout.cpp b/src/cs2_sdk/entity/customhudlayout.cpp new file mode 100644 index 000000000..72f25a873 --- /dev/null +++ b/src/cs2_sdk/entity/customhudlayout.cpp @@ -0,0 +1,220 @@ +/** + * ============================================================================= + * CS2Fixes + * Copyright (C) 2023-2026 Source2ZE + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "customhudlayout.h" +#include "entity.h" + +std::unordered_map CCSCustomHudLayout::sm_mapCustomLayoutCallbacks; + +CCSCustomHudLayout* CCSCustomHudLayout::Create(std::string sLayout, std::string sTargetName) +{ + CCSCustomHudLayout* pLayout = CreateEntityByName("custom_hud_layout"); + + if (!pLayout) + return nullptr; + + CEntityKeyValues* pKeyValues = new CEntityKeyValues(); + std::string sLayoutPath = "panorama/layout/custom_game/" + std::string(sLayout) + ".vxml_c"; + pKeyValues->SetString("layout", sLayoutPath.c_str()); + + if (!sTargetName.empty()) + pKeyValues->SetString("targetname", sTargetName.c_str()); + + pLayout->DispatchSpawn(pKeyValues); + pLayout->SetDisconnectCallback(&DefaultOnDisconnect); + + return pLayout; +} + +void CCSCustomHudLayout::ClearCallbacks() +{ + sm_mapCustomLayoutCallbacks.clear(); +} + +void CCSCustomHudLayout::OnClick(CCSPlayerController* pController, const std::string& sButtonId) +{ + if (auto it = sm_mapCustomLayoutCallbacks.find(GetHandle().ToInt()); it != sm_mapCustomLayoutCallbacks.end()) + it->second.m_OnClick(pController, this, sButtonId); +} + +void CCSCustomHudLayout::OnClientDisconnect(int slot) +{ + auto iterator = sm_mapCustomLayoutCallbacks.begin(); + + while (iterator != sm_mapCustomLayoutCallbacks.end()) + { + auto pLayout = CHandle(iterator->first).Get(); + + if (!pLayout) + { + iterator = sm_mapCustomLayoutCallbacks.erase(iterator); + continue; + } + + iterator->second.m_OnDisconnect(pLayout, slot); + iterator++; + } +} + +void CCSCustomHudLayout::DefaultOnDisconnect(CCSCustomHudLayout* pLayout, int slot) +{ + pLayout->ClearClasses(slot); + pLayout->ClearDialogVariables(slot); + pLayout->SetInputCaptureEnabled(false, slot); +} + +void CCSCustomHudLayout::OnEntityDeleted() +{ + sm_mapCustomLayoutCallbacks.erase(GetHandle().ToInt()); +} + +CCSCustomHudLayoutState& CCSCustomHudLayout::GetLayoutState(int nSlot) +{ + if (nSlot < 0 || nSlot >= 64) + return *m_globalLayoutState; + + return *(CCSCustomHudLayoutState*)m_vecPlayerLayoutStates.GetManipulator()(SCHEMA_COLLECTION_MANIPULATOR_ACTION_GET_ELEMENT, m_vecPlayerLayoutStates, nSlot, 0); +} + +CCSCustomHudLayoutState& CCSCustomHudLayout::GetLayoutState(CCSPlayerController* pController) +{ + return pController ? GetLayoutState(pController->GetPlayerSlot()) : *m_globalLayoutState; +} + +void CCSCustomHudLayout::SetHasClass(std::string sPanelId, std::string sClassName, bool bHasClass, int nSlot) +{ + auto panelIndex = m_vecPanelIds->Find(sPanelId.c_str()); + + if (panelIndex == -1) + panelIndex = m_vecPanelIds->AddToTail(sPanelId.c_str()); + + auto classIndex = m_vecClassNames->Find(sClassName.c_str()); + + if (classIndex == -1) + classIndex = m_vecClassNames->AddToTail(sClassName.c_str()); + + auto& layoutState = GetLayoutState(nSlot); + + HUDPanelHasClass_t hasClass(panelIndex, classIndex, bHasClass); + + auto hasClassIndex = layoutState.m_vecHasClasses->Find(hasClass); + + if (hasClassIndex == -1) + layoutState.m_vecHasClasses->AddToTail(hasClass); + else + layoutState.m_vecHasClasses->Element(hasClassIndex).m_eClassStatus = hasClass.m_eClassStatus; +} + +void CCSCustomHudLayout::SetHasClass(std::string sPanelId, std::string sClassName, bool bHasClass, CCSPlayerController* pController) +{ + SetHasClass(sPanelId, sClassName, bHasClass, pController ? pController->GetPlayerSlot() : -1); +} + +void CCSCustomHudLayout::SetDialogVariableString(std::string sPanelId, std::string sVariableName, std::string sValue, int nSlot) +{ + auto panelIndex = m_vecPanelIds->Find(sPanelId.c_str()); + + if (panelIndex == -1) + panelIndex = m_vecPanelIds->AddToTail(sPanelId.c_str()); + + auto variableIndex = m_vecDialogVariableNames->Find(sVariableName.c_str()); + + if (variableIndex == -1) + variableIndex = m_vecDialogVariableNames->AddToTail(sVariableName.c_str()); + + auto& layoutState = GetLayoutState(nSlot); + + HUDPanelDialogVariableString_t dialogVariable(panelIndex, variableIndex, sValue.c_str(), true); + + auto dialogVariableIndex = layoutState.m_vecDialogVariableStrings->Find(dialogVariable); + + if (dialogVariableIndex == -1) + { + layoutState.m_vecDialogVariableStrings->AddToTail(dialogVariable); + } + else + { + layoutState.m_vecDialogVariableStrings->Element(dialogVariableIndex).m_sValue = sValue.c_str(); + layoutState.m_vecDialogVariableStrings->Element(dialogVariableIndex).m_bIsSet = true; + } +} + +void CCSCustomHudLayout::SetDialogVariableString(std::string sPanelId, std::string sVariableName, std::string sValue, CCSPlayerController* pController) +{ + SetDialogVariableString(sPanelId, sVariableName, sValue, pController ? pController->GetPlayerSlot() : -1); +} + +void CCSCustomHudLayout::ClearClasses(int nSlot) +{ + auto& layoutState = GetLayoutState(nSlot); + + for (int i = 0; i < layoutState.m_vecHasClasses->Count(); i++) + { + if (layoutState.m_vecHasClasses->Element(i).m_eClassStatus == k_eHudPanelClassStatus_Undefined) + continue; + + auto pClass = (HUDPanelHasClass_t*)layoutState.m_vecHasClasses.GetManipulator()(SCHEMA_COLLECTION_MANIPULATOR_ACTION_GET_ELEMENT, layoutState.m_vecHasClasses, i, 0); + pClass->m_eClassStatus = k_eHudPanelClassStatus_Undefined; + } +} + +void CCSCustomHudLayout::ClearDialogVariables(int nSlot) +{ + auto& layoutState = GetLayoutState(nSlot); + + for (int i = 0; i < layoutState.m_vecDialogVariableStrings->Count(); i++) + { + if (!layoutState.m_vecDialogVariableStrings->Element(i).m_bIsSet) + continue; + + auto pString = (HUDPanelDialogVariableString_t*)layoutState.m_vecDialogVariableStrings.GetManipulator()(SCHEMA_COLLECTION_MANIPULATOR_ACTION_GET_ELEMENT, layoutState.m_vecDialogVariableStrings, i, 0); + pString->m_bIsSet = false; + pString->m_sValue = ""; + } +} + +void CCSCustomHudLayout::SetInputCaptureEnabled(bool bEnable, int nSlot) +{ + GetLayoutState(nSlot).m_bInputCaptureEnabled = bEnable; +} + +void CCSCustomHudLayout::SetInputCaptureEnabled(bool bEnable, CCSPlayerController* pController) +{ + SetInputCaptureEnabled(bEnable, pController ? pController->GetPlayerSlot() : -1); +} + +bool CCSCustomHudLayout::IsInputCaptureEnabled(int nSlot) +{ + return GetLayoutState(nSlot).m_bInputCaptureEnabled; +} + +bool CCSCustomHudLayout::IsInputCaptureEnabled(CCSPlayerController* pController) +{ + return GetLayoutState(pController).m_bInputCaptureEnabled; +} + +void CCSCustomHudLayout::SetClickCallback(CustomHudClickCallback_t callback) +{ + sm_mapCustomLayoutCallbacks[GetHandle().ToInt()].m_OnClick = callback; +} + +void CCSCustomHudLayout::SetDisconnectCallback(CustomHudDisconnectCallback_t callback) +{ + sm_mapCustomLayoutCallbacks[GetHandle().ToInt()].m_OnDisconnect = callback; +} diff --git a/src/cs2_sdk/entity/customhudlayout.h b/src/cs2_sdk/entity/customhudlayout.h new file mode 100644 index 000000000..1df3374d5 --- /dev/null +++ b/src/cs2_sdk/entity/customhudlayout.h @@ -0,0 +1,148 @@ +/** + * ============================================================================= + * CS2Fixes + * Copyright (C) 2023-2026 Source2ZE + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#include "ccsplayercontroller.h" +#include "cstrike15_usermessages.pb.h" + +class CCSCustomHudLayout; + +using CustomHudClickCallback_t = std::function; +using CustomHudDisconnectCallback_t = std::function; + +struct CustomHudLayoutCallbacks_t +{ + CustomHudClickCallback_t m_OnClick = nullptr; + CustomHudDisconnectCallback_t m_OnDisconnect = nullptr; +}; + +enum EHudPanelClassStatus_t : int +{ + k_eHudPanelClassStatus_Undefined = -1, + k_eHudPanelClassStatus_DoesNotHaveClass = 0, + k_eHudPanelClassStatus_HasClass = 1, +}; + +struct HUDPanelDialogVariableString_t +{ +private: + virtual void unk00() {} + +public: + HUDPanelDialogVariableString_t(uint16 nPanelIdIndex, uint16 nDialogVariableIndex, CUtlString sValue, bool bIsSet) : + m_nPanelIdIndex(nPanelIdIndex), m_nDialogVariableIndex(nDialogVariableIndex), m_sValue(sValue), m_bIsSet(bIsSet) + { + // Since we're constructing a new object, the vtable pointer will be incorrect so fix it + static const auto pVTable = modules::server->FindVirtualTable("HUDPanelDialogVariableString_t"); + ((void**)this)[0] = pVTable; + } + + bool operator==(const HUDPanelDialogVariableString_t& other) const + { + return m_nPanelIdIndex == other.m_nPanelIdIndex && m_nDialogVariableIndex == other.m_nDialogVariableIndex; + } + + uint16 m_nPanelIdIndex; + uint16 m_nDialogVariableIndex; + CUtlString m_sValue; + bool m_bIsSet; +}; + +struct HUDPanelHasClass_t +{ +public: + HUDPanelHasClass_t(uint16 nPanelIdIndex, uint16 nClassNameIndex, bool bHasClass) : + m_nPanelIdIndex(nPanelIdIndex), m_nClassNameIndex(nClassNameIndex), m_eClassStatus((EHudPanelClassStatus_t)bHasClass) + { + } + + bool operator==(const HUDPanelHasClass_t& other) const + { + return m_nPanelIdIndex == other.m_nPanelIdIndex && m_nClassNameIndex == other.m_nClassNameIndex; + } + + uint16 m_nPanelIdIndex; + uint16 m_nClassNameIndex; + EHudPanelClassStatus_t m_eClassStatus; +}; + +class CCSCustomHudLayoutState +{ +public: + DECLARE_SCHEMA_CLASS_INLINE(CCSCustomHudLayoutState) + + SCHEMA_FIELD(int, m_playerSlot) + SCHEMA_FIELD(bool, m_bInputCaptureEnabled) + SCHEMA_FIELD_POINTER(CUtlVector, m_vecHasClasses) + SCHEMA_FIELD_POINTER(CUtlVector, m_vecDialogVariableStrings) +}; + +class CCSCustomHudLayout : public CBaseEntity +{ +public: + DECLARE_SCHEMA_CLASS(CCSCustomHudLayout) + + SCHEMA_FIELD(CUtlSymbolLarge, m_strLayout); + + // Is actually a CUtlVectorEmbeddedNetworkVar with a different structure from a normal CUtlVector, get elements using the manipulator function + SCHEMA_FIELD_POINTER(CUtlVector, m_vecPlayerLayoutStates); + + SCHEMA_FIELD_POINTER(CCSCustomHudLayoutState, m_globalLayoutState); + SCHEMA_FIELD_POINTER(CUtlVector, m_vecPanelIds); + SCHEMA_FIELD_POINTER(CUtlVector, m_vecClassNames); + SCHEMA_FIELD_POINTER(CUtlVector, m_vecDialogVariableNames); + + static CCSCustomHudLayout* Create(std::string sLayout, std::string sTargetName = ""); + static void ClearCallbacks(); + + CCSCustomHudLayoutState& GetLayoutState(int nSlot); + CCSCustomHudLayoutState& GetLayoutState(CCSPlayerController* pController = nullptr); + + void SetHasClass(std::string sPanelId, std::string sClassName, bool bHasClass, int nSlot); + void SetHasClass(std::string sPanelId, std::string sClassName, bool bHasClass, CCSPlayerController* pController = nullptr); + + void SetDialogVariableString(std::string sPanelId, std::string sVariableName, std::string sValue, int nSlot); + void SetDialogVariableString(std::string sPanelId, std::string sVariableName, std::string sValue, CCSPlayerController* pController = nullptr); + + // NOTE: These will not apply any visible changes to ingame clients, despite them seeing empty vectors. + // They are only applicable for cleanup after clients disconnect. + void ClearClasses(int nSlot = -1); + void ClearDialogVariables(int nSlot = -1); + + void SetInputCaptureEnabled(bool bEnable, int nSlot); + void SetInputCaptureEnabled(bool bEnable, CCSPlayerController* pController); + + bool IsInputCaptureEnabled(int nSlot); + bool IsInputCaptureEnabled(CCSPlayerController* pController = nullptr); + + void SetClickCallback(CustomHudClickCallback_t callback); + void OnClick(CCSPlayerController* pController, const std::string& sButtonId); + + // All layouts spawned with Create will by default have DefaultOnDisconnect as their disconnect callback + // which clears all classes and variables for a slot, so only use this if you want custom logic + void SetDisconnectCallback(CustomHudDisconnectCallback_t callback); + static void OnClientDisconnect(int nSlot); + static void DefaultOnDisconnect(CCSCustomHudLayout* pLayout, int nSlot); + + void OnEntityDeleted(); + +private: + static std::unordered_map sm_mapCustomLayoutCallbacks; +}; \ No newline at end of file diff --git a/src/cs2_sdk/entity/customplayercamera.h b/src/cs2_sdk/entity/customplayercamera.h new file mode 100644 index 000000000..c8d3f97d3 --- /dev/null +++ b/src/cs2_sdk/entity/customplayercamera.h @@ -0,0 +1,45 @@ +/** + * ============================================================================= + * CS2Fixes + * Copyright (C) 2023-2026 Source2ZE + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#include "entity/ccsplayerpawn.h" + +enum CustomCameraMode_t : uint8_t +{ + CUSTOM_CAMERA_MODE_DISABLED = 0, + CUSTOM_CAMERA_MODE_CONTROLLED = 1, + CUSTOM_CAMERA_MODE_CONTROLLED_POSITION = 2, + CUSTOM_CAMERA_MODE_FOLLOW_POSITION = 3, +}; + +class CCSCustomPlayerCamera : public CBaseEntity +{ +public: + DECLARE_SCHEMA_CLASS(CCSCustomPlayerCamera) + + SCHEMA_FIELD(CHandle, m_hPawn) + SCHEMA_FIELD(CustomCameraMode_t, m_nCameraMode) + SCHEMA_FIELD(CHandle, m_hFollowEntity) + SCHEMA_FIELD(bool, m_bFollowEyes) + SCHEMA_FIELD(Vector, m_vecFollowOffset) + SCHEMA_FIELD(Vector, m_vecCameraOffset) + SCHEMA_FIELD(bool, m_bClipCameraOffset) + SCHEMA_FIELD(float32, m_flCameraOffsetReturnStrength) +}; \ No newline at end of file diff --git a/src/cs2_sdk/map.h b/src/cs2_sdk/map.h new file mode 100644 index 000000000..74bbe3e8d --- /dev/null +++ b/src/cs2_sdk/map.h @@ -0,0 +1,40 @@ +/** + * ============================================================================= + * CS2Fixes + * Copyright (C) 2023-2026 Source2ZE + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#include "utlstring.h" + +class CSingleWorldRep +{ +private: + void* vtable; + +public: + CUtlString m_name; + char pad[0x20]; + void* m_pCWorld; +}; + +struct LumpData +{ + CUtlString m_name; + char pad[0x18]; + CKV3Arena* m_allocatorContext; +}; diff --git a/src/cs2_sdk/schema.cpp b/src/cs2_sdk/schema.cpp index cdbfdab5c..bab07e43f 100644 --- a/src/cs2_sdk/schema.cpp +++ b/src/cs2_sdk/schema.cpp @@ -96,6 +96,9 @@ static void InitSchemaKeyValueMap(SchemaClassInfoData_t* pClassInfo, SchemaKeyVa keyValuePair.second.offset = field.m_nSingleInheritanceOffset; keyValuePair.second.networked = IsFieldNetworked(pClassInfo->m_pszName, field); + if (field.m_pType->m_eTypeCategory == SCHEMA_TYPE_ATOMIC && field.m_pType->m_eAtomicCategory == SCHEMA_ATOMIC_COLLECTION_OF_T) + keyValuePair.second.manipulator = static_cast(field.m_pType)->m_pfnManipulator; + keyValueMap.insert(keyValuePair); } @@ -106,12 +109,7 @@ static void InitSchemaKeyValueMap(SchemaClassInfoData_t* pClassInfo, SchemaKeyVa static bool InitSchemaFieldsForClass(SchemaTableMap_t& tableMap, const char* className, uint32_t classKey) { - CSchemaSystemTypeScope* pType = g_pSchemaSystem->FindTypeScopeForModule(MODULE_PREFIX "server" MODULE_EXT); - - if (!pType) - return false; - - SchemaClassInfoData_t* pClassInfo = pType->FindDeclaredClass(className).Get(); + SchemaClassInfoData_t* pClassInfo = schema::GetSchemaClassInfo(className); if (!pClassInfo) { @@ -159,6 +157,16 @@ SchemaKey schema::GetOffset(const char* className, uint32_t classKey, const char return tableMap[memberKey]; } +SchemaClassInfoData_t* schema::GetSchemaClassInfo(const char* className) +{ + static CSchemaSystemTypeScope* pType = g_pSchemaSystem->FindTypeScopeForModule(MODULE_PREFIX "server" MODULE_EXT); + + if (!pType) + return nullptr; + + return pType->FindDeclaredClass(className).Get(); +} + void NetworkVarStateChanged(uintptr_t pNetworkVar, uint32_t nOffset, uint32 nNetworkStateChangedOffset) { NetworkStateChangedData data(nOffset); diff --git a/src/cs2_sdk/schema.h b/src/cs2_sdk/schema.h index 5a4f1ddb1..a25035a7c 100644 --- a/src/cs2_sdk/schema.h +++ b/src/cs2_sdk/schema.h @@ -41,6 +41,7 @@ struct SchemaKey { int32 offset; bool networked; + SchemaCollectionManipulatorFn_t manipulator = nullptr; }; class CNetworkVarChainer @@ -66,6 +67,7 @@ namespace schema { int16_t FindChainOffset(const char* className, uint32_t classNameHash); SchemaKey GetOffset(const char* className, uint32_t classKey, const char* memberName, uint32_t memberKey); + SchemaClassInfoData_t* GetSchemaClassInfo(const char* className); } // namespace schema constexpr uint32_t val_32_const = 0x811c9dc5; @@ -193,6 +195,11 @@ inline constexpr uint64_t hash_64_fnv1a_const(const char* const str, const uint6 ::NetworkVarStateChanged(pThisClass, m_key.offset + extra_offset, m_networkStateChangedOffset); \ } \ } \ + SchemaCollectionManipulatorFn_t GetManipulator() \ + { \ + static const auto m_key = schema::GetOffset(m_className, m_classNameHash, #varName, m_varNameHash); \ + return m_key.manipulator; \ + } \ operator type*() \ { \ return Get(); \ @@ -221,14 +228,27 @@ inline constexpr uint64_t hash_64_fnv1a_const(const char* const str, const uint6 SCHEMA_FIELD_POINTER_OFFSET(type, varName, 0) // If the class needs a specific offset for its NetworkStateChanged (like CEconItemView), use this and provide the offset -#define DECLARE_SCHEMA_CLASS_BASE(ClassName, offset) \ -private: \ - typedef ClassName ThisClass; \ - static constexpr const char* m_className = #ClassName; \ - static constexpr uint32_t m_classNameHash = hash_32_fnv1a_const(#ClassName); \ - static constexpr int m_networkStateChangedOffset = offset; \ - \ -public: +#define DECLARE_SCHEMA_CLASS_BASE(ClassName, offset) \ +private: \ + typedef ClassName ThisClass; \ + static constexpr const char* m_className = #ClassName; \ + static constexpr uint32_t m_classNameHash = hash_32_fnv1a_const(#ClassName); \ + static constexpr int m_networkStateChangedOffset = offset; \ + \ +public: \ + static ThisClass* Create() \ + { \ + static auto pClassInfo = schema::GetSchemaClassInfo(m_className); \ + static auto pfnManipulator = pClassInfo->m_pfnManipulator; \ + ThisClass* pThis = (ThisClass*)pfnManipulator(SCHEMA_CLASS_MANIPULATOR_ACTION_ALLOCATE, nullptr); \ + return pThis; \ + } \ + void Construct() \ + { \ + static auto pClassInfo = schema::GetSchemaClassInfo(m_className); \ + static auto pfnManipulator = pClassInfo->m_pfnManipulator; \ + pfnManipulator(SCHEMA_CLASS_MANIPULATOR_ACTION_CONSTRUCT_IN_PLACE, this); \ + } #define DECLARE_SCHEMA_CLASS(className) DECLARE_SCHEMA_CLASS_BASE(className, 0) diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp index bd603b2dc..8e3290d2b 100644 --- a/src/cs2fixes.cpp +++ b/src/cs2fixes.cpp @@ -22,84 +22,39 @@ #include "adminsystem.h" #include "appframework/IAppSystem.h" -#include "cchecktransmitinfo.h" #include "cfgparser.h" #include "commands.h" #include "common.h" -#include "cs_gameevents.pb.h" #include "ctimer.h" #include "cvarwhitelist.h" -#include "detours.h" #include "discord.h" #include "entities.h" -#include "entity/ccsplayercontroller.h" +#include "entity/customhudlayout.h" #include "entity/services.h" #include "entitylistener.h" #include "entitysystem.h" #include "entwatch.h" #include "eventlistener.h" #include "gameconfig.h" -#include "gameevents.pb.h" #include "gamesystem.h" -#include "httpmanager.h" #include "hud_manager.h" #include "icvar.h" #include "idlemanager.h" -#include "interface.h" -#include "leader.h" +#include "khook_helpers.h" #include "map_votes.h" #include "mapmigrations.h" #include "networkstringtabledefs.h" #include "panoramavote.h" #include "patches.h" -#include "plat.h" #include "playermanager.h" #include "schemasystem/schemasystem.h" -#include "serversideclient.h" -#include "te.pb.h" -#include "tier0/dbg.h" -#include "tier0/vprof.h" #include "user_preferences.h" -#include "usermessages.pb.h" +#include "virtual_hooks.h" #include "votemanager.h" #include "zombiereborn.h" -#include #include "tier0/memdbgon.h" -class GameSessionConfiguration_t -{}; - -SH_DECL_HOOK3_void(IServerGameDLL, GameFrame, SH_NOATTRIB, 0, bool, bool, bool); -SH_DECL_HOOK0_void(IServerGameDLL, GameServerSteamAPIActivated, SH_NOATTRIB, 0); -SH_DECL_HOOK1_void(IServerGameDLL, ApplyGameSettings, SH_NOATTRIB, 0, KeyValues*); -SH_DECL_HOOK4_void(IServerGameClients, ClientActive, SH_NOATTRIB, 0, CPlayerSlot, bool, const char*, uint64); -SH_DECL_HOOK5_void(IServerGameClients, ClientDisconnect, SH_NOATTRIB, 0, CPlayerSlot, ENetworkDisconnectionReason, const char*, uint64, const char*); -SH_DECL_HOOK4_void(IServerGameClients, ClientPutInServer, SH_NOATTRIB, 0, CPlayerSlot, char const*, int, uint64); -SH_DECL_HOOK1_void(IServerGameClients, ClientSettingsChanged, SH_NOATTRIB, 0, CPlayerSlot); -SH_DECL_HOOK6_void(IServerGameClients, OnClientConnected, SH_NOATTRIB, 0, CPlayerSlot, const char*, uint64, const char*, const char*, bool); -SH_DECL_HOOK6(IServerGameClients, ClientConnect, SH_NOATTRIB, 0, bool, CPlayerSlot, const char*, uint64, const char*, bool, CBufferString*); -SH_DECL_HOOK8_void(IGameEventSystem, PostEventAbstract, SH_NOATTRIB, 0, CSplitScreenSlot, bool, int, const uint64*, - INetworkMessageInternal*, const CNetMessage*, unsigned long, NetChannelBufType_t) - SH_DECL_HOOK3_void(INetworkServerService, StartupServer, SH_NOATTRIB, 0, const GameSessionConfiguration_t&, ISource2WorldSession*, const char*); -SH_DECL_HOOK7_void(ISource2GameEntities, CheckTransmit, SH_NOATTRIB, 0, CCheckTransmitInfo**, int, CBitVec<16384>&, CBitVec<16384>&, const Entity2Networkable_t**, const uint16*, int); -SH_DECL_HOOK2_void(IServerGameClients, ClientCommand, SH_NOATTRIB, 0, CPlayerSlot, const CCommand&); -SH_DECL_HOOK3_void(ICvar, DispatchConCommand, SH_NOATTRIB, 0, ConCommandRef, const CCommandContext&, const CCommand&); -SH_DECL_MANUALHOOK1_void(CGamePlayerEquipUse, 0, 0, 0, InputData_t*); -SH_DECL_MANUALHOOK1_void(CGamePlayerEquipPrecache, 0, 0, 0, CEntityPrecacheContext*); -SH_DECL_MANUALHOOK1_void(CTriggerGravityPrecache, 0, 0, 0, CEntityPrecacheContext*); -SH_DECL_MANUALHOOK1_void(CTriggerGravityEndTouch, 0, 0, 0, CBaseEntity*); -SH_DECL_MANUALHOOK2_void(CreateWorkshopMapGroup, 0, 0, 0, const char*, const CUtlStringList&); -SH_DECL_MANUALHOOK1(OnTakeDamage_Alive, 0, 0, 0, bool, CTakeDamageResult*); -SH_DECL_MANUALHOOK1_void(CheckMovingGround, 0, 0, 0, double); -SH_DECL_HOOK2(IGameEventManager2, LoadEventsFromFile, SH_NOATTRIB, 0, int, const char*, bool); -SH_DECL_MANUALHOOK2_void(PhysicsTouchShuffle, 0, 0, 0, CUtlVector*, bool); -SH_DECL_MANUALHOOK3_void(DropWeapon, 0, 0, 0, CBasePlayerWeapon*, Vector*, Vector*); -SH_DECL_HOOK1_void(IServer, SetGameSpawnGroupMgr, SH_NOATTRIB, 0, IGameSpawnGroupMgr*); -SH_DECL_HOOK2_void(CEntitySystem, Spawn, SH_NOATTRIB, 0, int, const EntitySpawnInfo_t*); -SH_DECL_MANUALHOOK3_void(Teleport, 0, 0, 0, const Vector*, const QAngle*, const Vector*); -SH_DECL_HOOK1(CServerSideClient, ProcessVoiceData, SH_NOATTRIB, 0, bool, const CCLCMsg_VoiceData_t&); - CS2Fixes g_CS2Fixes; IGameEventSystem* g_gameEventSystem = nullptr; IGameEventManager2* g_gameEventManager = nullptr; @@ -108,25 +63,10 @@ IVEngineServer2* g_pEngineServer2 = nullptr; CCSGameRules* g_pGameRules = nullptr; // Will be null between map end & new map startup, null check if necessary! CSpawnGroupMgrGameSystem* g_pSpawnGroupMgr = nullptr; // Will be null between map end & new map startup, null check if necessary! -int g_iCGamePlayerEquipUseId = -1; -int g_iCGamePlayerEquipPrecacheId = -1; -int g_iCTriggerGravityPrecacheId = -1; -int g_iCTriggerGravityEndTouchId = -1; -int g_iCreateWorkshopMapGroupId = -1; -int g_iOnTakeDamageAliveId = -1; -int g_iCheckMovingGroundId = -1; -int g_iLoadEventsFromFileId = -1; -int g_iPhysicsTouchShuffle = -1; -int g_iWeaponServiceDropWeaponId = -1; -int g_iSetGameSpawnGroupMgrId = -1; -int g_iSpawnId = -1; -int g_iTeleportPreId = -1; -int g_iTeleportPostId = -1; -int g_iProcessVoiceDataId = -1; - double g_flUniversalTime = 0.0; float g_flLastTickedTime = 0.0f; bool g_bHasTicked = false; +bool g_bRequiredInitLoaded = true; CGameEntitySystem* GameEntitySystem() { @@ -160,6 +100,7 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool GET_V_IFACE_ANY(GetServerFactory, g_pSource2GameEntities, ISource2GameEntities, SOURCE2GAMEENTITIES_INTERFACE_VERSION); GET_V_IFACE_ANY(GetServerFactory, g_pSource2GameClients, IServerGameClients, SOURCE2GAMECLIENTS_INTERFACE_VERSION); GET_V_IFACE_ANY(GetEngineFactory, g_pNetworkServerService, INetworkServerService, NETWORKSERVERSERVICE_INTERFACE_VERSION); + GET_V_IFACE_ANY(GetEngineFactory, g_pEngineServiceMgr, IEngineServiceMgr, ENGINESERVICEMGR_INTERFACE_VERSION); GET_V_IFACE_ANY(GetEngineFactory, g_gameEventSystem, IGameEventSystem, GAMEEVENTSYSTEM_INTERFACE_VERSION); GET_V_IFACE_ANY(GetEngineFactory, g_pNetworkMessages, INetworkMessages, NETWORKMESSAGES_INTERFACE_VERSION); GET_V_IFACE_ANY(GetEngineFactory, g_pGameTypes, IGameTypes, GAMETYPES_INTERFACE_VERSION); @@ -181,161 +122,22 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool return false; } - int offset = g_GameConfig->GetOffset("IGameTypes_CreateWorkshopMapGroup"); - SH_MANUALHOOK_RECONFIGURE(CreateWorkshopMapGroup, offset, 0, 0); - - SH_ADD_HOOK(IServerGameDLL, GameFrame, g_pSource2Server, SH_MEMBER(this, &CS2Fixes::Hook_GameFramePost), true); - SH_ADD_HOOK(IServerGameDLL, GameServerSteamAPIActivated, g_pSource2Server, SH_MEMBER(this, &CS2Fixes::Hook_GameServerSteamAPIActivated), false); - SH_ADD_HOOK(IServerGameDLL, ApplyGameSettings, g_pSource2Server, SH_MEMBER(this, &CS2Fixes::Hook_ApplyGameSettings), false); - SH_ADD_HOOK(IServerGameClients, ClientActive, g_pSource2GameClients, SH_MEMBER(this, &CS2Fixes::Hook_ClientActive), true); - SH_ADD_HOOK(IServerGameClients, ClientDisconnect, g_pSource2GameClients, SH_MEMBER(this, &CS2Fixes::Hook_ClientDisconnect), true); - SH_ADD_HOOK(IServerGameClients, ClientPutInServer, g_pSource2GameClients, SH_MEMBER(this, &CS2Fixes::Hook_ClientPutInServer), true); - SH_ADD_HOOK(IServerGameClients, ClientSettingsChanged, g_pSource2GameClients, SH_MEMBER(this, &CS2Fixes::Hook_ClientSettingsChanged), false); - SH_ADD_HOOK(IServerGameClients, OnClientConnected, g_pSource2GameClients, SH_MEMBER(this, &CS2Fixes::Hook_OnClientConnected), false); - SH_ADD_HOOK(IServerGameClients, ClientConnect, g_pSource2GameClients, SH_MEMBER(this, &CS2Fixes::Hook_ClientConnect), false); - SH_ADD_HOOK(IServerGameClients, ClientCommand, g_pSource2GameClients, SH_MEMBER(this, &CS2Fixes::Hook_ClientCommand), false); - SH_ADD_HOOK(IGameEventSystem, PostEventAbstract, g_gameEventSystem, SH_MEMBER(this, &CS2Fixes::Hook_PostEvent), false); - SH_ADD_HOOK(INetworkServerService, StartupServer, g_pNetworkServerService, SH_MEMBER(this, &CS2Fixes::Hook_StartupServer), true); - SH_ADD_HOOK(ISource2GameEntities, CheckTransmit, g_pSource2GameEntities, SH_MEMBER(this, &CS2Fixes::Hook_CheckTransmit), true); - SH_ADD_HOOK(ICvar, DispatchConCommand, g_pCVar, SH_MEMBER(this, &CS2Fixes::Hook_DispatchConCommand), false); - g_iCreateWorkshopMapGroupId = SH_ADD_MANUALVPHOOK(CreateWorkshopMapGroup, g_pGameTypes, SH_MEMBER(this, &CS2Fixes::Hook_CreateWorkshopMapGroup), false); - - META_CONPRINTF("All hooks started!\n"); - - bool bRequiredInitLoaded = true; - - if (!addresses::Initialize(g_GameConfig)) - bRequiredInitLoaded = false; + if (!addresses::Initialize()) + g_bRequiredInitLoaded = false; if (!addresses::InitializeVScriptFunctions()) - bRequiredInitLoaded = false; + g_bRequiredInitLoaded = false; - if (!InitPatches(g_GameConfig)) - bRequiredInitLoaded = false; + InitVirtualHooks(); + InitKHooks(); - if (!InitDetours(g_GameConfig)) - bRequiredInitLoaded = false; + if (!InitPatches()) + g_bRequiredInitLoaded = false; if (!InitGameSystems()) - bRequiredInitLoaded = false; - - const auto pCGamePlayerEquipVTable = modules::server->FindVirtualTable("CGamePlayerEquip"); - if (!pCGamePlayerEquipVTable) - { - snprintf(error, maxlen, "Failed to find CGamePlayerEquip vtable\n"); - bRequiredInitLoaded = false; - } - - offset = g_GameConfig->GetOffset("CBaseEntity::Use"); - if (offset == -1) - { - snprintf(error, maxlen, "Failed to find CBaseEntity::Use\n"); - bRequiredInitLoaded = false; - } - SH_MANUALHOOK_RECONFIGURE(CGamePlayerEquipUse, offset, 0, 0); - g_iCGamePlayerEquipUseId = SH_ADD_MANUALDVPHOOK(CGamePlayerEquipUse, pCGamePlayerEquipVTable, SH_MEMBER(this, &CS2Fixes::Hook_CGamePlayerEquipUse), false); - - offset = g_GameConfig->GetOffset("CBaseEntity::Precache"); - if (offset == -1) - { - snprintf(error, maxlen, "Failed to find CBaseEntity::Precache\n"); - bRequiredInitLoaded = false; - } - SH_MANUALHOOK_RECONFIGURE(CGamePlayerEquipPrecache, offset, 0, 0); - g_iCGamePlayerEquipPrecacheId = SH_ADD_MANUALDVPHOOK(CGamePlayerEquipPrecache, pCGamePlayerEquipVTable, SH_MEMBER(this, &CS2Fixes::Hook_CGamePlayerEquipPrecache), true); - - const auto pTriggerGravityVTable = modules::server->FindVirtualTable("CTriggerGravity"); - if (!pTriggerGravityVTable) - { - snprintf(error, maxlen, "Failed to find TriggerGravity vtable\n"); - bRequiredInitLoaded = false; - } + g_bRequiredInitLoaded = false; - offset = g_GameConfig->GetOffset("CBaseEntity::Precache"); - if (offset == -1) - { - snprintf(error, maxlen, "Failed to find CBaseEntity::Precache\n"); - bRequiredInitLoaded = false; - } - SH_MANUALHOOK_RECONFIGURE(CTriggerGravityPrecache, offset, 0, 0); - g_iCTriggerGravityPrecacheId = SH_ADD_MANUALDVPHOOK(CTriggerGravityPrecache, pTriggerGravityVTable, SH_MEMBER(this, &CS2Fixes::Hook_CTriggerGravityPrecache), true); - - offset = g_GameConfig->GetOffset("CBaseEntity::EndTouch"); - if (offset == -1) - { - snprintf(error, maxlen, "Failed to find CBaseEntity::EndTouch\n"); - bRequiredInitLoaded = false; - } - SH_MANUALHOOK_RECONFIGURE(CTriggerGravityEndTouch, offset, 0, 0); - g_iCTriggerGravityEndTouchId = SH_ADD_MANUALDVPHOOK(CTriggerGravityEndTouch, pTriggerGravityVTable, SH_MEMBER(this, &CS2Fixes::Hook_CTriggerGravityEndTouch), true); - - const auto pCCSPlayerPawnVTable = modules::server->FindVirtualTable("CCSPlayerPawn"); - if (!pCCSPlayerPawnVTable) - { - snprintf(error, maxlen, "Failed to find CCSPlayerPawn vtable\n"); - bRequiredInitLoaded = false; - } - - offset = g_GameConfig->GetOffset("CCSPlayerPawn::OnTakeDamage_Alive"); - if (offset == -1) - { - snprintf(error, maxlen, "Failed to find CCSPlayerPawn::OnTakeDamage_Alive\n"); - bRequiredInitLoaded = false; - } - SH_MANUALHOOK_RECONFIGURE(OnTakeDamage_Alive, offset, 0, 0); - g_iOnTakeDamageAliveId = SH_ADD_MANUALDVPHOOK(OnTakeDamage_Alive, pCCSPlayerPawnVTable, SH_MEMBER(this, &CS2Fixes::Hook_OnTakeDamage_Alive), false); - - offset = g_GameConfig->GetOffset("Teleport"); - if (offset == -1) - { - snprintf(error, maxlen, "Failed to find Teleport\n"); - bRequiredInitLoaded = false; - } - SH_MANUALHOOK_RECONFIGURE(Teleport, offset, 0, 0); - g_iTeleportPreId = SH_ADD_MANUALDVPHOOK(Teleport, pCCSPlayerPawnVTable, SH_MEMBER(this, &CS2Fixes::Hook_CCSPlayerPawn_Teleport), false); - g_iTeleportPostId = SH_ADD_MANUALDVPHOOK(Teleport, pCCSPlayerPawnVTable, SH_MEMBER(this, &CS2Fixes::Hook_CCSPlayerPawn_Teleport_Post), true); - - const auto pCCSPlayer_MovementServicesVTable = modules::server->FindVirtualTable("CCSPlayer_MovementServices"); - offset = g_GameConfig->GetOffset("CCSPlayer_MovementServices::CheckMovingGround"); - if (offset == -1) - { - snprintf(error, maxlen, "Failed to find CCSPlayer_MovementServices::CheckMovingGround\n"); - bRequiredInitLoaded = false; - } - SH_MANUALHOOK_RECONFIGURE(CheckMovingGround, offset, 0, 0); - g_iCheckMovingGroundId = SH_ADD_MANUALDVPHOOK(CheckMovingGround, pCCSPlayer_MovementServicesVTable, SH_MEMBER(this, &CS2Fixes::Hook_CheckMovingGround), false); - - auto pCVPhys2WorldVTable = modules::vphysics2->FindVirtualTable("CVPhys2World"); - - offset = g_GameConfig->GetOffset("CVPhys2World::GetTouchingList"); - if (offset == -1) - { - snprintf(error, maxlen, "Failed to find CVPhys2World::GetTouchingList\n"); - bRequiredInitLoaded = false; - } - SH_MANUALHOOK_RECONFIGURE(PhysicsTouchShuffle, offset, 0, 0); - g_iPhysicsTouchShuffle = SH_ADD_MANUALDVPHOOK(PhysicsTouchShuffle, pCVPhys2WorldVTable, SH_MEMBER(this, &CS2Fixes::Hook_PhysicsTouchShuffle), true); - - const auto pCCSPlayer_WeaponServicesVTable = modules::server->FindVirtualTable("CCSPlayer_WeaponServices"); - offset = g_GameConfig->GetOffset("CCSPlayer_WeaponServices::DropWeapon"); - if (offset == -1) - { - snprintf(error, maxlen, "Failed to find CCSPlayer_WeaponServices::DropWeapon\n"); - bRequiredInitLoaded = false; - } - SH_MANUALHOOK_RECONFIGURE(DropWeapon, offset, 0, 0); - g_iWeaponServiceDropWeaponId = SH_ADD_MANUALDVPHOOK(DropWeapon, pCCSPlayer_WeaponServicesVTable, SH_MEMBER(this, &CS2Fixes::Hook_DropWeaponPost), true); - - auto pCGameEventManagerVTable = (IGameEventManager2*)modules::server->FindVirtualTable("CGameEventManager"); - g_iLoadEventsFromFileId = SH_ADD_DVPHOOK(IGameEventManager2, LoadEventsFromFile, pCGameEventManagerVTable, SH_MEMBER(this, &CS2Fixes::Hook_LoadEventsFromFile), false); - - auto pCEntitySystemVTable = (CEntitySystem*)modules::server->FindVirtualTable("CGameEntitySystem"); - g_iSpawnId = SH_ADD_DVPHOOK(CEntitySystem, Spawn, pCEntitySystemVTable, SH_MEMBER(this, &CS2Fixes::Hook_Spawn), false); - - auto pCServerSideClientVTable = (CServerSideClient*)modules::engine->FindVirtualTable("CServerSideClient"); - g_iProcessVoiceDataId = SH_ADD_DVPHOOK(CServerSideClient, ProcessVoiceData, pCServerSideClientVTable, SH_MEMBER(this, &CS2Fixes::Hook_ProcessVoiceData), false); - - if (!bRequiredInitLoaded) + if (!g_bRequiredInitLoaded) { snprintf(error, maxlen, "One or more address lookups, patches or detours failed, please refer to startup logs for more information"); return false; @@ -417,45 +219,11 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool bool CS2Fixes::Unload(char* error, size_t maxlen) { - SH_REMOVE_HOOK(IServerGameDLL, GameFrame, g_pSource2Server, SH_MEMBER(this, &CS2Fixes::Hook_GameFramePost), true); - SH_REMOVE_HOOK(IServerGameDLL, GameServerSteamAPIActivated, g_pSource2Server, SH_MEMBER(this, &CS2Fixes::Hook_GameServerSteamAPIActivated), false); - SH_REMOVE_HOOK(IServerGameDLL, ApplyGameSettings, g_pSource2Server, SH_MEMBER(this, &CS2Fixes::Hook_ApplyGameSettings), false); - SH_REMOVE_HOOK(IServerGameClients, ClientActive, g_pSource2GameClients, SH_MEMBER(this, &CS2Fixes::Hook_ClientActive), true); - SH_REMOVE_HOOK(IServerGameClients, ClientDisconnect, g_pSource2GameClients, SH_MEMBER(this, &CS2Fixes::Hook_ClientDisconnect), true); - SH_REMOVE_HOOK(IServerGameClients, ClientPutInServer, g_pSource2GameClients, SH_MEMBER(this, &CS2Fixes::Hook_ClientPutInServer), true); - SH_REMOVE_HOOK(IServerGameClients, ClientSettingsChanged, g_pSource2GameClients, SH_MEMBER(this, &CS2Fixes::Hook_ClientSettingsChanged), false); - SH_REMOVE_HOOK(IServerGameClients, OnClientConnected, g_pSource2GameClients, SH_MEMBER(this, &CS2Fixes::Hook_OnClientConnected), false); - SH_REMOVE_HOOK(IServerGameClients, ClientConnect, g_pSource2GameClients, SH_MEMBER(this, &CS2Fixes::Hook_ClientConnect), false); - SH_REMOVE_HOOK(IServerGameClients, ClientCommand, g_pSource2GameClients, SH_MEMBER(this, &CS2Fixes::Hook_ClientCommand), false); - SH_REMOVE_HOOK(IGameEventSystem, PostEventAbstract, g_gameEventSystem, SH_MEMBER(this, &CS2Fixes::Hook_PostEvent), false); - SH_REMOVE_HOOK(INetworkServerService, StartupServer, g_pNetworkServerService, SH_MEMBER(this, &CS2Fixes::Hook_StartupServer), true); - SH_REMOVE_HOOK(ISource2GameEntities, CheckTransmit, g_pSource2GameEntities, SH_MEMBER(this, &CS2Fixes::Hook_CheckTransmit), true); - SH_REMOVE_HOOK(ICvar, DispatchConCommand, g_pCVar, SH_MEMBER(this, &CS2Fixes::Hook_DispatchConCommand), false); - SH_REMOVE_HOOK_ID(g_iLoadEventsFromFileId); - SH_REMOVE_HOOK_ID(g_iCreateWorkshopMapGroupId); - SH_REMOVE_HOOK_ID(g_iOnTakeDamageAliveId); - SH_REMOVE_HOOK_ID(g_iCheckMovingGroundId); - SH_REMOVE_HOOK_ID(g_iPhysicsTouchShuffle); - SH_REMOVE_HOOK_ID(g_iWeaponServiceDropWeaponId); - SH_REMOVE_HOOK_ID(g_iCGamePlayerEquipUseId); - SH_REMOVE_HOOK_ID(g_iCGamePlayerEquipPrecacheId); - SH_REMOVE_HOOK_ID(g_iCTriggerGravityPrecacheId); - SH_REMOVE_HOOK_ID(g_iCTriggerGravityEndTouchId); - SH_REMOVE_HOOK_ID(g_iSpawnId); - SH_REMOVE_HOOK_ID(g_iTeleportPreId); - SH_REMOVE_HOOK_ID(g_iTeleportPostId); - SH_REMOVE_HOOK_ID(g_iProcessVoiceDataId); - - if (g_iSetGameSpawnGroupMgrId != -1) - SH_REMOVE_HOOK_ID(g_iSetGameSpawnGroupMgrId); - ConVar_Unregister(); + RemoveVirtualHooks(); UnregisterGameSystem(); - CommandList().clear(); - - FlushAllDetours(); UndoPatches(); RemoveAllTimers(); UnregisterEventListeners(); @@ -506,11 +274,7 @@ bool CS2Fixes::Unload(char* error, size_t maxlen) delete g_pPanoramaVoteHandler; if (g_pEWHandler) - { - g_pEWHandler->RemoveAllUseHooks(); - g_pEWHandler->RemoveAllTriggers(); delete g_pEWHandler; - } if (g_pConvarWhitelist) delete g_pConvarWhitelist; @@ -524,316 +288,6 @@ bool CS2Fixes::Unload(char* error, size_t maxlen) return true; } -void CS2Fixes::Hook_DispatchConCommand(ConCommandRef cmdHandle, const CCommandContext& ctx, const CCommand& args) -{ - VPROF_BUDGET("CS2Fixes::Hook_DispatchConCommand", "ConCommands"); - - if (!g_pEntitySystem) - RETURN_META(MRES_IGNORED); - - auto iCommandPlayerSlot = ctx.GetPlayerSlot(); - - if (!g_cvarEnableCommands.Get()) - RETURN_META(MRES_IGNORED); - - bool bSay = !V_stricmp(args.Arg(0), "say"); - bool bTeamSay = !V_stricmp(args.Arg(0), "say_team"); - - if (iCommandPlayerSlot != -1 && (bSay || bTeamSay)) - { - CCSPlayerController* pController = CCSPlayerController::FromSlot(iCommandPlayerSlot); - ZEPlayer* pPlayer = pController ? pController->GetZEPlayer() : nullptr; - - // Block chat messages from players not fully ingame, can be interpreted as console messages - if (!pPlayer || !pPlayer->IsInGame()) - { - Message("Blocked chat message from user ID %i not fully in game\n", g_pEngineServer2->GetPlayerUserId(iCommandPlayerSlot).Get()); - RETURN_META(MRES_SUPERCEDE); - } - - bool bGagged = pPlayer->IsGagged(); - bool bFlooding = pPlayer->IsFlooding(); - bool bIsAdmin = pPlayer->IsAdminFlagSet(ADMFLAG_GENERIC); - bool bAdminChat = bTeamSay && *args[1] == '@'; - bool bSilent = *args[1] == '/' || bAdminChat; - bool bCommand = *args[1] == '!' || *args[1] == '/'; - - // Chat messages should generate events regardless - if (pController) - { - IGameEvent* pEvent = g_gameEventManager->CreateEvent("player_chat"); - - if (pEvent) - { - pEvent->SetBool("teamonly", bTeamSay); - pEvent->SetInt("userid", pController->GetPlayerSlot()); - pEvent->SetString("text", args[1]); - - g_gameEventManager->FireEvent(pEvent, true); - } - } - - if (!bGagged && !bSilent && !bFlooding) - { - SH_CALL(g_pCVar, &ICvar::DispatchConCommand) - (cmdHandle, ctx, args); - - // Reset idle time if message is sent to chat - if (g_cvarIdleKickTime.Get() > 0.0f) - pPlayer->UpdateLastInputTime(); - } - else if (bFlooding) - { - if (pController) - ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "You are flooding the server!"); - } - else if (bAdminChat && GetGlobals()) // Admin chat can be sent by anyone but only seen by admins, use flood protection here too - { - // HACK: At this point, we can safely modify the arg buffer as it won't be passed anywhere else - // The string here is originally ("@foo bar"), trim it to be (foo bar) - char* pszMessage = (char*)(args.ArgS() + 2); - pszMessage[V_strlen(pszMessage) - 1] = 0; - - for (int i = 0; i < GetGlobals()->maxClients; i++) - { - ZEPlayer* pPlayer = g_playerManager->GetPlayer(i); - - if (!pPlayer) - continue; - - if (i == iCommandPlayerSlot.Get() || pPlayer->IsAdminFlagSet(ADMFLAG_GENERIC)) - ClientPrint(CCSPlayerController::FromSlot(i), HUD_PRINTTALK, " \4(%sADMINS) %s:\6 %s", bIsAdmin ? "" : "TO ", pController->GetPlayerName().c_str(), pszMessage); - } - } - - // Finally, run the chat command if it is one, so anything will print after the player's message - if (bCommand) - { - char* pszMessage = (char*)(args.ArgS() + 1); - - if (pszMessage[0] == '"' || pszMessage[0] == '!' || pszMessage[0] == '/') - pszMessage += 1; - - // Host_Say at some point removes the trailing " for whatever reason, so we only remove if it was never called - if ((bGagged || bSilent || bFlooding) && pszMessage[V_strlen(pszMessage) - 1] == '"') - pszMessage[V_strlen(pszMessage) - 1] = '\0'; - - ParseChatCommand(pszMessage, pController); - } - - RETURN_META(MRES_SUPERCEDE); - } - - RETURN_META(MRES_IGNORED); -} - -CConVar g_cvarMotdUrl("cs2f_motd_url", FCVAR_NONE, "Server MOTD URL, shows up as a \"Server Website\" button in scoreboard", ""); - -void CS2Fixes::Hook_StartupServer(const GameSessionConfiguration_t& config, ISource2WorldSession* pSession, const char* pszMapName) -{ - g_pEntitySystem = GameEntitySystem(); - g_pEntitySystem->AddListenerEntity(g_pEntityListener); - - if (g_pNetworkServerService->GetIGameServer()) - g_iSetGameSpawnGroupMgrId = SH_ADD_HOOK(IServer, SetGameSpawnGroupMgr, g_pNetworkServerService->GetIGameServer(), SH_MEMBER(this, &CS2Fixes::Hook_SetGameSpawnGroupMgr), false); - - Message("Hook_StartupServer: %s\n", pszMapName); - - RegisterEventListeners(); - - if (g_bHasTicked) - RemoveTimers(TIMERFLAG_MAP); - - g_bHasTicked = false; - - g_pPanoramaVoteHandler->Reset(); - g_pVoteManager->VoteManager_Init(); - g_pIdleSystem->Reset(); - - INetworkStringTable* pInfoPanelTable = g_pNetworkStringTableServer->FindTable("InfoPanel"); - - if (pInfoPanelTable && V_strcmp(g_cvarMotdUrl.Get(), "")) - { - SetStringUserDataRequest_t pUserData; - pUserData.m_pRawData = (void*)g_cvarMotdUrl.Get().Get(); - pUserData.m_cbDataSize = g_cvarMotdUrl.Get().Length() + 1; - - pInfoPanelTable->AddString(true, "motd", &pUserData); - } -} - -class CGamePlayerEquip; -void CS2Fixes::Hook_CGamePlayerEquipUse(InputData_t* pInput) -{ - CGamePlayerEquipHandler::Use(META_IFACEPTR(CGamePlayerEquip), pInput); - RETURN_META(MRES_IGNORED); -} -void CS2Fixes::Hook_CGamePlayerEquipPrecache(CEntityPrecacheContext* param) -{ - const auto kv = param->m_pKeyValues; - CGamePlayerEquipHandler::OnPrecache(META_IFACEPTR(CGamePlayerEquip), kv); - RETURN_META(MRES_IGNORED); -} -void CS2Fixes::Hook_CTriggerGravityPrecache(CEntityPrecacheContext* param) -{ - const auto kv = param->m_pKeyValues; - CTriggerGravityHandler::OnPrecache(META_IFACEPTR(CBaseEntity), kv); - RETURN_META(MRES_IGNORED); -} -void CS2Fixes::Hook_CTriggerGravityEndTouch(CBaseEntity* pOther) -{ - CTriggerGravityHandler::OnEndTouch(META_IFACEPTR(CBaseEntity), pOther); - RETURN_META(MRES_IGNORED); -} -void CS2Fixes::Hook_GameServerSteamAPIActivated() -{ - g_playerManager->OnSteamAPIActivated(); - - if (g_cvarVoteManagerEnable.Get() && !g_pMapVoteSystem->IsMapListLoaded()) - g_pMapVoteSystem->LoadMapList(); - - RETURN_META(MRES_IGNORED); -} - -CConVar g_cvarBlockParticleMsgs("cs2f_block_particle_msgs", FCVAR_NONE, "Whether to block CUserMsg_ParticleManager messages to fix lag/crashes, experimental", false); - -void CS2Fixes::Hook_PostEvent(CSplitScreenSlot nSlot, bool bLocalOnly, int nClientCount, const uint64* clients, - INetworkMessageInternal* pEvent, const CNetMessage* pData, unsigned long nSize, NetChannelBufType_t bufType) -{ - // Message( "Hook_PostEvent(%d, %d, %d, %lli)\n", nSlot, bLocalOnly, nClientCount, clients ); - // Need to explicitly get a pointer to the right function as it's overloaded and SH_CALL can't resolve that - static void (IGameEventSystem::*PostEventAbstract)(CSplitScreenSlot, bool, int, const uint64*, - INetworkMessageInternal*, const CNetMessage*, unsigned long, NetChannelBufType_t) = &IGameEventSystem::PostEventAbstract; - - NetMessageInfo_t* info = pEvent->GetNetMessageInfo(); - - if (g_cvarEnableStopSound.Get() && info->m_MessageId == GE_FireBulletsId) - { - if (g_playerManager->GetSilenceSoundMask()) - { - // Post the silenced sound to those who use silencesound - // Creating a new event object requires us to include the protobuf c files which I didn't feel like doing yet - // So instead just edit the event in place and reset later - auto msg = const_cast(pData)->ToPB(); - - int32_t weapon_id = msg->weapon_id(); - int32_t sound_type = msg->sound_type(); - int32_t item_def_index = msg->item_def_index(); - - // original weapon_id will override new settings if not removed - msg->set_weapon_id(0); - msg->set_sound_type(9); - msg->set_item_def_index(61); // weapon_usp_silencer - - uint64 clientMask = *(uint64*)clients & g_playerManager->GetSilenceSoundMask(); - - SH_CALL(g_gameEventSystem, PostEventAbstract) - (nSlot, bLocalOnly, nClientCount, &clientMask, pEvent, msg, nSize, bufType); - - msg->set_weapon_id(weapon_id); - msg->set_sound_type(sound_type); - msg->set_item_def_index(item_def_index); - } - - // Filter out people using stop/silence sound from the original event - *(uint64*)clients &= ~g_playerManager->GetStopSoundMask(); - *(uint64*)clients &= ~g_playerManager->GetSilenceSoundMask(); - } - else if (info->m_MessageId == GE_PlaceDecalEvent) - { - *(uint64*)clients &= ~g_playerManager->GetStopDecalsMask(); - } - else if (info->m_MessageId == GE_Source1LegacyGameEvent) - { - if (g_cvarEnableLeader.Get()) - Leader_PostEventAbstract_Source1LegacyGameEvent(clients, pData); - } - else if (info->m_MessageId == UM_Shake) - { - auto pPBData = const_cast(pData)->ToPB(); - if (g_cvarMaxShakeAmp.Get() >= 0 && pPBData->amplitude() > g_cvarMaxShakeAmp.Get()) - pPBData->set_amplitude(g_cvarMaxShakeAmp.Get()); - - // remove client with noshake from the event - if (g_cvarEnableNoShake.Get()) - *(uint64*)clients &= ~g_playerManager->GetNoShakeMask(); - } - else if (info->m_MessageId == GE_SosStartSoundEvent) - { - auto msg = const_cast(pData)->ToPB(); - - if (g_cvarEnableZR.Get()) - ZR_PostEventAbstract_SosStartSoundEvent(clients, msg); - - if (g_cvarEnableStopSound.Get()) - { - static std::set soundEventHashes; - - ExecuteOnce( - soundEventHashes.insert(GetSoundEventHash("Weapon_sg556.ZoomIn")); - soundEventHashes.insert(GetSoundEventHash("Weapon_sg556.ZoomOut")); - soundEventHashes.insert(GetSoundEventHash("Weapon_AUG.ZoomIn")); - soundEventHashes.insert(GetSoundEventHash("Weapon_AUG.ZoomOut")); - soundEventHashes.insert(GetSoundEventHash("Weapon_SSG08.Zoom")); - soundEventHashes.insert(GetSoundEventHash("Weapon_SSG08.ZoomOut")); - soundEventHashes.insert(GetSoundEventHash("Weapon_SCAR20.Zoom")); - soundEventHashes.insert(GetSoundEventHash("Weapon_SCAR20.ZoomOut")); - soundEventHashes.insert(GetSoundEventHash("Weapon_G3SG1.Zoom")); - soundEventHashes.insert(GetSoundEventHash("Weapon_G3SG1.ZoomOut")); - soundEventHashes.insert(GetSoundEventHash("Weapon_AWP.Zoom")); - soundEventHashes.insert(GetSoundEventHash("Weapon_AWP.ZoomOut")); - soundEventHashes.insert(GetSoundEventHash("Weapon_Revolver.Prepare")); - soundEventHashes.insert(GetSoundEventHash("Weapon.AutoSemiAutoSwitch"));); - - if (!soundEventHashes.contains(msg->soundevent_hash())) - return; - - uint64 stopSoundMask = g_playerManager->GetStopSoundMask(); - uint64 silenceSoundMask = g_playerManager->GetSilenceSoundMask(); - - if (!msg->has_source_entity_index()) - return; - - CBaseEntity* pSourceEntity = (CBaseEntity*)g_pEntitySystem->GetEntityInstance(CEntityIndex(msg->source_entity_index())); - int playerSlot = -1; - - if (!pSourceEntity) - return; - - if (pSourceEntity->IsPawn() && ((CCSPlayerPawn*)pSourceEntity)->GetController()) - { - playerSlot = ((CCSPlayerPawn*)pSourceEntity)->GetController()->GetPlayerSlot(); - } - else if (!V_strncasecmp(pSourceEntity->GetClassname(), "weapon_", 7)) - { - CCSPlayerPawn* pPawn = (CCSPlayerPawn*)pSourceEntity->m_hOwnerEntity().Get(); - - if (pPawn && pPawn->IsPawn() && pPawn->GetController()) - playerSlot = pPawn->GetController()->GetPlayerSlot(); - } - - // Remove player who triggered this sound from masks - // Because some of these sounds never get played locally (Zoom's, Knife Hit/Stab) - if (playerSlot != -1 && g_playerManager->IsPlayerUsingStopSound(playerSlot)) - stopSoundMask &= ~((uint64)1 << playerSlot); - - if (playerSlot != -1 && g_playerManager->IsPlayerUsingSilenceSound(playerSlot)) - silenceSoundMask &= ~((uint64)1 << playerSlot); - - // Filter out people using stop/silence sound from hearing this sound from other players - *(uint64*)clients &= ~stopSoundMask; - *(uint64*)clients &= ~silenceSoundMask; - } - } - else if (info->m_MessageId == UM_ParticleManager) - { - // These messages were previously unused, but recently started being used for weapon particles in the AG2 update - // Unfortunately, this new system seems extremely unoptimized for 64 players, and was causing severe performance issues & vector overflow client crashes - if (g_cvarBlockParticleMsgs.Get()) - *(uint64*)clients = 0; - } -} - void CS2Fixes::AllPluginsLoaded() { /* This is where we'd do stuff that relies on the mod or other plugins @@ -843,443 +297,6 @@ void CS2Fixes::AllPluginsLoaded() Message("AllPluginsLoaded\n"); } -void CS2Fixes::Hook_ClientActive(CPlayerSlot slot, bool bLoadGame, const char* pszName, uint64 xuid) -{ - Message("Hook_ClientActive(%d, %d, \"%s\", %lli)\n", slot, bLoadGame, pszName, xuid); -} - -void CS2Fixes::Hook_ClientCommand(CPlayerSlot slot, const CCommand& args) -{ -#ifdef _DEBUG - Message("Hook_ClientCommand(%d, \"%s\")\n", slot, args.GetCommandString()); -#endif - - ZEPlayer* pPlayer = g_playerManager->GetPlayer(slot); - - if (g_cvarIdleKickTime.Get() > 0.0f) - { - CCSPlayerController* pController = CCSPlayerController::FromSlot(slot); - if (pPlayer && pController) - { - // Only spectators doing spectator commands reset idle timer - if (pController->m_iTeamNum() == CS_TEAM_SPECTATOR && (!V_stricmp(args[0], "spec_mode") || !V_stricmp(args[0], "spec_prev") || !V_stricmp(args[0], "spec_next"))) - pPlayer->UpdateLastInputTime(); - } - } - - if (g_cvarVoteManagerEnable.Get() && V_stricmp(args[0], "endmatch_votenextmap") == 0 && args.ArgC() == 2) - { - if (g_pMapVoteSystem->RegisterPlayerVote(slot, atoi(args[1]))) - RETURN_META(MRES_HANDLED); - else - RETURN_META(MRES_SUPERCEDE); - } - - if (g_cvarEnableZR.Get() && slot != -1 && !V_strnicmp(args.Arg(0), "jointeam", 8)) - { - ZR_Hook_ClientCommand_JoinTeam(slot, args); - RETURN_META(MRES_SUPERCEDE); - } -} - -void CS2Fixes::Hook_ClientSettingsChanged(CPlayerSlot slot) -{ -#ifdef _DEBUG - Message("Hook_ClientSettingsChanged(%d)\n", slot); -#endif -} - -void CS2Fixes::Hook_OnClientConnected(CPlayerSlot slot, const char* pszName, uint64 xuid, const char* pszNetworkID, const char* pszAddress, bool bFakePlayer) -{ - Message("Hook_OnClientConnected(%d, \"%s\", %lli, \"%s\", \"%s\", %d)\n", slot, pszName, xuid, pszNetworkID, pszAddress, bFakePlayer); - - static ConVarRefAbstract tv_name("tv_name"); - const char* pszTvName = tv_name.GetString().Get(); - - // Ideally we would use CServerSideClient::IsHLTV().. but it doesn't work :( - if (bFakePlayer && V_strcmp(pszName, pszTvName)) - g_playerManager->OnBotConnected(slot); -} - -bool CS2Fixes::Hook_ClientConnect(CPlayerSlot slot, const char* pszName, uint64 xuid, const char* pszNetworkID, bool unk1, CBufferString* pRejectReason) -{ - Message("Hook_ClientConnect(%d, \"%s\", %lli, \"%s\", %d, \"%s\")\n", slot, pszName, xuid, pszNetworkID, unk1, pRejectReason->Get()); - - // Player is banned - if (!g_playerManager->OnClientConnected(slot, xuid, pszNetworkID)) - RETURN_META_VALUE(MRES_SUPERCEDE, false); - - RETURN_META_VALUE(MRES_IGNORED, true); -} - -void CS2Fixes::Hook_ClientPutInServer(CPlayerSlot slot, char const* pszName, int type, uint64 xuid) -{ - Message("Hook_ClientPutInServer(%d, \"%s\", %d, %d, %lli)\n", slot, pszName, type, xuid); - - if (!g_playerManager->GetPlayer(slot)) - return; - - g_playerManager->OnClientPutInServer(slot); - - if (g_cvarEnableZR.Get()) - ZR_Hook_ClientPutInServer(slot, pszName, type, xuid); -} - -void CS2Fixes::Hook_ClientDisconnect(CPlayerSlot slot, ENetworkDisconnectionReason reason, const char* pszName, uint64 xuid, const char* pszNetworkID) -{ - Message("Hook_ClientDisconnect(%d, %d, \"%s\", %lli)\n", slot, reason, pszName, xuid); - - CCSPlayerController* player = CCSPlayerController::FromSlot(slot); - - if (g_cvarEnableZR.Get()) - { - // Controller team num is not valid post-disconnect, so just check both teams - if (!ZR_CheckTeamWinConditions(CS_TEAM_T)) - ZR_CheckTeamWinConditions(CS_TEAM_CT); - } - - ZEPlayer* pPlayer = g_playerManager->GetPlayer(slot); - - if (!pPlayer) - return; - - // Dont add to c_listdc clients that are downloading MultiAddonManager stuff or were present during a map change - if (reason != NETWORK_DISCONNECT_LOOPSHUTDOWN && reason != NETWORK_DISCONNECT_SHUTDOWN) - g_pAdminSystem->AddDisconnectedPlayer(pszName, xuid, pPlayer ? pPlayer->GetIpAddress() : ""); - - g_playerManager->OnClientDisconnect(slot); -} - -void CS2Fixes::Hook_GameFramePost(bool simulating, bool bFirstTick, bool bLastTick) -{ - /** - * simulating: - * *********** - * true | game is ticking - * false | game is not ticking - */ - - VPROF_BUDGET("CS2Fixes::Hook_GameFramePost", "CS2FixesPerFrame"); - - if (!GetGlobals()) - return; - - if (simulating && g_bHasTicked) - g_flUniversalTime += GetGlobals()->curtime - g_flLastTickedTime; - - g_flLastTickedTime = GetGlobals()->curtime; - g_bHasTicked = true; - - RunTimers(); - EntityHandler_OnGameFramePost(simulating, GetGlobals()->tickcount); -} - -void CS2Fixes::Hook_CheckTransmit(CCheckTransmitInfo** ppInfoList, int infoCount, CBitVec<16384>& unionTransmitEdicts, - CBitVec<16384>&, const Entity2Networkable_t** pNetworkables, const uint16* pEntityIndicies, int nEntities) -{ - if (!g_pEntitySystem || !GetGlobals()) - return; - - VPROF("CS2Fixes::Hook_CheckTransmit"); - - for (int i = 0; i < infoCount; i++) - { - auto& pInfo = (CCheckTransmitInfoExtended*&)(ppInfoList[i]); - CCSPlayerController* pSelfController = CCSPlayerController::FromSlot(pInfo->m_nPlayerSlot); - - if (!pSelfController || !pSelfController->IsConnected()) - continue; - - auto pSelfZEPlayer = g_playerManager->GetPlayer(pInfo->m_nPlayerSlot); - - if (!pSelfZEPlayer) - continue; - - for (int j = 0; j < GetGlobals()->maxClients; j++) - { - CCSPlayerController* pController = CCSPlayerController::FromSlot(j); - // Always transmit to themselves - if (!pController || pController->m_bIsHLTV || j == pInfo->m_nPlayerSlot.Get()) - continue; - - // Don't transmit other players' flashlights - CBarnLight* pFlashLight = pController->IsConnected() ? g_playerManager->GetPlayer(j)->GetFlashLight() : nullptr; - - if (!g_cvarFlashLightTransmitOthers.Get() && pFlashLight) - pInfo->m_pTransmitEntity->Clear(pFlashLight->entindex()); - - if (g_cvarEnableEntWatch.Get() && g_pEWHandler->IsConfigLoaded()) - { - // Don't transmit other players' entwatch hud - CPointWorldText* pHud = pController->IsConnected() ? g_playerManager->GetPlayer(j)->GetEntwatchHud() : nullptr; - if (pHud) - pInfo->m_pTransmitEntity->Clear(pHud->entindex()); - } - - // Always transmit other players if spectating - if (!g_cvarEnableHide.Get() || pSelfController->GetPawnState() == STATE_OBSERVER_MODE) - continue; - - // Get the actual pawn as the player could be currently spectating - CCSPlayerPawn* pPawn = pController->GetPlayerPawn(); - - if (!pPawn) - continue; - - // Do not hide leaders or item holders to other players - ZEPlayer* pOtherZEPlayer = g_playerManager->GetPlayer(j); - if (pSelfZEPlayer->ShouldBlockTransmit(j) && pOtherZEPlayer && !pOtherZEPlayer->IsLeader() && g_pEWHandler->FindItemInstanceByOwner(j, false, 0) == -1) - { - pInfo->m_pTransmitEntity->Clear(pPawn->entindex()); - pInfo->m_pTransmitNonPlayers->Set(pPawn->entindex()); - } - } - - // Don't transmit glow model to it's owner - CBaseModelEntity* pGlowModel = pSelfZEPlayer->GetGlowModel(); - - if (pGlowModel) - pInfo->m_pTransmitEntity->Clear(pGlowModel->entindex()); - } -} - -void CS2Fixes::Hook_ApplyGameSettings(KeyValues* pKV) -{ - const char* pszMapName; - uint64 iWorkshopId; - - if (pKV->FindKey("launchoptions") && pKV->FindKey("launchoptions")->FindKey("levelname")) - pszMapName = pKV->FindKey("launchoptions")->GetString("levelname"); - else - pszMapName = ""; - - if (pKV->FindKey("launchoptions") && pKV->FindKey("launchoptions")->FindKey("customgamemode")) - iWorkshopId = pKV->FindKey("launchoptions")->GetUint64("customgamemode"); - else - iWorkshopId = 0; - - g_pCfgParser->ApplyGameSettings(pszMapName); - g_pMapVoteSystem->ApplyGameSettings(pszMapName, iWorkshopId); - g_pMapMigrations->ApplyGameSettings(iWorkshopId); -} - -void CS2Fixes::Hook_CreateWorkshopMapGroup(const char* name, const CUtlStringList& mapList) -{ - if (g_cvarVoteManagerEnable.Get() && g_pMapVoteSystem->IsMapListLoaded()) - RETURN_META_MNEWPARAMS(MRES_HANDLED, CreateWorkshopMapGroup, (name, g_pMapVoteSystem->CreateWorkshopMapGroup())); - else - RETURN_META(MRES_IGNORED); -} - -CConVar g_cvarDropMapWeapons("cs2f_drop_map_weapons", FCVAR_NONE, "Whether to force drop map-spawned weapons on death", false); - -bool CS2Fixes::Hook_OnTakeDamage_Alive(CTakeDamageResult* pDamageResult) -{ - CCSPlayerPawn* pPawn = META_IFACEPTR(CCSPlayerPawn); - - if (g_cvarEnableZR.Get() && ZR_Hook_OnTakeDamage_Alive(pDamageResult->m_pOriginatingInfo, pPawn)) - { - pDamageResult->m_bWasDamageSuppressed = true; - pDamageResult->m_flDamageDealt = 0.0f; - RETURN_META_VALUE(MRES_SUPERCEDE, false); - } - - // This is a shit place to be doing this, but player_death event is too late and there is no pre-hook alternative - // Check if this is going to kill the player - if (g_cvarDropMapWeapons.Get() && pPawn && pPawn->m_iHealth() <= 0) - { - if (g_cvarEnableEntWatch.Get()) - { - CCSPlayerController* pController = pPawn->GetOriginalController(); - if (pController) - EW_PlayerDeathPre(pController); - } - - pPawn->DropMapWeapons(); - } - - RETURN_META_VALUE(MRES_IGNORED, true); -} - -CConVar g_cvarFixPhysicsPlayerShuffle("cs2f_shuffle_player_physics_sim", FCVAR_NONE, "Whether to enable shuffle player list in physics simulate", false); - -struct TouchLinked_t -{ - uint32_t TouchFlags; - -private: - uint8_t padding_0[20]; - -public: - CBaseHandle SourceHandle; - CBaseHandle TargetHandle; - -private: - uint8_t padding_1[224]; - -public: - [[nodiscard]] bool IsUnTouching() const - { - return !!(TouchFlags & 0x10); - } - - [[nodiscard]] bool IsTouching() const - { - return (!!(TouchFlags & 4)) || (!!(TouchFlags & 8)); - } -}; -static_assert(sizeof(TouchLinked_t) == 256, "Touch_t size mismatch"); -void CS2Fixes::Hook_PhysicsTouchShuffle(CUtlVector* pList, bool unknown) -{ - if (!g_cvarFixPhysicsPlayerShuffle.Get() || g_SHPtr->GetStatus() == MRES_SUPERCEDE || pList->Count() <= 1) - return; - - // [Kxnrl] - // seems it sorted by flags? - - if (GetGlobals()) - std::srand(GetGlobals()->tickcount); - - // Fisher-Yates shuffle - - std::vector touchingLinks; - std::vector unTouchLinks; - - FOR_EACH_VEC(*pList, i) - { - const auto& link = pList->Element(i); - if (link.IsUnTouching()) - unTouchLinks.push_back(link); - else - touchingLinks.push_back(link); - } - - if (touchingLinks.size() <= 1) - return; - - for (size_t i = touchingLinks.size() - 1; i > 0; --i) - { - const auto j = std::rand() % (i + 1); - std::swap(touchingLinks[i], touchingLinks[j]); - } - - pList->Purge(); - - for (const auto& link : touchingLinks) - pList->AddToTail(link); - for (const auto& link : unTouchLinks) - pList->AddToTail(link); -} - -void CS2Fixes::Hook_CheckMovingGround(double frametime) -{ - CCSPlayer_MovementServices* pMove = META_IFACEPTR(CCSPlayer_MovementServices); - CCSPlayerPawn* pPawn = pMove->GetPawn(); - - if (!pPawn || !GetGlobals()) - RETURN_META(MRES_IGNORED); - - CCSPlayerController* pController = pPawn->GetOriginalController(); - - if (!pController) - RETURN_META(MRES_IGNORED); - - int iSlot = pController->GetPlayerSlot(); - - static int aPlayerTicks[MAXPLAYERS] = {0}; - - // The point of doing this is to avoid running the function (and applying/resetting basevelocity) multiple times per tick - // This can happen when the client or server lags - if (aPlayerTicks[iSlot] == GetGlobals()->tickcount) - RETURN_META(MRES_SUPERCEDE); - - aPlayerTicks[iSlot] = GetGlobals()->tickcount; - - RETURN_META(MRES_IGNORED); -} - -void CS2Fixes::Hook_DropWeaponPost(CBasePlayerWeapon* pWeapon, Vector* pVecTarget, Vector* pVelocity) -{ - if (g_cvarEnableEntWatch.Get()) - { - CCSPlayer_WeaponServices* pWeaponService = META_IFACEPTR(CCSPlayer_WeaponServices); - EW_DropWeapon(pWeaponService, pWeapon); - } - - RETURN_META(MRES_IGNORED); -} - -int CS2Fixes::Hook_LoadEventsFromFile(const char* filename, bool bSearchAll) -{ - ExecuteOnce(g_gameEventManager = META_IFACEPTR(IGameEventManager2)); - - RETURN_META_VALUE(MRES_IGNORED, 0); -} - -void CS2Fixes::Hook_SetGameSpawnGroupMgr(IGameSpawnGroupMgr* pSpawnGroupMgr) -{ - // This also resets our stored pointer on deletion, since null gets passed into this function, nice! - g_pSpawnGroupMgr = (CSpawnGroupMgrGameSystem*)pSpawnGroupMgr; -} - -void CS2Fixes::Hook_Spawn(int nCount, const EntitySpawnInfo_t* pInfo) -{ - for (int i = 0; i < nCount; i++) - g_pMapMigrations->OnEntitySpawned_Pre(reinterpret_cast(pInfo[i].m_pEntity->m_pInstance), pInfo[i].m_pKeyValues); -} - -float g_fTeleportXAngle; - -void CS2Fixes::Hook_CCSPlayerPawn_Teleport(const Vector* pPosition, const QAngle* pAngles, const Vector* pVelocity) -{ - if (!pAngles) - RETURN_META(MRES_IGNORED); - - g_fTeleportXAngle = pAngles->x; - - QAngle* pCastAngles = const_cast(pAngles); - - // Post-AG2, changing x or z angles via Teleport on a playermodel will bug out, go through SnapViewAngles for x later instead - pCastAngles->x = 0.0f; - pCastAngles->z = 0.0f; - - RETURN_META(MRES_HANDLED); -} - -void CS2Fixes::Hook_CCSPlayerPawn_Teleport_Post(const Vector* pPosition, const QAngle* pAngles, const Vector* pVelocity) -{ - if (!pAngles) - RETURN_META(MRES_IGNORED); - - // Revert the x edit, z should always be 0 - QAngle* pCastAngles = const_cast(pAngles); - pCastAngles->x = g_fTeleportXAngle; - - META_IFACEPTR(CCSPlayerPawn)->SnapViewAngles(pCastAngles); - RETURN_META(MRES_HANDLED); -} - -bool CS2Fixes::Hook_ProcessVoiceData(const CCLCMsg_VoiceData_t& msg) -{ - CServerSideClient* client = META_IFACEPTR(CServerSideClient); - - if (!client) - RETURN_META_VALUE(MRES_IGNORED, true); - - ZEPlayer* pPlayer = g_playerManager->GetPlayer(client->GetPlayerSlot()); - - if (!pPlayer) - RETURN_META_VALUE(MRES_IGNORED, true); - - if (pPlayer->IsMuted()) - RETURN_META_VALUE(MRES_SUPERCEDE, true); - - if (GetGlobals()) - pPlayer->SetLastVoiceTime(GetGlobals()->curtime); - - RETURN_META_VALUE(MRES_IGNORED, true); -} - void* CS2Fixes::OnMetamodQuery(const char* iface, int* ret) { if (V_strcmp(iface, CS2FIXES_INTERFACE)) @@ -1351,7 +368,7 @@ void CS2Fixes::OnLevelInit(char const* pMapName, // Only patch BotNavIgnore while a map is loaded, else adding bots will crash if (V_strcmp(pMapName, "error")) - g_CommonPatches[1].PerformPatch(g_GameConfig); + g_CommonPatches[1].PerformPatch(); g_playerManager->SetupInfiniteAmmo(); g_pMapVoteSystem->OnLevelInit(pMapName); @@ -1378,6 +395,8 @@ void CS2Fixes::OnLevelShutdown() if (g_cvarVoteManagerEnable.Get()) g_pMapVoteSystem->OnLevelShutdown(); + + CCSCustomHudLayout::ClearCallbacks(); } bool CS2Fixes::Pause(char* error, size_t maxlen) diff --git a/src/cs2fixes.h b/src/cs2fixes.h index ea13cc119..e34860e10 100644 --- a/src/cs2fixes.h +++ b/src/cs2fixes.h @@ -29,7 +29,6 @@ #include "steam/isteamhttp.h" #include #include -#include #ifdef AMBUILD #include "version_gen.h" @@ -37,12 +36,6 @@ #include "version_gen_placeholder.h" #endif -class CCSPlayer_MovementServices; -class CServerSideClient; -struct TouchLinked_t; -class CCSPlayer_WeaponServices; -class CBasePlayerWeapon; - extern IGameEventSystem* g_gameEventSystem; extern IGameEventManager2* g_gameEventManager; extern CGameEntitySystem* g_pEntitySystem; @@ -50,6 +43,9 @@ extern IVEngineServer2* g_pEngineServer2; extern CCSGameRules* g_pGameRules; extern CSpawnGroupMgrGameSystem* g_pSpawnGroupMgr; extern double g_flUniversalTime; +extern float g_flLastTickedTime; +extern bool g_bHasTicked; +extern bool g_bRequiredInitLoaded; extern INetworkGameServer* GetNetworkGameServer(); extern CGlobalVars* GetGlobals(); extern CConVar g_cvarDropMapWeapons; @@ -58,15 +54,10 @@ class CS2Fixes : public ISmmPlugin, public IMetamodListener, public ICS2Fixes { public: bool Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool late); - void Hook_PostEvent(CSplitScreenSlot nSlot, bool bLocalOnly, int nClientCount, const uint64* clients, - INetworkMessageInternal* pEvent, const CNetMessage* pData, unsigned long nSize, NetChannelBufType_t bufType); bool Unload(char* error, size_t maxlen); bool Pause(char* error, size_t maxlen); bool Unpause(char* error, size_t maxlen); void AllPluginsLoaded(); - -public: // hooks - void Hook_GameServerSteamAPIActivated(); void OnLevelInit(char const* pMapName, char const* pMapEntities, char const* pOldLevel, @@ -74,41 +65,6 @@ class CS2Fixes : public ISmmPlugin, public IMetamodListener, public ICS2Fixes bool loadGame, bool background); void OnLevelShutdown(); - void Hook_GameFramePost(bool simulating, bool bFirstTick, bool bLastTick); - void Hook_ClientActive(CPlayerSlot slot, bool bLoadGame, const char* pszName, uint64 xuid); - void Hook_ClientDisconnect(CPlayerSlot slot, ENetworkDisconnectionReason reason, const char* pszName, uint64 xuid, const char* pszNetworkID); - void Hook_ClientPutInServer(CPlayerSlot slot, char const* pszName, int type, uint64 xuid); - void Hook_ClientSettingsChanged(CPlayerSlot slot); - void Hook_OnClientConnected(CPlayerSlot slot, const char* pszName, uint64 xuid, const char* pszNetworkID, const char* pszAddress, bool bFakePlayer); - bool Hook_ClientConnect(CPlayerSlot slot, const char* pszName, uint64 xuid, const char* pszNetworkID, bool unk1, CBufferString* pRejectReason); - void Hook_ClientCommand(CPlayerSlot nSlot, const CCommand& _cmd); - void Hook_CheckTransmit(CCheckTransmitInfo** ppInfoList, int infoCount, CBitVec<16384>& unionTransmitEdicts, - CBitVec<16384>&, const Entity2Networkable_t** pNetworkables, const uint16* pEntityIndicies, int nEntities); - void Hook_DispatchConCommand(ConCommandRef cmd, const CCommandContext& ctx, const CCommand& args); - void Hook_CGamePlayerEquipUse(class InputData_t*); - void Hook_CGamePlayerEquipPrecache(CEntityPrecacheContext*); - void Hook_CTriggerGravityPrecache(CEntityPrecacheContext* param); - void Hook_CTriggerGravityEndTouch(CBaseEntity* pOther); - void Hook_StartupServer(const GameSessionConfiguration_t& config, ISource2WorldSession*, const char*); - void Hook_ApplyGameSettings(KeyValues* pKV); - void Hook_CreateWorkshopMapGroup(const char* name, const CUtlStringList& mapList); - bool Hook_OnTakeDamage_Alive(CTakeDamageResult* pDamageResult); - void Hook_PhysicsTouchShuffle(CUtlVector* pList, bool unknown); - void Hook_CCSPlayerPawn_Teleport(const Vector* pPosition, const QAngle* pAngles, const Vector* pVelocity); - void Hook_CCSPlayerPawn_Teleport_Post(const Vector* pPosition, const QAngle* pAngles, const Vector* pVelocity); -#ifdef PLATFORM_WINDOWS - Vector* Hook_GetEyePosition(Vector*); - QAngle* Hook_GetEyeAngles(QAngle*); -#else - Vector Hook_GetEyePosition(); - QAngle Hook_GetEyeAngles(); -#endif - void Hook_CheckMovingGround(double frametime); - void Hook_DropWeaponPost(CBasePlayerWeapon* pWeapon, Vector* pVecTarget, Vector* pVelocity); - int Hook_LoadEventsFromFile(const char* filename, bool bSearchAll); - void Hook_SetGameSpawnGroupMgr(IGameSpawnGroupMgr* pSpawnGroupMgr); - bool Hook_ProcessVoiceData(const CCLCMsg_VoiceData_t& msg); - void Hook_Spawn(int nCount, const EntitySpawnInfo_t* pInfo); public: // MetaMod API void* OnMetamodQuery(const char* iface, int* ret); diff --git a/src/detours.cpp b/src/detours.cpp index eea5ac108..c8fec1698 100644 --- a/src/detours.cpp +++ b/src/detours.cpp @@ -24,7 +24,6 @@ #include "addresses.h" #include "bspflags.h" #include "buttonwatch.h" -#include "cdetour.h" #include "commands.h" #include "common.h" #include "ctimer.h" @@ -47,6 +46,7 @@ #include "gameconfig.h" #include "igameevents.h" #include "irecipientfilter.h" +#include "khook_helpers.h" #include "map_votes.h" #include "mapmigrations.h" #include "module.h" @@ -59,48 +59,59 @@ #include "tier0/memdbgon.h" -CUtlVector g_vecDetours; - -DECLARE_DETOUR(UTIL_SayTextFilter, Detour_UTIL_SayTextFilter); -DECLARE_DETOUR(UTIL_SayText2Filter, Detour_UTIL_SayText2Filter); -DECLARE_DETOUR(TriggerPush_Touch, Detour_TriggerPush_Touch); -DECLARE_DETOUR(CBaseEntity_TakeDamageOld, Detour_CBaseEntity_TakeDamageOld); -DECLARE_DETOUR(CCSPlayer_WeaponServices_CanUse, Detour_CCSPlayer_WeaponServices_CanUse); -DECLARE_DETOUR(CCSPlayer_WeaponServices_EquipWeapon, Detour_CCSPlayer_WeaponServices_EquipWeapon); -DECLARE_DETOUR(CEntityIdentity_AcceptInput, Detour_CEntityIdentity_AcceptInput); -DECLARE_DETOUR(CNavMesh_GetNearestNavArea, Detour_CNavMesh_GetNearestNavArea); -DECLARE_DETOUR(ProcessMovement, Detour_ProcessMovement); -DECLARE_DETOUR(ProcessUsercmds, Detour_ProcessUsercmds); -DECLARE_DETOUR(CGamePlayerEquip_InputTriggerForAllPlayers, Detour_CGamePlayerEquip_InputTriggerForAllPlayers); -DECLARE_DETOUR(CGamePlayerEquip_InputTriggerForActivatedPlayer, Detour_CGamePlayerEquip_InputTriggerForActivatedPlayer); -DECLARE_DETOUR(CTriggerGravity_GravityTouch, Detour_CTriggerGravity_GravityTouch); -DECLARE_DETOUR(GetFreeClient, Detour_GetFreeClient); +KHOOK_FUNCTION(UTIL_SayTextFilter, Detour_UTIL_SayTextFilter, nullptr); +KHOOK_FUNCTION(UTIL_SayText2Filter, Detour_UTIL_SayText2Filter, nullptr); +KHOOK_MEMBER(TriggerPush_Touch, Detour_TriggerPush_Touch, nullptr); +KHOOK_MEMBER(CBaseEntity_TakeDamageOld, Detour_CBaseEntity_TakeDamageOld, Detour_CBaseEntity_TakeDamageOld_Post); +KHOOK_MEMBER(CCSPlayer_WeaponServices_CanUse, Detour_CCSPlayer_WeaponServices_CanUse, nullptr); +KHOOK_MEMBER(CCSPlayer_WeaponServices_EquipWeapon, Detour_CCSPlayer_WeaponServices_EquipWeapon, nullptr); +KHOOK_MEMBER(CEntityIdentity_AcceptInput, Detour_CEntityIdentity_AcceptInput, nullptr); +KHOOK_MEMBER(CNavMesh_GetNearestNavArea, Detour_CNavMesh_GetNearestNavArea, nullptr); +KHOOK_MEMBER(ProcessMovement, Detour_ProcessMovement, Detour_ProcessMovement_Post); +KHOOK_MEMBER(ProcessUsercmds, Detour_ProcessUsercmds, nullptr); +KHOOK_MEMBER(CGamePlayerEquip_InputTriggerForAllPlayers, Detour_CGamePlayerEquip_InputTriggerForAllPlayers, nullptr); +KHOOK_MEMBER(CGamePlayerEquip_InputTriggerForActivatedPlayer, Detour_CGamePlayerEquip_InputTriggerForActivatedPlayer, nullptr); +KHOOK_MEMBER(CTriggerGravity_GravityTouch, Detour_CTriggerGravity_GravityTouch, nullptr); +KHOOK_FUNCTION(GetFreeClient, Detour_GetFreeClient, nullptr); #ifdef __linux__ // Inlined by MSVC as of 2025-07-28 CS2 update // TODO: Find some alternative that supports Windows -DECLARE_DETOUR(CCSPlayerPawn_GetMaxSpeed, Detour_CCSPlayerPawn_GetMaxSpeed); +KHOOK_MEMBER(CCSPlayerPawn_GetMaxSpeed, Detour_CCSPlayerPawn_GetMaxSpeed, nullptr); #endif -DECLARE_DETOUR(FindUseEntity, Detour_FindUseEntity); -DECLARE_DETOUR(TraceFunc, Detour_TraceFunc); -DECLARE_DETOUR(TraceShape, Detour_TraceShape); -DECLARE_DETOUR(CBasePlayerPawn_GetEyePosition, Detour_CBasePlayerPawn_GetEyePosition); -DECLARE_DETOUR(CBasePlayerPawn_GetEyeAngles, Detour_CBasePlayerPawn_GetEyeAngles); -DECLARE_DETOUR(CBaseFilter_InputTestActivator, Detour_CBaseFilter_InputTestActivator); -DECLARE_DETOUR(GameSystem_Think_CheckSteamBan, Detour_GameSystem_Think_CheckSteamBan); -DECLARE_DETOUR(CCSPlayer_ItemServices_CanAcquire, Detour_CCSPlayer_ItemServices_CanAcquire); -DECLARE_DETOUR(CS_Script_SetModel, Detour_CS_Script_SetModel); -DECLARE_DETOUR(CBaseModelEntity_SetModel, Detour_CBaseModelEntity_SetModel); -DECLARE_DETOUR(CCSGameRules_GoToIntermission, Detour_CCSGameRules_GoToIntermission); -DECLARE_DETOUR(SetBeamOrigin, Detour_SetBeamOrigin); -DECLARE_DETOUR(SetBeamEndPos, Detour_SetBeamEndPos); -DECLARE_DETOUR(IsCommandWhitelisted, Detour_IsCommandWhitelisted); +KHOOK_MEMBER(FindUseEntity, Detour_FindUseEntity, Detour_FindUseEntity_Post); +KHOOK_FUNCTION(TraceFunc, Detour_TraceFunc, nullptr); +KHOOK_FUNCTION(TraceShape, Detour_TraceShape, nullptr); +KHOOK_MEMBER(CEntityIOOutput_FireOutputInternal, Detour_CEntityIOOutput_FireOutputInternal, nullptr); +KHOOK_MEMBER(CBasePlayerPawn_GetEyePosition, Detour_CBasePlayerPawn_GetEyePosition, nullptr); +KHOOK_MEMBER(CBasePlayerPawn_GetEyeAngles, Detour_CBasePlayerPawn_GetEyeAngles, nullptr); +KHOOK_FUNCTION(GameSystem_Think_CheckSteamBan, Detour_GameSystem_Think_CheckSteamBan, Detour_GameSystem_Think_CheckSteamBan_Post); +KHOOK_MEMBER(CCSPlayer_ItemServices_CanAcquire, Detour_CCSPlayer_ItemServices_CanAcquire, nullptr); +KHOOK_FUNCTION(CS_Script_SetModel, Detour_CS_Script_SetModel, Detour_CS_Script_SetModel_Post); +KHOOK_MEMBER(CBaseModelEntity_SetModel, Detour_CBaseModelEntity_SetModel, nullptr); +KHOOK_MEMBER(CCSGameRules_GoToIntermission, Detour_CCSGameRules_GoToIntermission, nullptr); +KHOOK_MEMBER(SetBeamOrigin, Detour_SetBeamOrigin, nullptr); +KHOOK_MEMBER(SetBeamEndPos, Detour_SetBeamEndPos, nullptr); +KHOOK_FUNCTION(IsCommandWhitelisted, Detour_IsCommandWhitelisted, nullptr); +KHOOK_MEMBER(CWorldRendererMgr_CreateWorld_Internal, nullptr, Detour_CWorldRendererMgr_CreateWorld_Internal_Post); + +std::vector& GetKHookList() +{ + static std::vector s_vecSigHooks; + return s_vecSigHooks; +} + +void InitKHooks() +{ + for (auto hook : GetKHookList()) + hook->Configure(); +} CConVar g_cvarBlockMolotovSelfDmg("cs2f_block_molotov_self_dmg", FCVAR_NONE, "Whether to block self-damage from molotovs", false); CConVar g_cvarBlockAllDamage("cs2f_block_all_dmg", FCVAR_NONE, "Whether to block all damage to players", false); CConVar g_cvarFixBlockDamage("cs2f_fix_block_dmg", FCVAR_NONE, "Whether to fix block-damage on players", false); CConVar g_cvarPropDamageScale("cs2f_prop_dmg_scale", FCVAR_NONE, "Multiplier on prop damage", 1.0f, true, 0.0f, false, 0.0f); -int64 FASTCALL Detour_CBaseEntity_TakeDamageOld(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult) +KHook::Return Detour_CBaseEntity_TakeDamageOld(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult) { // NOTE valve always return 1 here, since 2025/10/15 update. @@ -123,7 +134,7 @@ int64 FASTCALL Detour_CBaseEntity_TakeDamageOld(CBaseEntity* pThis, CTakeDamageI // Block all player damage if desired if (g_cvarBlockAllDamage.Get() && pThis->IsPawn()) - return 1; + return {KHook::Action::Supersede, 1}; CEntityInstance* pInflictor = pInfo->m_hInflictor.Get(); const char* pszInflictorClass = pInflictor ? pInflictor->GetClassname() : ""; @@ -149,11 +160,11 @@ int64 FASTCALL Detour_CBaseEntity_TakeDamageOld(CBaseEntity* pThis, CTakeDamageI // Prevent molly on self if (g_cvarBlockMolotovSelfDmg.Get() && pInfo->m_hAttacker == pThis && !V_strncmp(pszInflictorClass, "inferno", 7)) - return 1; + return {KHook::Action::Supersede, 1}; // Fix disconnected players grenades being able to damage teammates if (!V_strcasecmp(pszInflictorClass, "hegrenade_projectile") && pInfo->m_AttackerInfo.m_bIsPawn && pInfo->m_AttackerInfo.m_nTeam == 0) - return 1; + return {KHook::Action::Supersede, 1}; if (!V_strncasecmp(pszInflictorClass, "prop_physics", 12)) { @@ -167,50 +178,47 @@ int64 FASTCALL Detour_CBaseEntity_TakeDamageOld(CBaseEntity* pThis, CTakeDamageI if (pResult == nullptr) { damageResult.CopyFrom(pInfo); - pResult = &damageResult; + return KHook::Recall(nullptr, {KHook::Action::Ignore}, pThis, pInfo, &damageResult); } - CBaseEntity_TakeDamageOld(pThis, pInfo, pResult); + return {KHook::Action::Ignore}; +} - if (pResult->m_flDamageDealt > 0.0f && !pResult->m_bWasDamageSuppressed && g_cvarEnableZR.Get() && pThis->IsPawn()) +KHook::Return Detour_CBaseEntity_TakeDamageOld_Post(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult) +{ + if (!KHook::WasOriginalFunctionSkipped() && pResult->m_flDamageDealt > 0.0f && !pResult->m_bWasDamageSuppressed && g_cvarEnableZR.Get() && pThis->IsPawn()) ZR_OnPlayerTakeDamage(reinterpret_cast(pThis), pInfo, pResult->m_flDamageDealt); - return 1; + return {KHook::Action::Ignore}; } CConVar g_cvarUseOldPush("cs2f_use_old_push", FCVAR_NONE, "Whether to use the old CSGO trigger_push behavior", false); CConVar g_cvarLogPushes("cs2f_log_pushes", FCVAR_NONE, "Whether to log pushes (cs2f_use_old_push must be enabled)", false); -void FASTCALL Detour_TriggerPush_Touch(CTriggerPush* pPush, CBaseEntity* pOther) +KHook::Return Detour_TriggerPush_Touch(CTriggerPush* pPush, CBaseEntity* pOther) { // This trigger pushes only once (and kills itself) or pushes only on StartTouch, both of which are fine already if (!g_cvarUseOldPush.Get() || pPush->m_spawnflags() & SF_TRIG_PUSH_ONCE || pPush->m_bTriggerOnStartTouch()) - { - TriggerPush_Touch(pPush, pOther); - return; - } + return {KHook::Action::Ignore}; MoveType_t movetype = pOther->m_nActualMoveType(); // VPhysics handling doesn't need any changes if (movetype == MOVETYPE_VPHYSICS) - { - TriggerPush_Touch(pPush, pOther); - return; - } + return {KHook::Action::Ignore}; if (movetype == MOVETYPE_NONE || movetype == MOVETYPE_PUSH || movetype == MOVETYPE_NOCLIP) - return; + return {KHook::Action::Supersede}; CCollisionProperty* collisionProp = pOther->m_pCollision(); if (!IsSolid(collisionProp->m_nSolidType(), collisionProp->m_usSolidFlags())) - return; + return {KHook::Action::Supersede}; if (!pPush->PassesTriggerFilters(pOther)) - return; + return {KHook::Action::Supersede}; if (pOther->m_CBodyComponent()->m_pSceneNode()->m_pParent()) - return; + return {KHook::Action::Supersede}; Vector vecAbsDir; matrix3x4_t matTransform = pPush->m_CBodyComponent()->m_pSceneNode()->EntityToWorldTransform(); @@ -253,15 +261,14 @@ void FASTCALL Detour_TriggerPush_Touch(CTriggerPush* pPush, CBaseEntity* pOther) flags |= (1 << 23); // TODO: is FL_BASEVELOCITY really gone? pOther->m_fFlags(flags); + + return {KHook::Action::Supersede}; } -void SayChatMessageWithTimer(IRecipientFilter& filter, const char* pText, CCSPlayerController* pPlayer, uint64 eMessageType) +KHook::Return SayChatMessageWithTimer(IRecipientFilter& filter, const char* pText, CCSPlayerController* pPlayer, uint64 eMessageType) { VPROF("SayChatMessageWithTimer"); - if (!GetGlobals() || !g_pGameRules) - return; - char buf[256]; // Filter console message - remove non-alphanumeric chars and convert to lowercase @@ -357,29 +364,29 @@ void SayChatMessageWithTimer(IRecipientFilter& filter, const char* pText, CCSPla else V_snprintf(buf, sizeof(buf), "%s %s", " \7CONSOLE:\4", pText + sizeof("Console:")); - UTIL_SayTextFilter(filter, buf, pPlayer, eMessageType); + return KHook::Recall(nullptr, {KHook::Action::Ignore}, filter, buf, pPlayer, eMessageType); } CConVar g_cvarEnableTriggerTimer("cs2f_trigger_timer_enable", FCVAR_NONE, "Whether to process countdown messages said by Console (e.g. Hold for 10 seconds) and append the round time where the countdown resolves", false); -void FASTCALL Detour_UTIL_SayTextFilter(IRecipientFilter& filter, const char* pText, CCSPlayerController* pPlayer, uint64 eMessageType) +KHook::Return Detour_UTIL_SayTextFilter(IRecipientFilter& filter, const char* pText, CCSPlayerController* pPlayer, int eMessageType) { if (pPlayer) - return UTIL_SayTextFilter(filter, pText, pPlayer, eMessageType); + return {KHook::Action::Ignore}; - if (g_cvarEnableTriggerTimer.Get()) + if (g_cvarEnableTriggerTimer.Get() && GetGlobals() && g_pGameRules) return SayChatMessageWithTimer(filter, pText, pPlayer, eMessageType); char buf[256]; V_snprintf(buf, sizeof(buf), "%s %s", " \7CONSOLE:\4", pText + sizeof("Console:")); - UTIL_SayTextFilter(filter, buf, pPlayer, eMessageType); + return KHook::Recall(nullptr, {KHook::Action::Ignore}, filter, buf, pPlayer, eMessageType); } -void FASTCALL Detour_UTIL_SayText2Filter( +KHook::Return Detour_UTIL_SayText2Filter( IRecipientFilter& filter, CCSPlayerController* pEntity, - uint64 eMessageType, + int eMessageType, const char* msg_name, const char* param1, const char* param2, @@ -394,25 +401,25 @@ void FASTCALL Detour_UTIL_SayText2Filter( Message("Chat from %s to %s: %s\n", param1, target->GetPlayerName().c_str(), param2); #endif - UTIL_SayText2Filter(filter, pEntity, eMessageType, msg_name, pEntity->GetPlayerName().c_str(), param2, param3, param4); + return KHook::Recall(nullptr, {KHook::Action::Ignore}, filter, pEntity, eMessageType, msg_name, pEntity->GetPlayerName().c_str(), param2, param3, param4); } -bool FASTCALL Detour_CCSPlayer_WeaponServices_CanUse(CCSPlayer_WeaponServices* pWeaponServices, CBasePlayerWeapon* pPlayerWeapon) +KHook::Return Detour_CCSPlayer_WeaponServices_CanUse(CCSPlayer_WeaponServices* pWeaponServices, CBasePlayerWeapon* pPlayerWeapon) { if (g_cvarEnableEntWatch.Get() && !EW_Detour_CCSPlayer_WeaponServices_CanUse(pWeaponServices, pPlayerWeapon)) - return false; + return {KHook::Action::Supersede, false}; - return CCSPlayer_WeaponServices_CanUse(pWeaponServices, pPlayerWeapon); + return {KHook::Action::Ignore}; } -void FASTCALL Detour_CCSPlayer_WeaponServices_EquipWeapon(CCSPlayer_WeaponServices* pWeaponServices, CBasePlayerWeapon* pPlayerWeapon) +KHook::Return Detour_CCSPlayer_WeaponServices_EquipWeapon(CCSPlayer_WeaponServices* pWeaponServices, CBasePlayerWeapon* pPlayerWeapon) { if (g_cvarEnableEntWatch.Get()) EW_Detour_CCSPlayer_WeaponServices_EquipWeapon(pWeaponServices, pPlayerWeapon); g_pMapMigrations->OnEquipWeapon(pPlayerWeapon); - return CCSPlayer_WeaponServices_EquipWeapon(pWeaponServices, pPlayerWeapon); + return {KHook::Action::Ignore}; } CConVar g_cvarDisableSetModel("cs2f_disable_setmodel", FCVAR_NONE, "Whether to disable SetModel usage from maps (custom input, cs_script function)", false); @@ -433,7 +440,7 @@ bool PrepareMapSetModel(CBaseModelEntity* pModel) return true; } -bool FASTCALL Detour_CEntityIdentity_AcceptInput(CEntityIdentity* pThis, CUtlSymbolLarge* pInputName, CEntityInstance* pActivator, CEntityInstance* pCaller, variant_t* value, void* a6, void* a7) +KHook::Return Detour_CEntityIdentity_AcceptInput(CEntityIdentity* pThis, CUtlSymbolLarge* pInputName, CEntityInstance* pActivator, CEntityInstance* pCaller, variant_t* value, CPulseArgumentPack* pArgumentPack, CPulseInputParamMap* pParamMap) { VPROF_SCOPE_BEGIN("Detour_CEntityIdentity_AcceptInput"); @@ -442,7 +449,7 @@ bool FASTCALL Detour_CEntityIdentity_AcceptInput(CEntityIdentity* pThis, CUtlSym bool result = ZR_Detour_CEntityIdentity_AcceptInput(pThis, pInputName, pActivator, pCaller, value); if (!result) - return result; + return {KHook::Action::Supersede, result}; } // Handle KeyValue(s) @@ -451,10 +458,10 @@ bool FASTCALL Detour_CEntityIdentity_AcceptInput(CEntityIdentity* pThis, CUtlSym if ((value->m_type == FIELD_CSTRING || value->m_type == FIELD_STRING) && value->m_pszString) { // always const char*, even if it's FIELD_STRING (that is bug string from lua 'EntFire') - return CustomIO_HandleInput(pThis->m_pInstance, value->m_pszString, pActivator, pCaller); + return {KHook::Action::Supersede, CustomIO_HandleInput(pThis->m_pInstance, value->m_pszString, pActivator, pCaller)}; } Message("Invalid value type for input %s\n", pInputName->String()); - return false; + return {KHook::Action::Supersede, false}; } if (!V_strnicmp(pInputName->String(), "IgniteL", 7)) // Override IgniteLifetime @@ -469,7 +476,7 @@ bool FASTCALL Detour_CEntityIdentity_AcceptInput(CEntityIdentity* pThis, CUtlSym CCSPlayerPawn* pPawn = reinterpret_cast(pThis->m_pInstance); if (pPawn->IsPawn() && IgnitePawn(pPawn, flDuration, pPawn, pPawn)) - return true; + return {KHook::Action::Supersede, true}; } else if (!V_strnicmp(pInputName->String(), "AddScore", 8)) { @@ -485,7 +492,7 @@ bool FASTCALL Detour_CEntityIdentity_AcceptInput(CEntityIdentity* pThis, CUtlSym if (pPawn->IsPawn() && pPawn->GetOriginalController()) { pPawn->GetOriginalController()->AddScore(iScore); - return true; + return {KHook::Action::Supersede, true}; } } else if (!V_strcasecmp(pInputName->String(), "SetMessage")) @@ -494,7 +501,7 @@ bool FASTCALL Detour_CEntityIdentity_AcceptInput(CEntityIdentity* pThis, CUtlSym { if ((value->m_type == FIELD_CSTRING || value->m_type == FIELD_STRING) && value->m_pszString) pHudHint->m_iszMessage(GameEntitySystem()->AllocPooledString(value->m_pszString)); - return true; + return {KHook::Action::Supersede, true}; } } else if (!V_strcasecmp(pInputName->String(), "SetModel")) @@ -504,56 +511,64 @@ bool FASTCALL Detour_CEntityIdentity_AcceptInput(CEntityIdentity* pThis, CUtlSym if ((value->m_type == FIELD_CSTRING || value->m_type == FIELD_STRING) && value->m_pszString && PrepareMapSetModel(pModelEntity)) pModelEntity->SetModel(value->m_pszString); - return true; + return {KHook::Action::Supersede, true}; } } else if (const auto pGameUI = reinterpret_cast(pThis->m_pInstance)->AsGameUI()) { if (!V_strcasecmp(pInputName->String(), "Activate")) - return CGameUIHandler::OnActivate(pGameUI, reinterpret_cast(pActivator)); + return {KHook::Action::Supersede, CGameUIHandler::OnActivate(pGameUI, reinterpret_cast(pActivator))}; if (!V_strcasecmp(pInputName->String(), "Deactivate")) - return CGameUIHandler::OnDeactivate(pGameUI, reinterpret_cast(pActivator)); + return {KHook::Action::Supersede, CGameUIHandler::OnDeactivate(pGameUI, reinterpret_cast(pActivator))}; } else if (const auto pViewControl = reinterpret_cast(pThis->m_pInstance)->AsPointViewControl()) { if (!V_strcasecmp(pInputName->String(), "EnableCamera")) - return CPointViewControlHandler::OnEnable(pViewControl, reinterpret_cast(pActivator)); + return {KHook::Action::Supersede, CPointViewControlHandler::OnEnable(pViewControl, reinterpret_cast(pActivator))}; if (!V_strcasecmp(pInputName->String(), "DisableCamera")) - return CPointViewControlHandler::OnDisable(pViewControl, reinterpret_cast(pActivator)); + return {KHook::Action::Supersede, CPointViewControlHandler::OnDisable(pViewControl, reinterpret_cast(pActivator))}; if (!V_strcasecmp(pInputName->String(), "EnableCameraAll")) - return CPointViewControlHandler::OnEnableAll(pViewControl); + return {KHook::Action::Supersede, CPointViewControlHandler::OnEnableAll(pViewControl)}; if (!V_strcasecmp(pInputName->String(), "DisableCameraAll")) - return CPointViewControlHandler::OnDisableAll(pViewControl); + return {KHook::Action::Supersede, CPointViewControlHandler::OnDisableAll(pViewControl)}; } + if (g_pMapMigrations->Detour_CEntityIdentity_AcceptInput(pThis, pInputName, pActivator, pCaller, value)) + return {KHook::Action::Supersede, true}; + + // If null activator (player disconnected & pawn removed), block the real TestActivator function from executing and crashing the server + if (!V_strnicmp(pThis->GetClassname(), "filter_", 7) && !V_strcasecmp(pInputName->String(), "TestActivator") && !pActivator) + return {KHook::Action::Supersede, true}; + VPROF_SCOPE_END(); - return CEntityIdentity_AcceptInput(pThis, pInputName, pActivator, pCaller, value, a6, a7); + return {KHook::Action::Ignore}; } CConVar g_cvarBlockNavLookup("cs2f_block_nav_lookup", FCVAR_NONE, "Whether to block navigation mesh lookup, improves server performance but breaks bot navigation", false); -void* FASTCALL Detour_CNavMesh_GetNearestNavArea(int64_t unk1, float* unk2, unsigned int* unk3, unsigned int unk4, int64_t unk5, float unk6, int64_t unk7) +KHook::Return Detour_CNavMesh_GetNearestNavArea(CNavMesh* pNavMesh, float* unk2, unsigned int* unk3, unsigned int unk4, int64_t unk5, float unk6, int64_t unk7) { if (g_cvarBlockNavLookup.Get()) - return nullptr; + return {KHook::Action::Supersede, nullptr}; - return CNavMesh_GetNearestNavArea(unk1, unk2, unk3, unk4, unk5, unk6, unk7); + return {KHook::Action::Ignore}; } CConVar g_cvarAllowDuckSpam("cs2f_allow_duck_spam", FCVAR_NONE, "Whether to allow duck spamming by removing the duck slowdown, clients will only partially predict [0 = disabled, 1 = both teams, 2 = T only, 3 = CT only]", 0, true, 0, true, CS_TEAM_CT); +float g_flStoreFrametime = 0.0f; -void FASTCALL Detour_ProcessMovement(CCSPlayer_MovementServices* pThis, void* pMove) +KHook::Return Detour_ProcessMovement(CCSPlayer_MovementServices* pThis, void* pMove) { CCSPlayerPawn* pPawn = pThis->GetPawn(); if (!pPawn->IsAlive() || !GetGlobals()) - return ProcessMovement(pThis, pMove); + return {KHook::Action::Ignore}; CCSPlayerController* pController = pPawn->GetOriginalController(); if (!pController || !pController->IsConnected()) - return ProcessMovement(pThis, pMove); + return {KHook::Action::Ignore}; int iAllowDuckSpam = g_cvarAllowDuckSpam.Get(); @@ -563,17 +578,25 @@ void FASTCALL Detour_ProcessMovement(CCSPlayer_MovementServices* pThis, void* pM float flSpeedMod = pController->GetZEPlayer()->GetSpeedMod(); if (flSpeedMod == 1.f) - return ProcessMovement(pThis, pMove); + return {KHook::Action::Ignore}; // Yes, this is what source1 does to scale player speed // Scale frametime during the entire movement processing step and revert right after - float flStoreFrametime = GetGlobals()->frametime; - + g_flStoreFrametime = GetGlobals()->frametime; GetGlobals()->frametime *= flSpeedMod; - ProcessMovement(pThis, pMove); + return {KHook::Action::Ignore}; +} + +KHook::Return Detour_ProcessMovement_Post(CCSPlayer_MovementServices* pThis, void* pMove) +{ + if (g_flStoreFrametime != 0.0f) + { + GetGlobals()->frametime = g_flStoreFrametime; + g_flStoreFrametime = 0.0f; + } - GetGlobals()->frametime = flStoreFrametime; + return {KHook::Action::Ignore}; } CConVar g_cvarDisableSubtickMovement("cs2f_disable_subtick_move", FCVAR_NONE, "Whether to disable subtick movement", false); @@ -590,7 +613,7 @@ class CUserCmd #endif }; -void* FASTCALL Detour_ProcessUsercmds(CCSPlayerController* pController, CUserCmd* cmds, int numcmds, bool paused, float margin) +KHook::Return Detour_ProcessUsercmds(CCSPlayerController* pController, CUserCmd* cmds, int numcmds, bool paused, float margin) { VPROF_SCOPE_BEGIN("Detour_ProcessUsercmds"); @@ -635,40 +658,42 @@ void* FASTCALL Detour_ProcessUsercmds(CCSPlayerController* pController, CUserCmd VPROF_SCOPE_END(); - return ProcessUsercmds(pController, cmds, numcmds, paused, margin); + return {KHook::Action::Ignore}; } -void FASTCALL Detour_CGamePlayerEquip_InputTriggerForAllPlayers(CGamePlayerEquip* pEntity, InputData_t* pInput) +KHook::Return Detour_CGamePlayerEquip_InputTriggerForAllPlayers(CGamePlayerEquip* pEntity, InputData_t* pInput) { CGamePlayerEquipHandler::TriggerForAllPlayers(pEntity, pInput); - CGamePlayerEquip_InputTriggerForAllPlayers(pEntity, pInput); + return {KHook::Action::Ignore}; } -void FASTCALL Detour_CGamePlayerEquip_InputTriggerForActivatedPlayer(CGamePlayerEquip* pEntity, InputData_t* pInput) +KHook::Return Detour_CGamePlayerEquip_InputTriggerForActivatedPlayer(CGamePlayerEquip* pEntity, InputData_t* pInput) { if (CGamePlayerEquipHandler::TriggerForActivatedPlayer(pEntity, pInput)) - CGamePlayerEquip_InputTriggerForActivatedPlayer(pEntity, pInput); + return {KHook::Action::Ignore}; + + return {KHook::Action::Supersede}; } -void FASTCALL Detour_CTriggerGravity_GravityTouch(CBaseEntity* pEntity, CBaseEntity* pOther) +KHook::Return Detour_CTriggerGravity_GravityTouch(CTriggerGravity* pEntity, CBaseEntity* pOther) { // no need to call original function here // because original function calls CBaseEntity::SetGravityScale internal // but passes the wrong gravity scale value if (CTriggerGravityHandler::GravityTouching(pEntity, pOther)) - return; + return {KHook::Action::Supersede}; - CTriggerGravity_GravityTouch(pEntity, pOther); + return {KHook::Action::Ignore}; } -CServerSideClient* FASTCALL Detour_GetFreeClient(int64_t unk1, const __m128i* unk2, unsigned int unk3, int64_t unk4, char unk5, void* unk6) +KHook::Return Detour_GetFreeClient(int64_t unk1, const __m128i* unk2, unsigned int unk3, int64_t unk4, char unk5, void* unk6) { // Not sure if this function can even be called in this state, but if it is, we can't do shit anyways if (!GetClientList() || !GetGlobals()) - return nullptr; + return {KHook::Action::Supersede, nullptr}; // Check if there is still unused slots, this should never break so just fall back to original behaviour for ease (we don't have a CServerSideClient constructor) if (GetGlobals()->maxClients != GetClientList()->Count()) - return GetFreeClient(unk1, unk2, unk3, unk4, unk5, unk6); + return {KHook::Action::Ignore}; // Phantom client fix for (int i = 0; i < GetClientList()->Count(); i++) @@ -676,49 +701,53 @@ CServerSideClient* FASTCALL Detour_GetFreeClient(int64_t unk1, const __m128i* un CServerSideClient* pClient = (*GetClientList())[i]; if (pClient && pClient->GetSignonState() < SIGNONSTATE_CONNECTED) - return pClient; + return {KHook::Action::Supersede, pClient}; } // Server is actually full for real - return nullptr; + return {KHook::Action::Supersede, nullptr}; } #ifdef __linux__ -float FASTCALL Detour_CCSPlayerPawn_GetMaxSpeed(CCSPlayerPawn* pPawn) +KHook::Return Detour_CCSPlayerPawn_GetMaxSpeed(CCSPlayerPawn* pPawn) { - auto flMaxSpeed = CCSPlayerPawn_GetMaxSpeed(pPawn); + auto flMaxSpeed = hookCCSPlayerPawn_GetMaxSpeed->CallOriginal(pPawn); const auto pController = reinterpret_cast(pPawn->GetController()); if (const auto pPlayer = pController != nullptr ? pController->GetZEPlayer() : nullptr) flMaxSpeed *= pPlayer->GetMaxSpeed(); - return flMaxSpeed; + return {KHook::Action::Supersede, flMaxSpeed}; } #endif CConVar g_cvarPreventUsingPlayers("cs2f_prevent_using_players", FCVAR_NONE, "Whether to prevent +use from hitting players (0=can use players, 1=cannot use players)", false); - bool g_bFindingUseEntity = false; -int64 FASTCALL Detour_FindUseEntity(CCSPlayer_UseServices* pThis, float a2) + +KHook::Return Detour_FindUseEntity(CCSPlayer_UseServices* pThis, float unk2, int64_t unk3) { g_bFindingUseEntity = true; - int64 ent = FindUseEntity(pThis, a2); + return {KHook::Action::Ignore}; +} + +KHook::Return Detour_FindUseEntity_Post(CCSPlayer_UseServices* pThis, float unk2, int64_t unk3) +{ g_bFindingUseEntity = false; - return ent; + return {KHook::Action::Ignore}; } -bool FASTCALL Detour_TraceFunc(int64* a1, int* a2, float* a3, uint64 traceMask) +KHook::Return Detour_TraceFunc(int64* a1, int* a2, float* a3, uint64 traceMask) { if (g_cvarPreventUsingPlayers.Get() && g_bFindingUseEntity) { uint64 newMask = traceMask & (~(CONTENTS_PLAYER & CONTENTS_NPC)); - return TraceFunc(a1, a2, a3, newMask); + KHook::Recall(nullptr, {KHook::Action::Ignore}, a1, a2, a3, newMask); } - return TraceFunc(a1, a2, a3, traceMask); + return {KHook::Action::Ignore}; } -bool FASTCALL Detour_TraceShape(int64* a1, int64 a2, int64 a3, int64 a4, CTraceFilter* filter, int64 a6) +KHook::Return Detour_TraceShape(int64* a1, int64 a2, int64 a3, int64 a4, CTraceFilter* filter, int64 a6) { if (g_cvarPreventUsingPlayers.Get() && g_bFindingUseEntity) { @@ -726,202 +755,199 @@ bool FASTCALL Detour_TraceShape(int64* a1, int64 a2, int64 a3, int64 a4, CTraceF filter->DisableInteractsWithLayer(LAYER_INDEX_CONTENTS_NPC); } - return TraceShape(a1, a2, a3, a4, filter, a6); + return {KHook::Action::Ignore}; } -CDetour* CEntityIOOutput_FireOutputInternal = nullptr; -std::map> mapIOFunctions{}; -void FASTCALL Detour_CEntityIOOutput_FireOutputInternal(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEntityInstance* pCaller, const CVariant* value, float flDelay, void* a6, void* a7) +KHook::Return Detour_CEntityIOOutput_FireOutputInternal(CEntityIOOutput* pThis, CEntityInstance* pActivator, CEntityInstance* pCaller, CPulseArgumentPack* pArgumentPack, float flDelay, CPulseInputParamMap* pParamMap, const CVariant* value) { - for (const auto& [name, cb] : mapIOFunctions) - cb(pThis, pActivator, pCaller, value, flDelay); - - (*CEntityIOOutput_FireOutputInternal)(pThis, pActivator, pCaller, value, flDelay, a6, a7); -} + if (g_cvarEnableButtonWatch.Get()) + ButtonWatch(pThis, pActivator, pCaller, flDelay); -// Tries to setup Detour_CEntityIOOutput_FireOutputInternal if it is not already setup. This is not -// enabled unless a feature needs it, as the detour breaks CS# compatibility -// Returns true if detour is usable, otherwise false. -bool SetupFireOutputInternalDetour() -{ - if (CEntityIOOutput_FireOutputInternal != nullptr) - return true; + if (g_cvarEnableEntWatch.Get()) + EW_FireOutput(pThis, pActivator, pCaller, flDelay); - CEntityIOOutput_FireOutputInternal = new CDetour(Detour_CEntityIOOutput_FireOutputInternal, "CEntityIOOutput_FireOutputInternal"); - if (!CEntityIOOutput_FireOutputInternal->CreateDetour(g_GameConfig)) - { - Panic("Failed to detour CEntityIOOutput_FireOutputInternal\n"); - delete CEntityIOOutput_FireOutputInternal; - CEntityIOOutput_FireOutputInternal = nullptr; - return false; - } - CEntityIOOutput_FireOutputInternal->EnableDetour(); - return true; + return {KHook::Action::Ignore}; } #ifdef PLATFORM_WINDOWS -Vector* FASTCALL Detour_CBasePlayerPawn_GetEyePosition(CBasePlayerPawn* pPawn, Vector* pRet) +KHook::Return Detour_CBasePlayerPawn_GetEyePosition(CBasePlayerPawn* pPawn, Vector* pRet) { if (pPawn->IsAlive() && CPointViewControlHandler::IsViewControl(reinterpret_cast(pPawn))) { const auto& origin = pPawn->GetEyePosition(); pRet->Init(origin.x, origin.y, origin.z); - return pRet; + return {KHook::Action::Supersede, pRet}; } - return CBasePlayerPawn_GetEyePosition(pPawn, pRet); + return {KHook::Action::Ignore}; } -QAngle* FASTCALL Detour_CBasePlayerPawn_GetEyeAngles(CBasePlayerPawn* pPawn, QAngle* pRet) +KHook::Return Detour_CBasePlayerPawn_GetEyeAngles(CBasePlayerPawn* pPawn, QAngle* pRet) { if (pPawn->IsAlive() && CPointViewControlHandler::IsViewControl(reinterpret_cast(pPawn))) { const auto& angles = pPawn->v_angle(); pRet->Init(angles.x, angles.y, angles.z); - return pRet; + return {KHook::Action::Supersede, pRet}; } - return CBasePlayerPawn_GetEyeAngles(pPawn, pRet); + return {KHook::Action::Ignore}; } #else -Vector FASTCALL Detour_CBasePlayerPawn_GetEyePosition(CBasePlayerPawn* pPawn) +KHook::Return Detour_CBasePlayerPawn_GetEyePosition(CBasePlayerPawn* pPawn) { if (pPawn->IsAlive() && CPointViewControlHandler::IsViewControl(reinterpret_cast(pPawn))) { const auto& origin = pPawn->GetEyePosition(); - return origin; + return {KHook::Action::Supersede, origin}; } - return CBasePlayerPawn_GetEyePosition(pPawn); + return {KHook::Action::Ignore}; } -QAngle FASTCALL Detour_CBasePlayerPawn_GetEyeAngles(CBasePlayerPawn* pPawn) +KHook::Return Detour_CBasePlayerPawn_GetEyeAngles(CBasePlayerPawn* pPawn) { if (pPawn->IsAlive() && CPointViewControlHandler::IsViewControl(reinterpret_cast(pPawn))) { const auto& angles = pPawn->v_angle(); - return angles; + return {KHook::Action::Supersede, angles}; } - return CBasePlayerPawn_GetEyeAngles(pPawn); + return {KHook::Action::Ignore}; } #endif -void FASTCALL Detour_CBaseFilter_InputTestActivator(CBaseEntity* pThis, InputData_t& inputdata) +CConVar g_cvarFixGameBans("cs2f_fix_game_bans", FCVAR_NONE, "Whether to fix CS2 game bans spreading to all new joining players", false); + +KHook::Return Detour_GameSystem_Think_CheckSteamBan() { - // If null activator (player disconnected & pawn removed), block the real function from executing and crashing the server - if (!inputdata.pActivator) - return; + auto pMap = addresses::sm_mapGcBanInformation; + static ConVarRefAbstract sv_kick_players_with_cooldown("sv_kick_players_with_cooldown"); - CBaseFilter_InputTestActivator(pThis, inputdata); -} + // Fix competitive cooldowns still being applied without sv_kick_players_with_cooldown 2 + if (sv_kick_players_with_cooldown.GetInt() < 2) + { + for (int i = pMap->FirstInorder(); i != pMap->InvalidIndex();) + { + int next = pMap->NextInorder(i); + uint32_t reason = pMap->Element(i).m_uiReason; -CConVar g_cvarFixGameBans("cs2f_fix_game_bans", FCVAR_NONE, "Whether to fix CS2 game bans spreading to all new joining players", false); + if (reason == 20 || reason == 22 || reason == 23) + pMap->RemoveAt(i); -void FASTCALL Detour_GameSystem_Think_CheckSteamBan() -{ - // Implementation shared by @aiolos1045 - GameSystem_Think_CheckSteamBan(); + i = next; + } + } - if (!g_cvarFixGameBans.Get()) - return; + return {KHook::Action::Ignore}; +} +KHook::Return Detour_GameSystem_Think_CheckSteamBan_Post() +{ auto pMap = addresses::sm_mapGcBanInformation; - unsigned int count = pMap->Count(); // After player has been kicked, remove any ban entries, to prevent spreading to all new joining players - if (count > 0) + // Implementation shared by @aiolos1045 + if (g_cvarFixGameBans.Get() && pMap->Count() > 0) pMap->RemoveAll(); + + return {KHook::Action::Ignore}; } -AcquireResult FASTCALL Detour_CCSPlayer_ItemServices_CanAcquire(CCSPlayer_ItemServices* pItemServices, CEconItemView* pEconItem, AcquireMethod iAcquireMethod, uint64_t unk4) +KHook::Return Detour_CCSPlayer_ItemServices_CanAcquire(CCSPlayer_ItemServices* pItemServices, CEconItemView* pEconItem, AcquireMethod iAcquireMethod, uint64_t unk4) { if (g_cvarEnableZR.Get()) { AcquireResult zrResult = ZR_Detour_CCSPlayer_ItemServices_CanAcquire(pItemServices, pEconItem); if (zrResult != AcquireResult::Allowed) - return zrResult; + return {KHook::Action::Supersede, zrResult}; } - return CCSPlayer_ItemServices_CanAcquire(pItemServices, pEconItem, iAcquireMethod, unk4); + return {KHook::Action::Ignore}; } bool g_bInScriptSetModel = false; -void FASTCALL Detour_CS_Script_SetModel(uint64_t unk1) +KHook::Return Detour_CS_Script_SetModel(uint64_t unk1) { g_bInScriptSetModel = true; - CS_Script_SetModel(unk1); + return {KHook::Action::Ignore}; +} + +KHook::Return Detour_CS_Script_SetModel_Post(uint64_t unk1) +{ g_bInScriptSetModel = false; + return {KHook::Action::Ignore}; } -void FASTCALL Detour_CBaseModelEntity_SetModel(CBaseModelEntity* pModel, const char* pszModel) +KHook::Return Detour_CBaseModelEntity_SetModel(CBaseModelEntity* pModel, const char* pszModel) { if (!g_bInScriptSetModel) - return CBaseModelEntity_SetModel(pModel, pszModel); + return {KHook::Action::Ignore}; if (PrepareMapSetModel(pModel)) - return CBaseModelEntity_SetModel(pModel, pszModel); + return {KHook::Action::Ignore}; + + return {KHook::Action::Supersede}; } -void FASTCALL Detour_CCSGameRules_GoToIntermission(CCSGameRules* pThis, bool bAbortedMatch) +KHook::Return Detour_CCSGameRules_GoToIntermission(CCSGameRules* pThis, bool bAbortedMatch) { if (!g_pMapVoteSystem->IsIntermissionAllowed(false) && g_cvarVoteManagerEnable.Get()) - return; + return {KHook::Action::Supersede}; if (g_cvarVoteManagerEnable.Get()) g_pVoteManager->OnIntermission(); - return CCSGameRules_GoToIntermission(pThis, bAbortedMatch); + return {KHook::Action::Ignore}; } -void FASTCALL Detour_SetBeamOrigin(CBeam* pThis, const Vector* pVecPosition) +KHook::Return Detour_SetBeamOrigin(CBeam* pThis, const Vector* pVecPosition) { // Game code still works for parented beams/lasers if (pThis->m_CBodyComponent()->m_pSceneNode()->m_pParent()) - SetBeamOrigin(pThis, pVecPosition); + return {KHook::Action::Ignore}; // If no parent, then game code would hit infinite loop, just reimplement this simple path ourselves pThis->SetAbsOrigin(*pVecPosition); + return {KHook::Action::Supersede}; } -void FASTCALL Detour_SetBeamEndPos(CBeam* pThis, const Vector* pVecPosition) +KHook::Return Detour_SetBeamEndPos(CBeam* pThis, const Vector* pVecPosition) { // Game code still works for parented beams/lasers if (pThis->m_CBodyComponent()->m_pSceneNode()->m_pParent()) - SetBeamEndPos(pThis, pVecPosition); + return {KHook::Action::Ignore}; // If no parent, then game code would hit infinite loop, just reimplement this simple path ourselves pThis->m_vecEndPos = *(VectorWS*)(pVecPosition); + return {KHook::Action::Supersede}; } -bool FASTCALL Detour_IsCommandWhitelisted(void* pAddonManager, const char* pszCommandName) +KHook::Return Detour_IsCommandWhitelisted(void* pAddonManager, const char* pszCommandName) { if (!g_cvarConVarWhitelistEnable.Get() || !g_pConvarWhitelist->IsConfigLoaded()) - return IsCommandWhitelisted(pAddonManager, pszCommandName); + return {KHook::Action::Ignore}; - return g_pConvarWhitelist->IsWhitelisted(pszCommandName); + return {KHook::Action::Supersede, g_pConvarWhitelist->IsWhitelisted(pszCommandName)}; } -bool InitDetours(CGameConfig* gameConfig) +KHook::Return Detour_CWorldRendererMgr_CreateWorld_Internal_Post(IWorldRendererMgr* pThis, CSingleWorldRep* singleWorld) { - bool success = true; + // The world can fail to be created and the function will return nullptr. + if (!KHook::GetOriginalReturn()) + return {KHook::Action::Ignore}; - FOR_EACH_VEC(g_vecDetours, i) + auto pWorld = singleWorld->m_pCWorld; + auto vecLumpData = (CUtlVector*)((uint8_t*)pWorld + 0x298); + + FOR_EACH_VEC(*vecLumpData, i) { - if (!g_vecDetours[i]->CreateDetour(gameConfig)) - success = false; + auto& lump = (*vecLumpData)[i]; + auto lumpData = *(LumpData**)lump; + auto vecEntityKeyValues = (CUtlVector*)((uint8_t*)lumpData + 0x1220); - g_vecDetours[i]->EnableDetour(); + g_pMapMigrations->RunMigrations(vecEntityKeyValues); } - return success; + return {KHook::Action::Ignore}; } - -void FlushAllDetours() -{ - FOR_EACH_VEC_BACK(g_vecDetours, i) - { - g_vecDetours[i]->FreeDetour(); - g_vecDetours.FastRemove(i); - } -} \ No newline at end of file diff --git a/src/detours.h b/src/detours.h index 19f4cdc7e..73f3d27c2 100644 --- a/src/detours.h +++ b/src/detours.h @@ -18,19 +18,20 @@ */ #pragma once -#include "cdetour.h" #include "cs2_sdk/entityio.h" +#include "khook.hpp" +#include "map.h" #include class CCheckTransmitInfo; class IRecipientFilter; class ISoundEmitterSystemBase; class CBaseEntity; -class CBaseEntity; class CCSPlayerController; class CEntityIndex; class CCommand; class CTriggerPush; +class CTriggerGravity; class CGameConfig; class CGameRules; class CTakeDamageInfo; @@ -49,6 +50,8 @@ class InputData_t; class CCSPlayerPawn; class CCSPlayer_UseServices; class CTraceFilter; +class CNavMesh; +class CBaseModelEntity; class Vector; class QAngle; class CEconItemView; @@ -56,10 +59,6 @@ class CCSGameRules; class CBeam; struct CTakeDamageResult; -// Add callback functions to this map that wish to hook into Detour_CEntityIOOutput_FireOutputInternal -// to make it more modular/cleaner than shoving everything into the detour (buttonwatch, entwatch, etc.) -extern std::map> mapIOFunctions; - enum class AcquireMethod { PickUp, @@ -81,42 +80,43 @@ enum class AcquireResult NotAllowedByProhibition, }; -bool InitDetours(CGameConfig* gameConfig); -void FlushAllDetours(); -bool SetupFireOutputInternalDetour(); - -void FASTCALL Detour_UTIL_SayTextFilter(IRecipientFilter&, const char*, CCSPlayerController*, uint64); -void FASTCALL Detour_UTIL_SayText2Filter(IRecipientFilter&, CCSPlayerController*, uint64, const char*, const char*, const char*, const char*, const char*); -void FASTCALL Detour_TriggerPush_Touch(CTriggerPush* pPush, CBaseEntity* pOther); -int64 FASTCALL Detour_CBaseEntity_TakeDamageOld(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult); -bool FASTCALL Detour_CCSPlayer_WeaponServices_CanUse(CCSPlayer_WeaponServices*, CBasePlayerWeapon*); -void FASTCALL Detour_CCSPlayer_WeaponServices_EquipWeapon(CCSPlayer_WeaponServices*, CBasePlayerWeapon*); -bool FASTCALL Detour_CEntityIdentity_AcceptInput(CEntityIdentity* pThis, CUtlSymbolLarge* pInputName, CEntityInstance* pActivator, CEntityInstance* pCaller, variant_t* value, void*, void*); -void* FASTCALL Detour_CNavMesh_GetNearestNavArea(int64_t unk1, float* unk2, unsigned int* unk3, unsigned int unk4, int64_t unk5, float unk6, int64_t unk7); -void FASTCALL Detour_ProcessMovement(CCSPlayer_MovementServices* pThis, void* pMove); -void* FASTCALL Detour_ProcessUsercmds(CCSPlayerController* pController, CUserCmd* cmds, int numcmds, bool paused, float margin); -void FASTCALL Detour_CGamePlayerEquip_InputTriggerForAllPlayers(CGamePlayerEquip*, InputData_t*); -void FASTCALL Detour_CGamePlayerEquip_InputTriggerForActivatedPlayer(CGamePlayerEquip*, InputData_t*); -void FASTCALL Detour_CTriggerGravity_GravityTouch(CBaseEntity* pEntity, CBaseEntity* pOther); -CServerSideClient* FASTCALL Detour_GetFreeClient(int64_t unk1, const __m128i* unk2, unsigned int unk3, int64_t unk4, char unk5, void* unk6); -float FASTCALL Detour_CCSPlayerPawn_GetMaxSpeed(CCSPlayerPawn*); -int64 FASTCALL Detour_FindUseEntity(CCSPlayer_UseServices* pThis, float); -bool FASTCALL Detour_TraceFunc(int64*, int*, float*, uint64); -bool FASTCALL Detour_TraceShape(int64*, int64, int64, int64, CTraceFilter*, int64); -void FASTCALL Detour_CEntityIOOutput_FireOutputInternal(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEntityInstance* pCaller, const CVariant* value, float flDelay, void*, void*); +KHook::Return Detour_CBaseEntity_TakeDamageOld(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult); +KHook::Return Detour_CBaseEntity_TakeDamageOld_Post(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult); +KHook::Return Detour_TriggerPush_Touch(CTriggerPush* pPush, CBaseEntity* pOther); +KHook::Return Detour_UTIL_SayTextFilter(IRecipientFilter&, const char*, CCSPlayerController*, int); +KHook::Return Detour_UTIL_SayText2Filter(IRecipientFilter&, CCSPlayerController*, int, const char*, const char*, const char*, const char*, const char*); +KHook::Return Detour_CCSPlayer_WeaponServices_CanUse(CCSPlayer_WeaponServices*, CBasePlayerWeapon*); +KHook::Return Detour_CCSPlayer_WeaponServices_EquipWeapon(CCSPlayer_WeaponServices*, CBasePlayerWeapon*); +KHook::Return Detour_CEntityIdentity_AcceptInput(CEntityIdentity* pThis, CUtlSymbolLarge* pInputName, CEntityInstance* pActivator, CEntityInstance* pCaller, variant_t* value, CPulseArgumentPack* pArgumentPack, CPulseInputParamMap* pParamMap); +KHook::Return Detour_CNavMesh_GetNearestNavArea(CNavMesh* pNavMesh, float* unk2, unsigned int* unk3, unsigned int unk4, int64_t unk5, float unk6, int64_t unk7); +KHook::Return Detour_ProcessMovement(CCSPlayer_MovementServices* pThis, void* pMove); +KHook::Return Detour_ProcessMovement_Post(CCSPlayer_MovementServices* pThis, void* pMove); +KHook::Return Detour_ProcessUsercmds(CCSPlayerController* pController, CUserCmd* cmds, int numcmds, bool paused, float margin); +KHook::Return Detour_CGamePlayerEquip_InputTriggerForAllPlayers(CGamePlayerEquip*, InputData_t*); +KHook::Return Detour_CGamePlayerEquip_InputTriggerForActivatedPlayer(CGamePlayerEquip*, InputData_t*); +KHook::Return Detour_CTriggerGravity_GravityTouch(CTriggerGravity* pEntity, CBaseEntity* pOther); +KHook::Return Detour_GetFreeClient(int64_t unk1, const __m128i* unk2, unsigned int unk3, int64_t unk4, char unk5, void* unk6); +KHook::Return Detour_CCSPlayerPawn_GetMaxSpeed(CCSPlayerPawn*); +KHook::Return Detour_FindUseEntity(CCSPlayer_UseServices* pThis, float unk2, int64_t unk3); +KHook::Return Detour_FindUseEntity_Post(CCSPlayer_UseServices* pThis, float unk2, int64_t unk3); +KHook::Return Detour_TraceFunc(int64*, int*, float*, uint64); +KHook::Return Detour_TraceShape(int64*, int64, int64, int64, CTraceFilter*, int64); +KHook::Return Detour_CEntityIOOutput_FireOutputInternal(CEntityIOOutput* pThis, CEntityInstance* pActivator, CEntityInstance* pCaller, CPulseArgumentPack* pArgumentPack, float flDelay, CPulseInputParamMap* pParamMap, const CVariant* value); #ifdef PLATFORM_WINDOWS -Vector* FASTCALL Detour_CBasePlayerPawn_GetEyePosition(CBasePlayerPawn*, Vector*); -QAngle* FASTCALL Detour_CBasePlayerPawn_GetEyeAngles(CBasePlayerPawn*, QAngle*); +KHook::Return Detour_CBasePlayerPawn_GetEyePosition(CBasePlayerPawn*, Vector*); +KHook::Return Detour_CBasePlayerPawn_GetEyeAngles(CBasePlayerPawn*, QAngle*); #else -Vector FASTCALL Detour_CBasePlayerPawn_GetEyePosition(CBasePlayerPawn*); -QAngle FASTCALL Detour_CBasePlayerPawn_GetEyeAngles(CBasePlayerPawn*); +KHook::Return Detour_CBasePlayerPawn_GetEyePosition(CBasePlayerPawn*); +KHook::Return Detour_CBasePlayerPawn_GetEyeAngles(CBasePlayerPawn*); #endif -void FASTCALL Detour_CBaseFilter_InputTestActivator(CBaseEntity* pThis, InputData_t& inputdata); -void FASTCALL Detour_GameSystem_Think_CheckSteamBan(); -AcquireResult FASTCALL Detour_CCSPlayer_ItemServices_CanAcquire(CCSPlayer_ItemServices* pItemServices, CEconItemView* pEconItem, AcquireMethod iAcquireMethod, uint64_t unk4); -void FASTCALL Detour_CS_Script_SetModel(uint64_t unk1); -void FASTCALL Detour_CBaseModelEntity_SetModel(CBaseModelEntity* pModel, const char* pszModel); -void FASTCALL Detour_CCSGameRules_GoToIntermission(CCSGameRules* pThis, bool bAbortedMatch); -void FASTCALL Detour_SetBeamOrigin(CBeam* pThis, const Vector* pVecWorldPosition); -void FASTCALL Detour_SetBeamEndPos(CBeam* pThis, const Vector* pVecWorldPosition); -bool FASTCALL Detour_IsCommandWhitelisted(void* pAddonManager, const char* pszCommandName); \ No newline at end of file +KHook::Return Detour_GameSystem_Think_CheckSteamBan(); +KHook::Return Detour_GameSystem_Think_CheckSteamBan_Post(); +KHook::Return Detour_CCSPlayer_ItemServices_CanAcquire(CCSPlayer_ItemServices* pItemServices, CEconItemView* pEconItem, AcquireMethod iAcquireMethod, uint64_t unk4); +KHook::Return Detour_CS_Script_SetModel(uint64_t unk1); +KHook::Return Detour_CS_Script_SetModel_Post(uint64_t unk1); +KHook::Return Detour_CBaseModelEntity_SetModel(CBaseModelEntity* pModel, const char* pszModel); +KHook::Return Detour_CCSGameRules_GoToIntermission(CCSGameRules* pThis, bool bAbortedMatch); +KHook::Return Detour_SetBeamOrigin(CBeam* pThis, const Vector* pVecWorldPosition); +KHook::Return Detour_SetBeamEndPos(CBeam* pThis, const Vector* pVecWorldPosition); +KHook::Return Detour_IsCommandWhitelisted(void* pAddonManager, const char* pszCommandName); +KHook::Return Detour_CWorldRendererMgr_CreateWorld_Internal_Post(IWorldRendererMgr* pThis, CSingleWorldRep* pSingleWorld); diff --git a/src/entities.cpp b/src/entities.cpp index a9e4f61e8..f38c27494 100644 --- a/src/entities.cpp +++ b/src/entities.cpp @@ -122,7 +122,7 @@ namespace CTriggerGravityHandler { static std::unordered_map s_gravityMap; - void OnPrecache(CBaseEntity* pEntity, const CEntityKeyValues* kv) + void OnPrecache(CTriggerGravity* pEntity, const CEntityKeyValues* kv) { const auto pGravity = kv->GetKeyValue("gravity"); const auto pHammerId = kv->GetKeyValue("hammerUniqueId"); @@ -135,9 +135,9 @@ namespace CTriggerGravityHandler s_gravityMap[hEntity] = flGravity; } - bool GravityTouching(CBaseEntity* pEntity, CBaseEntity* pOther) + bool GravityTouching(CTriggerGravity* pEntity, CBaseEntity* pOther) { - const auto hEntity = GetEntityUnique(pEntity); + const auto hEntity = GetEntityUnique((CBaseEntity*)pEntity); if (hEntity == ENTITY_UNIQUE_INVALID) return false; @@ -151,7 +151,7 @@ namespace CTriggerGravityHandler return true; } - void OnEndTouch(CBaseEntity* pEntity, CBaseEntity* pOther) + void OnEndTouch(CTriggerGravity* pEntity, CBaseEntity* pOther) { if (pOther->IsPawn()) pOther->SetGravityScale(1); diff --git a/src/entities.h b/src/entities.h index 4a107114f..a32771e9f 100644 --- a/src/entities.h +++ b/src/entities.h @@ -26,12 +26,13 @@ class CGameUI; class CPointViewControl; class CCSPlayerPawn; class CEntityKeyValues; +class CTriggerGravity; namespace CTriggerGravityHandler { - void OnPrecache(CBaseEntity* pEntity, const CEntityKeyValues* kv); - bool GravityTouching(CBaseEntity* pEntity, CBaseEntity* pOther); - void OnEndTouch(CBaseEntity* pEntity, CBaseEntity* pOther); + void OnPrecache(CTriggerGravity* pEntity, const CEntityKeyValues* kv); + bool GravityTouching(CTriggerGravity* pEntity, CBaseEntity* pOther); + void OnEndTouch(CTriggerGravity* pEntity, CBaseEntity* pOther); } // namespace CTriggerGravityHandler namespace CGamePlayerEquipHandler diff --git a/src/entitylistener.cpp b/src/entitylistener.cpp index 72e6fd611..ab5d84a0e 100644 --- a/src/entitylistener.cpp +++ b/src/entitylistener.cpp @@ -20,13 +20,13 @@ #include "entitylistener.h" #include "common.h" #include "cs2_sdk/entity/cbaseentity.h" +#include "cs2_sdk/entity/customhudlayout.h" #include "cs2fixes.h" #include "entities.h" #include "entity/cgamerules.h" #include "entityclass.h" #include "entwatch.h" #include "gameconfig.h" -#include "mapmigrations.h" #include "plat.h" CEntityListener* g_pEntityListener = nullptr; @@ -44,7 +44,6 @@ void CEntityListener::OnEntitySpawned(CEntityInstance* pEntity) reinterpret_cast(pEntity)->SetCollisionGroup(COLLISION_GROUP_DEBRIS); EntityHandler_OnEntitySpawned(reinterpret_cast(pEntity)); - g_pMapMigrations->OnEntitySpawned_Post(reinterpret_cast(pEntity)); if (g_cvarEnableEntWatch.Get()) EW_OnEntitySpawned(pEntity); @@ -55,12 +54,15 @@ void CEntityListener::OnEntityCreated(CEntityInstance* pEntity) ExecuteOnce(addresses::UTIL_Remove = pEntity->m_pEntity->m_pClass->m_NameToThinkFunc("CBaseEntitySUB_Remove")); if (!V_strcmp("cs_gamerules", pEntity->GetClassname())) - g_pGameRules = ((CCSGameRulesProxy*)pEntity)->m_pGameRules; + g_pGameRules = reinterpret_cast(pEntity)->m_pGameRules; } void CEntityListener::OnEntityDeleted(CEntityInstance* pEntity) { EW_OnEntityDeleted(pEntity); + + if (!V_strcmp("custom_hud_layout", pEntity->GetClassname())) + reinterpret_cast(pEntity)->OnEntityDeleted(); } void CEntityListener::OnEntityParentChanged(CEntityInstance* pEntity, CEntityInstance* pNewParent) diff --git a/src/entwatch.cpp b/src/entwatch.cpp index d5be7ca27..6b214016a 100644 --- a/src/entwatch.cpp +++ b/src/entwatch.cpp @@ -53,27 +53,18 @@ CEWHandler* g_pEWHandler = nullptr; -SH_DECL_MANUALHOOK1_void(CBaseButton_Use, 0, 0, 0, InputData_t*); -SH_DECL_MANUALHOOK1_void(CPhysBox_Use, 0, 0, 0, InputData_t*); -SH_DECL_MANUALHOOK1_void(CRotButton_Use, 0, 0, 0, InputData_t*); -SH_DECL_MANUALHOOK1_void(CMomentaryRotButton_Use, 0, 0, 0, InputData_t*); -SH_DECL_MANUALHOOK1_void(CPhysicalButton_Use, 0, 0, 0, InputData_t*); - -SH_DECL_MANUALHOOK1_void(CTriggerTeleport_StartTouch, 0, 0, 0, CBaseEntity*); -SH_DECL_MANUALHOOK1_void(CTriggerTeleport_Touch, 0, 0, 0, CBaseEntity*); -SH_DECL_MANUALHOOK1_void(CTriggerTeleport_EndTouch, 0, 0, 0, CBaseEntity*); -SH_DECL_MANUALHOOK1_void(CTriggerOnce_StartTouch, 0, 0, 0, CBaseEntity*); -SH_DECL_MANUALHOOK1_void(CTriggerOnce_Touch, 0, 0, 0, CBaseEntity*); -SH_DECL_MANUALHOOK1_void(CTriggerOnce_EndTouch, 0, 0, 0, CBaseEntity*); -SH_DECL_MANUALHOOK1_void(CTriggerMultiple_StartTouch, 0, 0, 0, CBaseEntity*); -SH_DECL_MANUALHOOK1_void(CTriggerMultiple_Touch, 0, 0, 0, CBaseEntity*); -SH_DECL_MANUALHOOK1_void(CTriggerMultiple_EndTouch, 0, 0, 0, CBaseEntity*); +CBaseEntity* g_pCBaseButtonVTable = nullptr; +CBaseEntity* g_pCPhysBoxVTable = nullptr; +CBaseEntity* g_pCRotButtonVTable = nullptr; +CBaseEntity* g_pCMomentaryRotButtonVTable = nullptr; +CBaseEntity* g_pCPhysicalButtonVTable = nullptr; +CBaseEntity* g_pCTriggerTeleportVTable = nullptr; +CBaseEntity* g_pCTriggerOnceVTable = nullptr; +CBaseEntity* g_pCTriggerMultipleVTable = nullptr; CConVar g_cvarEnableEntWatch("entwatch_enable", FCVAR_NONE, "INCOMPATIBLE WITH CS#. Whether to enable EntWatch features", false); CConVar g_cvarEnableFiltering("entwatch_auto_filter", FCVAR_NONE, "Whether to automatically block non-item holders from triggering uses", true); CConVar g_cvarUseEntwatchClantag("entwatch_clantag", FCVAR_NONE, "Whether to set item holder's clantag and set score", true); -CConVar g_cvarClantagColourMode("entwatch_clantag_colour_mode", FCVAR_NONE, "Whether to set item clantag colours (0=No colour, 1=Use item colour, 2=Use entwatch_clantag_colour value)", 1, true, 0, true, 2); -CConVar g_cvarClantagColour("entwatch_clantag_colour", FCVAR_NONE, "Hex colour (RRGGBB) to use if entwatch_clantag_colour_mode 2", "FFFFFF"); CConVar g_cvarItemHolderScore("entwatch_score", FCVAR_NONE, "Score to give item holders (0 = dont change score at all) Requires entwatch_clantag 1", 9999, true, 0, false, 0); CConVar g_cvarEnableEntwatchHud("entwatch_hud", FCVAR_NONE, "Whether to enable the EntWatch hud and related commands", true); @@ -235,7 +226,7 @@ void EWItemHandler::RegisterEntity(CBaseEntity* pEntity) switch (type) { case Button: - g_pEWHandler->AddUseHook(pEntity); + g_pEWHandler->vecUseHookedEntities.push_back(pEntity->GetHandle()); break; case CounterDown: case CounterUp: @@ -707,7 +698,7 @@ bool EWItemInstance::RemoveHandler(CBaseEntity* pEnt) if (vecHandlers[i]->iEntIndex == pEnt->entindex()) { if (vecHandlers[i]->type == EWHandlerType::Button) - g_pEWHandler->RemoveUseHook(pEnt); + g_pEWHandler->RemoveUseEntity(pEnt); vecHandlers[i]->iEntIndex = -1; return true; } @@ -1055,12 +1046,9 @@ bool EWItemInstance::IsEmpty() void CEWHandler::UnLoadConfig() { - if (!bConfigLoaded) + if (!m_bConfigLoaded) return; - if (EW_IsFireOutputHooked()) - mapIOFunctions.erase("entwatch"); - // Clantags first so scores can be set back properly ResetAllClantags(); @@ -1071,10 +1059,10 @@ void CEWHandler::UnLoadConfig() vecItems[i]->vecHandlers[j]->RemoveHook(); vecItems.clear(); - RemoveAllUseHooks(); - RemoveAllTriggers(); + vecUseHookedEntities.clear(); + vecHookedTriggers.clear(); - bConfigLoaded = false; + m_bConfigLoaded = false; } /* @@ -1129,21 +1117,12 @@ void CEWHandler::LoadConfig(const char* sFilePath) mapItemConfig[hash_32_fnv1a_const(sHammerid.c_str())] = item; } - if (mapItemConfig.size() > 0) - { - // Hook FireOutput - if (!SetupFireOutputInternalDetour()) - mapIOFunctions.erase("entwatch"); - else if (!EW_IsFireOutputHooked()) - mapIOFunctions["entwatch"] = EW_FireOutput; - } - - bConfigLoaded = true; + m_bConfigLoaded = true; } void CEWHandler::PrintLoadedConfig(CCSPlayerController* pController) { - if (!bConfigLoaded) + if (!m_bConfigLoaded) { ClientPrint(pController, HUD_PRINTTALK, EW_PREFIX "No config loaded."); return; @@ -1322,7 +1301,7 @@ bool CEWHandler::RegisterTrigger(CBaseEntity* pEnt) if (strcmp(sHammerid, pItem->vecTriggers[j].c_str())) continue; - AddTouchHook(pEnt); + vecHookedTriggers.push_back(pEnt->GetHandle()); return true; } @@ -1330,74 +1309,116 @@ bool CEWHandler::RegisterTrigger(CBaseEntity* pEnt) return false; } -void CEWHandler::AddTouchHook(CBaseEntity* pEnt) +void CEWHandler::CreateHooks() { - static int startOffset = g_GameConfig->GetOffset("CBaseEntity::StartTouch"); - static int touchOffset = g_GameConfig->GetOffset("CBaseEntity::Touch"); - static int endOffset = g_GameConfig->GetOffset("CBaseEntity::EndTouch"); + g_pCBaseButtonVTable = (CBaseEntity*)modules::server->FindVirtualTable("CBaseButton"); + g_pCPhysBoxVTable = (CBaseEntity*)modules::server->FindVirtualTable("CPhysBox"); + g_pCRotButtonVTable = (CBaseEntity*)modules::server->FindVirtualTable("CRotButton"); + g_pCMomentaryRotButtonVTable = (CBaseEntity*)modules::server->FindVirtualTable("CMomentaryRotButton"); + g_pCPhysicalButtonVTable = (CBaseEntity*)modules::server->FindVirtualTable("CPhysicalButton"); + g_pCTriggerTeleportVTable = (CBaseEntity*)modules::server->FindVirtualTable("CTriggerTeleport"); + g_pCTriggerOnceVTable = (CBaseEntity*)modules::server->FindVirtualTable("CTriggerOnce"); + g_pCTriggerMultipleVTable = (CBaseEntity*)modules::server->FindVirtualTable("CTriggerMultiple"); - if (!V_strcmp(pEnt->GetClassname(), "trigger_teleport")) + int offset = g_GameConfig->GetOffset("CBaseEntity::Use"); + + if (offset == -1) { - if (iTriggerTeleportTouchHooks[0] == -1) - { - const auto pTeleport_VTable = modules::server->FindVirtualTable("CTriggerTeleport"); + Panic("Failed to find CBaseEntity::Use offset\n"); + return; + } - SH_MANUALHOOK_RECONFIGURE(CTriggerTeleport_StartTouch, startOffset, 0, 0); - iTriggerTeleportTouchHooks[0] = SH_ADD_MANUALDVPHOOK(CTriggerTeleport_StartTouch, pTeleport_VTable, SH_MEMBER(this, &CEWHandler::Hook_Touch), false); + m_hBaseButtonUse.Configure(offset); + m_hBaseButtonUse.AddGlobal((CBaseEntity*)&g_pCBaseButtonVTable); - SH_MANUALHOOK_RECONFIGURE(CTriggerTeleport_Touch, touchOffset, 0, 0); - iTriggerTeleportTouchHooks[1] = SH_ADD_MANUALDVPHOOK(CTriggerTeleport_Touch, pTeleport_VTable, SH_MEMBER(this, &CEWHandler::Hook_Touch), false); + m_hPhysBoxUse.Configure(offset); + m_hPhysBoxUse.AddGlobal((CBaseEntity*)&g_pCPhysBoxVTable); - SH_MANUALHOOK_RECONFIGURE(CTriggerTeleport_EndTouch, endOffset, 0, 0); - iTriggerTeleportTouchHooks[2] = SH_ADD_MANUALDVPHOOK(CTriggerTeleport_EndTouch, pTeleport_VTable, SH_MEMBER(this, &CEWHandler::Hook_Touch), false); - } - } - else if (!V_strcmp(pEnt->GetClassname(), "trigger_multiple")) - { - if (iTriggerMultipleTouchHooks[0] == -1) - { - const auto pMultiple_VTable = modules::server->FindVirtualTable("CTriggerMultiple"); + m_hRotButtonUse.Configure(offset); + m_hRotButtonUse.AddGlobal((CBaseEntity*)&g_pCRotButtonVTable); - SH_MANUALHOOK_RECONFIGURE(CTriggerMultiple_StartTouch, startOffset, 0, 0); - iTriggerMultipleTouchHooks[0] = SH_ADD_MANUALDVPHOOK(CTriggerMultiple_StartTouch, pMultiple_VTable, SH_MEMBER(this, &CEWHandler::Hook_Touch), false); + m_hMomentaryRotButtonUse.Configure(offset); + m_hMomentaryRotButtonUse.AddGlobal((CBaseEntity*)&g_pCMomentaryRotButtonVTable); - SH_MANUALHOOK_RECONFIGURE(CTriggerMultiple_Touch, touchOffset, 0, 0); - iTriggerMultipleTouchHooks[1] = SH_ADD_MANUALDVPHOOK(CTriggerMultiple_Touch, pMultiple_VTable, SH_MEMBER(this, &CEWHandler::Hook_Touch), false); + m_hPhysicalButtonUse.Configure(offset); + m_hPhysicalButtonUse.AddGlobal((CBaseEntity*)&g_pCPhysicalButtonVTable); - SH_MANUALHOOK_RECONFIGURE(CTriggerMultiple_EndTouch, endOffset, 0, 0); - iTriggerMultipleTouchHooks[2] = SH_ADD_MANUALDVPHOOK(CTriggerMultiple_EndTouch, pMultiple_VTable, SH_MEMBER(this, &CEWHandler::Hook_Touch), false); - } + offset = g_GameConfig->GetOffset("CBaseEntity::StartTouch"); + + if (offset == -1) + { + Panic("Failed to find CBaseEntity::StartTouch offset\n"); + return; } - else if (!V_strcmp(pEnt->GetClassname(), "trigger_once")) + + m_hTriggerTeleportStartTouch.Configure(offset); + m_hTriggerTeleportStartTouch.AddGlobal((CBaseEntity*)&g_pCTriggerTeleportVTable); + + m_hTriggerOnceStartTouch.Configure(offset); + m_hTriggerOnceStartTouch.AddGlobal((CBaseEntity*)&g_pCTriggerOnceVTable); + + m_hTriggerMultipleStartTouch.Configure(offset); + m_hTriggerMultipleStartTouch.AddGlobal((CBaseEntity*)&g_pCTriggerMultipleVTable); + + offset = g_GameConfig->GetOffset("CBaseEntity::Touch"); + + if (offset == -1) { - if (iTriggerOnceTouchHooks[0] == -1) - { - const auto pOnce_VTable = modules::server->FindVirtualTable("CTriggerOnce"); + Panic("Failed to find CBaseEntity::Touch offset\n"); + return; + } - SH_MANUALHOOK_RECONFIGURE(CTriggerOnce_StartTouch, startOffset, 0, 0); - iTriggerOnceTouchHooks[0] = SH_ADD_MANUALDVPHOOK(CTriggerOnce_StartTouch, pOnce_VTable, SH_MEMBER(this, &CEWHandler::Hook_Touch), false); + m_hTriggerTeleportTouch.Configure(offset); + m_hTriggerTeleportTouch.AddGlobal((CBaseEntity*)&g_pCTriggerTeleportVTable); - SH_MANUALHOOK_RECONFIGURE(CTriggerOnce_Touch, touchOffset, 0, 0); - iTriggerOnceTouchHooks[1] = SH_ADD_MANUALDVPHOOK(CTriggerOnce_Touch, pOnce_VTable, SH_MEMBER(this, &CEWHandler::Hook_Touch), false); + m_hTriggerOnceTouch.Configure(offset); + m_hTriggerOnceTouch.AddGlobal((CBaseEntity*)&g_pCTriggerOnceVTable); - SH_MANUALHOOK_RECONFIGURE(CTriggerOnce_EndTouch, endOffset, 0, 0); - iTriggerOnceTouchHooks[2] = SH_ADD_MANUALDVPHOOK(CTriggerOnce_EndTouch, pOnce_VTable, SH_MEMBER(this, &CEWHandler::Hook_Touch), false); - } + m_hTriggerMultipleTouch.Configure(offset); + m_hTriggerMultipleTouch.AddGlobal((CBaseEntity*)&g_pCTriggerMultipleVTable); + + offset = g_GameConfig->GetOffset("CBaseEntity::EndTouch"); + + if (offset == -1) + { + Panic("Failed to find CBaseEntity::EndTouch offset\n"); + return; } - vecHookedTriggers.push_back(pEnt->GetHandle()); + m_hTriggerTeleportEndTouch.Configure(offset); + m_hTriggerTeleportEndTouch.AddGlobal((CBaseEntity*)&g_pCTriggerTeleportVTable); + + m_hTriggerOnceEndTouch.Configure(offset); + m_hTriggerOnceEndTouch.AddGlobal((CBaseEntity*)&g_pCTriggerOnceVTable); + + m_hTriggerMultipleEndTouch.Configure(offset); + m_hTriggerMultipleEndTouch.AddGlobal((CBaseEntity*)&g_pCTriggerMultipleVTable); } -void CEWHandler::Hook_Touch(CBaseEntity* pOther) +void CEWHandler::RemoveHooks() { - CBaseEntity* pEntity = META_IFACEPTR(CBaseEntity); - if (!pEntity) - RETURN_META(MRES_IGNORED); + m_hBaseButtonUse.RemoveGlobal((CBaseEntity*)&g_pCBaseButtonVTable); + m_hPhysBoxUse.RemoveGlobal((CBaseEntity*)&g_pCPhysBoxVTable); + m_hRotButtonUse.RemoveGlobal((CBaseEntity*)&g_pCRotButtonVTable); + m_hMomentaryRotButtonUse.RemoveGlobal((CBaseEntity*)&g_pCMomentaryRotButtonVTable); + m_hPhysicalButtonUse.RemoveGlobal((CBaseEntity*)&g_pCPhysicalButtonVTable); + m_hTriggerTeleportStartTouch.RemoveGlobal((CBaseEntity*)&g_pCTriggerTeleportVTable); + m_hTriggerOnceStartTouch.RemoveGlobal((CBaseEntity*)&g_pCTriggerOnceVTable); + m_hTriggerMultipleStartTouch.RemoveGlobal((CBaseEntity*)&g_pCTriggerMultipleVTable); + m_hTriggerTeleportTouch.RemoveGlobal((CBaseEntity*)&g_pCTriggerTeleportVTable); + m_hTriggerOnceTouch.RemoveGlobal((CBaseEntity*)&g_pCTriggerOnceVTable); + m_hTriggerMultipleTouch.RemoveGlobal((CBaseEntity*)&g_pCTriggerMultipleVTable); + m_hTriggerTeleportEndTouch.RemoveGlobal((CBaseEntity*)&g_pCTriggerTeleportVTable); + m_hTriggerOnceEndTouch.RemoveGlobal((CBaseEntity*)&g_pCTriggerOnceVTable); + m_hTriggerMultipleEndTouch.RemoveGlobal((CBaseEntity*)&g_pCTriggerMultipleVTable); +} +KHook::Return CEWHandler::Hook_Touch(CBaseEntity* pThis, CBaseEntity* pOther) +{ bool bFound = false; for (int i = 0; i < (vecHookedTriggers).size(); i++) { - if (vecHookedTriggers[i] == pEntity->GetHandle()) + if (vecHookedTriggers[i] == pThis->GetHandle()) { bFound = true; break; @@ -1405,24 +1426,24 @@ void CEWHandler::Hook_Touch(CBaseEntity* pOther) } if (!bFound) - RETURN_META(MRES_IGNORED); + return {KHook::Action::Ignore}; CCSPlayerPawn* pPawn = (CCSPlayerPawn*)pOther; if (!pPawn) - RETURN_META(MRES_IGNORED); + return {KHook::Action::Ignore}; CCSPlayerController* pController = pPawn->GetOriginalController(); if (!pController) - RETURN_META(MRES_IGNORED); + return {KHook::Action::Ignore}; ZEPlayer* zpPlayer = pController->GetZEPlayer(); if (!zpPlayer) - RETURN_META(MRES_IGNORED); + return {KHook::Action::Ignore}; if (zpPlayer->IsEbanned()) - RETURN_META(MRES_SUPERCEDE); + return {KHook::Action::Supersede}; - RETURN_META(MRES_IGNORED); + return {KHook::Action::Ignore}; } bool CEWHandler::RemoveTrigger(CBaseEntity* pEnt) @@ -1439,23 +1460,6 @@ bool CEWHandler::RemoveTrigger(CBaseEntity* pEnt) return false; } -void CEWHandler::RemoveAllTriggers() -{ - Message("[EntWatch] Fully unhooking touch hooks\n"); - for (int i = 0; i < 3; i++) - { - SH_REMOVE_HOOK_ID(iTriggerTeleportTouchHooks[i]); - iTriggerTeleportTouchHooks[i] = -1; - - SH_REMOVE_HOOK_ID(iTriggerMultipleTouchHooks[i]); - iTriggerMultipleTouchHooks[i] = -1; - - SH_REMOVE_HOOK_ID(iTriggerOnceTouchHooks[i]); - iTriggerOnceTouchHooks[i] = -1; - } - vecHookedTriggers.clear(); -} - void CEWHandler::RemoveHandler(CBaseEntity* pEnt) { for (int i = 0; i < (vecItems).size(); i++) @@ -1798,65 +1802,7 @@ void CEWHandler::Transfer(CCSPlayerController* pCaller, int iItemInstance, CHand } } -void CEWHandler::AddUseHook(CBaseEntity* pEnt) -{ - static int offset = g_GameConfig->GetOffset("CBaseEntity::Use"); - if (offset == -1) - { - Panic("Failed to find CBaseEntity::Use offset in entwatch.cpp::AddUseHook() !!\n"); - return; - } - - if (!V_strcmp(pEnt->GetClassname(), "func_button")) - { - if (iBaseBtnUseHookId == -1) - { - const auto pBaseButton_VTable = modules::server->FindVirtualTable("CBaseButton"); - SH_MANUALHOOK_RECONFIGURE(CBaseButton_Use, offset, 0, 0); - iBaseBtnUseHookId = SH_ADD_MANUALDVPHOOK(CBaseButton_Use, pBaseButton_VTable, SH_MEMBER(this, &CEWHandler::Hook_Use), false); - } - } - else if (!V_strcmp(pEnt->GetClassname(), "func_physbox")) - { - if (iPhysboxUseHookId == -1) - { - const auto pPhysbox_VTable = modules::server->FindVirtualTable("CPhysBox"); - SH_MANUALHOOK_RECONFIGURE(CPhysBox_Use, offset, 0, 0); - iPhysboxUseHookId = SH_ADD_MANUALDVPHOOK(CPhysBox_Use, pPhysbox_VTable, SH_MEMBER(this, &CEWHandler::Hook_Use), false); - } - } - else if (!V_strcmp(pEnt->GetClassname(), "func_rot_button")) - { - if (iRotBtnUseHookId == -1) - { - const auto pCRotButton_VTable = modules::server->FindVirtualTable("CRotButton"); - SH_MANUALHOOK_RECONFIGURE(CRotButton_Use, offset, 0, 0); - iRotBtnUseHookId = SH_ADD_MANUALDVPHOOK(CRotButton_Use, pCRotButton_VTable, SH_MEMBER(this, &CEWHandler::Hook_Use), false); - } - } - else if (!V_strcmp(pEnt->GetClassname(), "momentary_rot_button")) - { - if (iMomRotBtnUseHookId == -1) - { - const auto pCMomentaryRotButton_VTable = modules::server->FindVirtualTable("CMomentaryRotButton"); - SH_MANUALHOOK_RECONFIGURE(CMomentaryRotButton_Use, offset, 0, 0); - iMomRotBtnUseHookId = SH_ADD_MANUALDVPHOOK(CMomentaryRotButton_Use, pCMomentaryRotButton_VTable, SH_MEMBER(this, &CEWHandler::Hook_Use), false); - } - } - else if (!V_strcmp(pEnt->GetClassname(), "func_physical_button")) - { - if (iPhysicalBtnUseHookId == -1) - { - const auto pCPhysicalButton_VTable = modules::server->FindVirtualTable("CPhysicalButton"); - SH_MANUALHOOK_RECONFIGURE(CPhysicalButton_Use, offset, 0, 0); - iPhysicalBtnUseHookId = SH_ADD_MANUALDVPHOOK(CPhysicalButton_Use, pCPhysicalButton_VTable, SH_MEMBER(this, &CEWHandler::Hook_Use), false); - } - } - - vecUseHookedEntities.push_back(pEnt->GetHandle()); -} - -void CEWHandler::RemoveUseHook(CBaseEntity* pEnt) +void CEWHandler::RemoveUseEntity(CBaseEntity* pEnt) { for (int i = 0; i < (vecUseHookedEntities).size(); i++) { @@ -1868,39 +1814,13 @@ void CEWHandler::RemoveUseHook(CBaseEntity* pEnt) } } -void CEWHandler::RemoveAllUseHooks() -{ - Message("[EntWatch] Fully unhooking use hooks\n"); - - SH_REMOVE_HOOK_ID(iBaseBtnUseHookId); - iBaseBtnUseHookId = -1; - - SH_REMOVE_HOOK_ID(iPhysboxUseHookId); - iPhysboxUseHookId = -1; - - SH_REMOVE_HOOK_ID(iRotBtnUseHookId); - iRotBtnUseHookId = -1; - - SH_REMOVE_HOOK_ID(iMomRotBtnUseHookId); - iMomRotBtnUseHookId = -1; - - SH_REMOVE_HOOK_ID(iPhysicalBtnUseHookId); - iPhysicalBtnUseHookId = -1; - - vecUseHookedEntities.clear(); -} - -void CEWHandler::Hook_Use(InputData_t* pInput) +KHook::Return CEWHandler::Hook_Use(CBaseEntity* pThis, InputData_t* pInput) { // Message("##### USE HOOK #####\n"); - CBaseEntity* pEntity = META_IFACEPTR(CBaseEntity); - if (!pEntity) - RETURN_META(MRES_IGNORED); - bool bFound = false; for (int i = 0; i < (vecUseHookedEntities).size(); i++) { - if (vecUseHookedEntities[i] == pEntity->GetHandle()) + if (vecUseHookedEntities[i] == pThis->GetHandle()) { bFound = true; break; @@ -1908,9 +1828,9 @@ void CEWHandler::Hook_Use(InputData_t* pInput) } if (!bFound) - RETURN_META(MRES_IGNORED); + return {KHook::Action::Ignore}; - int index = pEntity->entindex(); + int index = pThis->entindex(); int itemIndex = -1; int handlerIndex = -1; for (int i = 0; i < (vecItems).size(); i++) @@ -1925,31 +1845,31 @@ void CEWHandler::Hook_Use(InputData_t* pInput) } if (itemIndex == -1 || handlerIndex == -1) - RETURN_META(MRES_IGNORED); + return {KHook::Action::Ignore}; // Prevent uses from non item owners if we are filtering - META_RES resVal = MRES_IGNORED; + KHook::Return returnVal = {KHook::Action::Ignore}; if (g_cvarEnableFiltering.Get()) - resVal = MRES_SUPERCEDE; + returnVal = {KHook::Action::Supersede}; CBaseEntity* pActivator = pInput->pActivator; if (!pActivator || !pActivator->IsPawn()) - RETURN_META(resVal); + return returnVal; std::shared_ptr pItem = vecItems[itemIndex]; CCSPlayerPawn* pPawn = (CCSPlayerPawn*)pActivator; CCSPlayerController* pController = pPawn->GetOriginalController(); if (!pController || pController->GetPlayerSlot() != pItem->iOwnerSlot) - RETURN_META(resVal); + return returnVal; // // WE SHOW USE MESSAGE IN FireOutput // This is just to prevent unnecessary stuff with buttons like movement // - RETURN_META(MRES_IGNORED); + return {KHook::Action::Ignore}; } // Update cd and uses of all held items @@ -1983,21 +1903,7 @@ float EW_UpdateHud() if (g_cvarUseEntwatchClantag.Get()) { - switch (g_cvarClantagColourMode.Get()) - { - case 0: - V_snprintf(pItem->sClantag, sizeof(EWItemInstance::sClantag), "[%s]%s:", - sItemText.c_str(), pItem->szShortName.c_str()); - break; - case 1: - V_snprintf(pItem->sClantag, sizeof(EWItemInstance::sClantag), "[%s]%s", - pItem->colorGlow.r(), pItem->colorGlow.g(), pItem->colorGlow.b(), sItemText.c_str(), pItem->szShortName.c_str()); - break; - case 2: - V_snprintf(pItem->sClantag, sizeof(EWItemInstance::sClantag), "[%s]%s", - g_cvarClantagColour.Get().String(), sItemText.c_str(), pItem->szShortName.c_str()); - break; - } + V_snprintf(pItem->sClantag, sizeof(EWItemInstance::sClantag), "%s: %s", sItemText.c_str(), pItem->szShortName.c_str()); if (pItem->bHasThisClantag) pOwner->SetClanTag(pItem->sClantag); } @@ -2085,7 +1991,7 @@ void EW_RoundPreStart() void EW_OnEntitySpawned(CEntityInstance* pEntity) { - if (!g_pEWHandler || !g_pEWHandler->bConfigLoaded) + if (!g_pEWHandler || !g_pEWHandler->m_bConfigLoaded) return; CBaseEntity* pEnt = (CBaseEntity*)pEntity; @@ -2131,7 +2037,7 @@ void EW_OnEntitySpawned(CEntityInstance* pEntity) void EW_OnEntityDeleted(CEntityInstance* pEntity) { - if (!g_pEWHandler || !g_pEWHandler->bConfigLoaded) + if (!g_pEWHandler || !g_pEWHandler->m_bConfigLoaded) return; CBaseEntity* pEnt = (CBaseEntity*)pEntity; @@ -2166,7 +2072,7 @@ void EW_OnWeaponDeleted(CBaseEntity* pEntity) bool EW_Detour_CCSPlayer_WeaponServices_CanUse(CCSPlayer_WeaponServices* pWeaponServices, CBasePlayerWeapon* pPlayerWeapon) { // false=block it, true=dont block it - if (!g_pEWHandler || !g_pEWHandler->bConfigLoaded) + if (!g_pEWHandler || !g_pEWHandler->m_bConfigLoaded) return true; CCSPlayerPawn* pPawn = pWeaponServices->GetPawn(); @@ -2210,7 +2116,7 @@ bool EW_Detour_CCSPlayer_WeaponServices_CanUse(CCSPlayer_WeaponServices* pWeapon void EW_Detour_CCSPlayer_WeaponServices_EquipWeapon(CCSPlayer_WeaponServices* pWeaponServices, CBasePlayerWeapon* pWeapon) { - if (!g_pEWHandler || !g_pEWHandler->bConfigLoaded) + if (!g_pEWHandler || !g_pEWHandler->m_bConfigLoaded) return; CCSPlayerPawn* pPawn = pWeaponServices->GetPawn(); @@ -2230,7 +2136,7 @@ void EW_Detour_CCSPlayer_WeaponServices_EquipWeapon(CCSPlayer_WeaponServices* pW void EW_DropWeapon(CCSPlayer_WeaponServices* pWeaponServices, CBasePlayerWeapon* pWeapon) { - if (!g_pEWHandler || !g_pEWHandler->bConfigLoaded) + if (!g_pEWHandler || !g_pEWHandler->m_bConfigLoaded) return; CCSPlayerPawn* pPawn = pWeaponServices->GetPawn(); @@ -2317,14 +2223,9 @@ void EW_PlayerDisconnect(int slot) g_pEWHandler->PlayerDrop(EWDropReason::Disconnect, -1, pController); } -bool EW_IsFireOutputHooked() -{ - return std::any_of(mapIOFunctions.begin(), mapIOFunctions.end(), [](const auto& p) { return p.first == "entwatch"; }); -} - -void EW_FireOutput(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEntityInstance* pCaller, const CVariant* value, float flDelay) +void EW_FireOutput(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEntityInstance* pCaller, float flDelay) { - if (!EW_IsFireOutputHooked() || !pCaller) + if (!g_pEWHandler->IsConfigLoaded() || !pCaller) return; for (int i = 0; i < (g_pEWHandler->vecItems).size(); i++) @@ -2347,7 +2248,7 @@ void EW_FireOutput(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CE // Message("Output for item %s (instance:%d) handler:%d outputname:%s\n", g_pEWHandler->vecItems[i]->szItemName, i, j, pThis->m_pDesc->m_pName); if (handler->type == EWHandlerType::CounterDown || handler->type == EWHandlerType::CounterUp) - handler->Use(value->m_float32); + handler->Use(((CMathCounter*)pCaller)->GetCounterValue()); else handler->Use(0.0); } @@ -2399,7 +2300,7 @@ CON_COMMAND_CHAT_FLAGS(ew_reload, "- Reloads the current map's entwatch config", // LoadConfig unloads the current config already g_pEWHandler->LoadMapConfig(GetGlobals()->mapname.ToCStr()); - if (!g_pEWHandler->bConfigLoaded) + if (!g_pEWHandler->m_bConfigLoaded) { ClientPrint(player, HUD_PRINTTALK, EW_PREFIX "Error reloading config, check console log for details."); return; diff --git a/src/entwatch.h b/src/entwatch.h index 32ce33a30..1526bb2fb 100644 --- a/src/entwatch.h +++ b/src/entwatch.h @@ -23,6 +23,7 @@ #include "ctimer.h" #include "eventlistener.h" #include "gamesystem.h" +#include "khook.hpp" #include "vendor/nlohmann/json_fwd.hpp" using ordered_json = nlohmann::ordered_json; @@ -215,26 +216,35 @@ struct EActiveTransfer class CEWHandler { public: - CEWHandler() + CEWHandler() : + m_bConfigLoaded(false), + m_hBaseButtonUse(0U, this, &CEWHandler::Hook_Use, nullptr), + m_hPhysBoxUse(0U, this, &CEWHandler::Hook_Use, nullptr), + m_hRotButtonUse(0U, this, &CEWHandler::Hook_Use, nullptr), + m_hMomentaryRotButtonUse(0U, this, &CEWHandler::Hook_Use, nullptr), + m_hPhysicalButtonUse(0U, this, &CEWHandler::Hook_Use, nullptr), + m_hTriggerTeleportStartTouch(0U, this, &CEWHandler::Hook_Touch, nullptr), + m_hTriggerOnceStartTouch(0U, this, &CEWHandler::Hook_Touch, nullptr), + m_hTriggerMultipleStartTouch(0U, this, &CEWHandler::Hook_Touch, nullptr), + m_hTriggerTeleportTouch(0U, this, &CEWHandler::Hook_Touch, nullptr), + m_hTriggerOnceTouch(0U, this, &CEWHandler::Hook_Touch, nullptr), + m_hTriggerMultipleTouch(0U, this, &CEWHandler::Hook_Touch, nullptr), + m_hTriggerTeleportEndTouch(0U, this, &CEWHandler::Hook_Touch, nullptr), + m_hTriggerOnceEndTouch(0U, this, &CEWHandler::Hook_Touch, nullptr), + m_hTriggerMultipleEndTouch(0U, this, &CEWHandler::Hook_Touch, nullptr) { - bConfigLoaded = false; - - iBaseBtnUseHookId = -1; - iPhysboxUseHookId = -1; - iPhysicalBtnUseHookId = -1; - iRotBtnUseHookId = -1; - iMomRotBtnUseHookId = -1; - - for (int i = 0; i < 3; i++) - { - iTriggerTeleportTouchHooks[i] = -1; - iTriggerMultipleTouchHooks[i] = -1; - iTriggerOnceTouchHooks[i] = -1; - } + CreateHooks(); } - bool bConfigLoaded; - bool IsConfigLoaded() { return bConfigLoaded; } + ~CEWHandler() + { + RemoveHooks(); + } + + void CreateHooks(); + void RemoveHooks(); + + bool IsConfigLoaded() { return m_bConfigLoaded; } void UnLoadConfig(); void LoadMapConfig(const char* sMapName); @@ -250,10 +260,8 @@ class CEWHandler void RegisterHandler(CBaseEntity* pEnt); bool RegisterTrigger(CBaseEntity* pEnt); - void AddTouchHook(CBaseEntity* pEnt); - void Hook_Touch(CBaseEntity* pOther); + KHook::Return Hook_Touch(CBaseEntity* pThis, CBaseEntity* pOther); bool RemoveTrigger(CBaseEntity* pEnt); - void RemoveAllTriggers(); void RemoveHandler(CBaseEntity* pEnt); void ResetAllClantags(); @@ -263,30 +271,35 @@ class CEWHandler void PlayerDrop(EWDropReason reason, int iItemInstance, CCSPlayerController* pController); void Transfer(CCSPlayerController* pCaller, int iItemInstance, CHandle hReceiver); - void AddUseHook(CBaseEntity* pEnt); - void RemoveUseHook(CBaseEntity* pEnt); - void RemoveAllUseHooks(); - void Hook_Use(InputData_t* pInput); + void RemoveUseEntity(CBaseEntity* pEnt); + KHook::Return Hook_Use(CBaseEntity* pThis, InputData_t* pInput); std::map> mapItemConfig; /* items defined in the config */ std::vector> vecItems; /* all items found spawned */ std::vector> vecHookedTriggers; - int iTriggerTeleportTouchHooks[3]; - int iTriggerMultipleTouchHooks[3]; - int iTriggerOnceTouchHooks[3]; - std::vector> vecUseHookedEntities; - int iBaseBtnUseHookId; - int iPhysboxUseHookId; - int iPhysicalBtnUseHookId; - int iRotBtnUseHookId; - int iMomRotBtnUseHookId; std::weak_ptr m_pHudTimer; + bool m_bConfigLoaded; std::map> mapTransfers; // Any etransfers that target multiple items std::vector> vecActiveTransfers; // Active transfers where only the receiver can pickup the weapon + + KHook::Virtual m_hBaseButtonUse; + KHook::Virtual m_hPhysBoxUse; + KHook::Virtual m_hRotButtonUse; + KHook::Virtual m_hMomentaryRotButtonUse; + KHook::Virtual m_hPhysicalButtonUse; + KHook::Virtual m_hTriggerTeleportStartTouch; + KHook::Virtual m_hTriggerOnceStartTouch; + KHook::Virtual m_hTriggerMultipleStartTouch; + KHook::Virtual m_hTriggerTeleportTouch; + KHook::Virtual m_hTriggerOnceTouch; + KHook::Virtual m_hTriggerMultipleTouch; + KHook::Virtual m_hTriggerTeleportEndTouch; + KHook::Virtual m_hTriggerOnceEndTouch; + KHook::Virtual m_hTriggerMultipleEndTouch; }; extern CEWHandler* g_pEWHandler; @@ -302,7 +315,6 @@ void EW_DropWeapon(CCSPlayer_WeaponServices* pWeaponServices, CBasePlayerWeapon* void EW_PlayerDeath(IGameEvent* pEvent); void EW_PlayerDeathPre(CCSPlayerController* pController); void EW_PlayerDisconnect(int slot); -bool EW_IsFireOutputHooked(); -void EW_FireOutput(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEntityInstance* pCaller, const CVariant* value, float flDelay); +void EW_FireOutput(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEntityInstance* pCaller, float flDelay); int GetTemplateSuffixNumber(const char* szName); float EW_UpdateHud(); \ No newline at end of file diff --git a/src/gameconfig.cpp b/src/gameconfig.cpp index ceab26717..5113c301a 100644 --- a/src/gameconfig.cpp +++ b/src/gameconfig.cpp @@ -1,5 +1,6 @@ #include "gameconfig.h" #include "addresses.h" +#include "khook.hpp" #undef snprintf #include "vendor/nlohmann/json.hpp" @@ -160,6 +161,8 @@ CModule** CGameConfig::GetModule(const char* name) return &modules::networksystem; else if (strcmp(library, "matchmaking") == 0) return &modules::matchmaking; + else if (strcmp(library, "worldrenderer") == 0) + return &modules::worldrenderer; #ifdef _WIN32 else if (strcmp(library, "hammer") == 0) return &modules::hammer; @@ -219,17 +222,12 @@ void* CGameConfig::ResolveSignature(const char* name) return nullptr; } - size_t iLength = 0; - byte* pSignature = IDASigToUint8Array(signature, iLength); - if (!pSignature) - return nullptr; - - int error; + std::vector bytes; - address = (*module)->FindSignature(pSignature, iLength, error); + if (!IsValidIDASignature(signature, bytes)) + return nullptr; - if (error == SIG_FOUND_MULTIPLE) - Panic("!!!!!!!!!! Signature for %s occurs multiple times! Using first match but this might end up crashing!\n", name); + address = KHook::LookupSignature((*module)->m_base, (*module)->m_size, signature); } if (!address) @@ -268,7 +266,7 @@ bool CGameConfig::ParsePatternBytes(const char* pattern, std::vector& b if (*cursor == '?') { - bytes.push_back('\x2A'); + bytes.push_back('?'); cursor++; if (*cursor == '?') cursor++; @@ -287,21 +285,30 @@ bool CGameConfig::ParsePatternBytes(const char* pattern, std::vector& b return !bytes.empty(); } -byte* CGameConfig::IDASigToUint8Array(const char* signature, size_t& length) +bool CGameConfig::IsValidIDASignature(const char* signature, std::vector& bytes) { if (!signature || strlen(signature) <= 0) { Panic("Invalid IDA signature string\n"); - return nullptr; + return false; } - std::vector bytes; if (!ParsePatternBytes(signature, bytes)) { Panic("Invalid IDA signature format \"%s\"\n", signature); - return nullptr; + return false; } + return true; +} + +byte* CGameConfig::IDASigToUint8Array(const char* signature, size_t& length) +{ + std::vector bytes; + + if (!IsValidIDASignature(signature, bytes)) + return nullptr; + length = bytes.size(); uint8_t* dest = new uint8_t[length]; diff --git a/src/gameconfig.h b/src/gameconfig.h index 9a296902f..bf5de6fac 100644 --- a/src/gameconfig.h +++ b/src/gameconfig.h @@ -27,6 +27,7 @@ class CGameConfig void* ResolveSignature(const char* name); int ParseHexNibble(char c); bool ParsePatternBytes(const char* pattern, std::vector& bytes); + bool IsValidIDASignature(const char* signature, std::vector& bytes); byte* IDASigToUint8Array(const char* signature, size_t& length); private: diff --git a/src/map_votes.cpp b/src/map_votes.cpp index 72dd2479f..1f2a13640 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -24,6 +24,7 @@ #include "entity/cgamerules.h" #include "eventlistener.h" #include "iserver.h" +#include "mapmigrations.h" #include "playermanager.h" #include "steam/steam_gameserver.h" #include "strtools.h" @@ -73,6 +74,10 @@ CON_COMMAND_CHAT_FLAGS(map, " - Change map", ADMFLAG_CHANGEMAP) return -1.0f; }); + // Map migrations need map info ahead of time + if (pMap->GetWorkshopId() != 0) + CMapSystemWorkshopDetailsQuery::Create(pMap->GetWorkshopId()); + ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "Changing map to \x06%s\x01...", pMap->GetName()); }); } @@ -340,7 +345,13 @@ void CMapVoteSystem::StartVote() for (int i = 0; i < m_iVoteSize; i++) { int iMapIndex = g_pGameRules->m_nEndMatchMapGroupVoteOptions[i]; + uint64 iWorkshopId = GetMapByIndex(iMapIndex)->GetWorkshopId(); + Message("The %d-th chosen map index %d is %s\n", i, iMapIndex, GetMapName(iMapIndex)); + + // Map migrations need map info ahead of time + if (iWorkshopId != 0) + CMapSystemWorkshopDetailsQuery::Create(iWorkshopId); } static ConVarRefAbstract mp_endmatch_votenextleveltime("mp_endmatch_votenextleveltime"); @@ -858,7 +869,10 @@ void CMapVoteSystem::ForceNextMap(CCSPlayerController* pController, const char* return; } - // When found, print the map and store the forced map + // Map migrations need map info ahead of time + if (pMap->GetWorkshopId() != 0) + CMapSystemWorkshopDetailsQuery::Create(pMap->GetWorkshopId()); + g_pMapVoteSystem->SetForcedNextMap(pMap); ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "\x06%s \x01has been forced as the next map.\n", g_pMapVoteSystem->GetForcedNextMap()->GetName()); }); @@ -886,30 +900,24 @@ void CMapVoteSystem::PrintDownloadProgress() void CMapVoteSystem::OnMapDownloaded(DownloadItemResult_t* pResult) { - if (std::find(m_DownloadQueue.begin(), m_DownloadQueue.end(), pResult->m_nPublishedFileId) == m_DownloadQueue.end() || !GetSteamUGC()) + if (m_DownloadQueue.empty() || m_DownloadQueue.front() != pResult->m_nPublishedFileId) return; - // Some weird rate limiting that's been observed? Back off for a while then retry download + // This result is also used for some kind of rate limiting, back off for a while then retry download if (pResult->m_eResult == k_EResultNoConnection) { - PublishedFileId_t workshopID = m_DownloadQueue.front(); - Message("Addon %llu download failed with status code 3, retrying in 2 minutes\n", workshopID); - - m_pRateLimitedDownloadTimer = CTimer::Create(120.0f, TIMERFLAG_NONE, [workshopID]() { - GetSteamUGC()->DownloadItem(workshopID, false); - - return -1.0f; - }); - + Message("Addon %llu download failed with status code 3, retrying in 5 minutes\n", m_DownloadQueue.front()); + StartMapDownload(300.0f); return; } - m_DownloadQueue.pop_front(); + if (pResult->m_eResult != k_EResultOK) + Message("Addon %llu download failed with status code %i, skipping\n", pResult->m_nPublishedFileId, pResult->m_eResult); - if (GetDownloadQueueSize() == 0) - return; + m_DownloadQueue.pop_front(); - GetSteamUGC()->DownloadItem(m_DownloadQueue.front(), false); + // Go slowly, try to avoid the rate limit because it can break required on-demand downloads elsewhere + StartMapDownload(30.0f); } void CMapVoteSystem::QueueMapDownload(PublishedFileId_t iWorkshopId) @@ -920,7 +928,28 @@ void CMapVoteSystem::QueueMapDownload(PublishedFileId_t iWorkshopId) m_DownloadQueue.push_back(iWorkshopId); if (m_DownloadQueue.front() == iWorkshopId) - GetSteamUGC()->DownloadItem(iWorkshopId, false); + StartMapDownload(); +} + +void CMapVoteSystem::StartMapDownload(float flDelay) +{ + if (auto pTimer = m_pDownloadTimer.lock()) + pTimer->Cancel(); + + if (m_DownloadQueue.empty()) + return; + + PublishedFileId_t workshopID = m_DownloadQueue.front(); + + m_pDownloadTimer = CTimer::Create(flDelay, TIMERFLAG_NONE, [workshopID]() { + if (!GetSteamUGC() || !GetSteamUGC()->DownloadItem(workshopID, false)) + { + Message("Addon %llu download failed to start, retrying\n", workshopID); + return 30.0f; + } + + return -1.0f; + }); } bool CMapVoteSystem::LoadMapList() @@ -1144,9 +1173,6 @@ void CMapVoteSystem::OnPlayerCountChange() void CMapVoteSystem::ApplyGameSettings(const char* pszMapName, uint64 iWorkshopId) { - if (!g_cvarVoteManagerEnable.Get()) - return; - auto pair = GetMapInfoByIdentifiers(pszMapName, iWorkshopId); if (pair.first != -1) @@ -1154,6 +1180,9 @@ void CMapVoteSystem::ApplyGameSettings(const char* pszMapName, uint64 iWorkshopI else SetCurrentMap(std::make_shared(pszMapName, iWorkshopId, pszMapName[0] == '\0' && iWorkshopId == 0)); + if (!g_cvarVoteManagerEnable.Get()) + return; + ProcessGroupCooldowns(); } @@ -1362,8 +1391,8 @@ bool CMapVoteSystem::ReloadMapList(bool bReloadMap) if (!m_pDownloadProgressTimer.expired()) m_pDownloadProgressTimer.lock()->Cancel(); - if (!m_pRateLimitedDownloadTimer.expired()) - m_pRateLimitedDownloadTimer.lock()->Cancel(); + if (!m_pDownloadTimer.expired()) + m_pDownloadTimer.lock()->Cancel(); } if (!g_pMapVoteSystem->LoadMapList()) @@ -1396,8 +1425,7 @@ std::shared_ptr CMapSystemWorkshopDetailsQuery:: { if (!GetSteamUGC()) { - Panic("A workshop map query was attempted on null ISteamUGC, returning early.\n"); - ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Failed to query workshop map information for ID \x06%llu\x01.", iWorkshopId); + ReportCreateFailure(iWorkshopId, pController, callbackSuccess); return nullptr; } @@ -1406,13 +1434,20 @@ std::shared_ptr CMapSystemWorkshopDetailsQuery:: if (hQuery == k_UGCQueryHandleInvalid) { - ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Failed to query workshop map information for ID \x06%llu\x01.", iWorkshopId); + ReportCreateFailure(iWorkshopId, pController, callbackSuccess); return nullptr; } GetSteamUGC()->SetAllowCachedResponse(hQuery, 0); SteamAPICall_t hCall = GetSteamUGC()->SendQueryUGCRequest(hQuery); + if (hCall == k_uAPICallInvalid) + { + GetSteamUGC()->ReleaseQueryUGCRequest(hQuery); + ReportCreateFailure(iWorkshopId, pController, callbackSuccess); + return nullptr; + } + auto pQuery = std::make_shared(hQuery, iWorkshopId, pController, callbackSuccess); g_pMapVoteSystem->AddWorkshopDetailsQuery(pQuery); pQuery->m_CallResult.Set(hCall, pQuery.get(), &CMapSystemWorkshopDetailsQuery::OnQueryCompleted); @@ -1420,23 +1455,40 @@ std::shared_ptr CMapSystemWorkshopDetailsQuery:: return pQuery; } +void CMapSystemWorkshopDetailsQuery::ReportCreateFailure(uint64 iWorkshopId, CCSPlayerController* pController, QueryCallback_t callbackSuccess) +{ + Message("Failed to query workshop map information for ID %llu\n", iWorkshopId); + + if (callbackSuccess) + ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Failed to query workshop map information for ID \x06%llu\x01.", iWorkshopId); +} + void CMapSystemWorkshopDetailsQuery::OnQueryCompleted(SteamUGCQueryCompleted_t* pCompletedQuery, bool bFailed) { CCSPlayerController* pController = m_hController.Get(); SteamUGCDetails_t details; - // Only allow null controller if controller was originally null (console) - if (m_bConsole || pController) + // Is this a player map lookup? Only allow null controller if controller was originally null (console) + bool bMapLookupActive = m_callbackSuccess && (m_bConsole || pController); + + if (bFailed || pCompletedQuery->m_eResult != k_EResultOK || pCompletedQuery->m_unNumResultsReturned < 1 || !GetSteamUGC()->GetQueryUGCResult(pCompletedQuery->m_handle, 0, &details) || details.m_eResult != k_EResultOK) { - if (bFailed || pCompletedQuery->m_eResult != k_EResultOK || pCompletedQuery->m_unNumResultsReturned < 1 || !GetSteamUGC()->GetQueryUGCResult(pCompletedQuery->m_handle, 0, &details) || details.m_eResult != k_EResultOK) - { + if (bMapLookupActive) ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Failed to query workshop map information for ID \x06%llu\x01.", m_iWorkshopId); - } - else if (details.m_nConsumerAppID != 730 || details.m_eFileType != k_EWorkshopFileTypeCommunity) - { - ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "The ID \x06%llu\x01 is not a valid CS2 workshop map.", m_iWorkshopId); - } else + Message("Failed to query workshop map information for ID %llu\n", m_iWorkshopId); + } + else if (details.m_nConsumerAppID != 730 || details.m_eFileType != k_EWorkshopFileTypeCommunity) + { + if (bMapLookupActive) + ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "The ID \x06%llu\x01 is not a valid CS2 workshop map.", m_iWorkshopId); + } + else + { + // Map migrations also need to know when maps were updated + g_pMapMigrations->UpdateMapUpdateTime(m_iWorkshopId, details.m_rtimeUpdated); + + if (bMapLookupActive) { // Try to get a head start on downloading the map if needed GetSteamUGC()->DownloadItem(m_iWorkshopId, false); diff --git a/src/map_votes.h b/src/map_votes.h index ac55efac1..e6990288b 100644 --- a/src/map_votes.h +++ b/src/map_votes.h @@ -131,7 +131,6 @@ class CGroup float m_fCooldown; }; -// Implementation is a bit hardcoded for HandlePlayerMapLookup use class CMapSystemWorkshopDetailsQuery : public std::enable_shared_from_this { public: @@ -149,7 +148,8 @@ class CMapSystemWorkshopDetailsQuery : public std::enable_shared_from_this Create(uint64 iWorkshopId, CCSPlayerController* pController, QueryCallback_t callbackSuccess); + static std::shared_ptr Create(uint64 iWorkshopId, CCSPlayerController* pController = nullptr, QueryCallback_t callbackSuccess = nullptr); + static void ReportCreateFailure(uint64 iWorkshopId, CCSPlayerController* pController, QueryCallback_t callbackSuccess); private: void OnQueryCompleted(SteamUGCQueryCompleted_t* pCompletedQuery, bool bFailed); @@ -202,6 +202,7 @@ class CMapVoteSystem bool IsMapListLoaded() { return m_bMapListLoaded; } CUtlStringList CreateWorkshopMapGroup(); void QueueMapDownload(PublishedFileId_t iWorkshopId); + void StartMapDownload(float flDelay = 0.0f); void PrintDownloadProgress(); std::shared_ptr GetCurrentMap() { return m_pCurrentMap; } void SetCurrentMap(std::shared_ptr pCurrentMap) { m_pCurrentMap = pCurrentMap; } @@ -245,7 +246,7 @@ class CMapVoteSystem bool g_bDisableCooldowns = false; std::filesystem::file_time_type m_timeMapListModified = std::filesystem::file_time_type::min(); std::weak_ptr m_pDownloadProgressTimer; - std::weak_ptr m_pRateLimitedDownloadTimer; + std::weak_ptr m_pDownloadTimer; std::vector> m_vecWorkshopDetailsQueries; int m_iSessionMaxPlayerCount = 0; }; diff --git a/src/mapmigrations.cpp b/src/mapmigrations.cpp index 03db6b160..8e57a8eb0 100644 --- a/src/mapmigrations.cpp +++ b/src/mapmigrations.cpp @@ -20,8 +20,8 @@ #include "mapmigrations.h" #include "cs2fixes.h" #include "entity.h" -#include "entity/cbasemodelentity.h" #include "entity/cbasetoggle.h" +#include "map_votes.h" #include "utils.h" #include "vprof.h" @@ -29,36 +29,36 @@ CMapMigrations* g_pMapMigrations = nullptr; const time_t g_time20260121 = 1769036239; const time_t g_time20260420 = 1776725888; +const time_t g_time20260922 = 1790120204; CConVar g_cvarMapMigrations20260121("cs2f_mapmigrations_20260121", FCVAR_NONE, "Current mode for 2026-01-21 CS2 update map migrations. [0 = Force disabled, 1 = Force enabled, 2 = Automatically enabled for maps updated before 2026-01-21 & disabled if updated after]", 2); CConVar g_cvarMapMigrations20260420("cs2f_mapmigrations_20260420", FCVAR_NONE, "Current mode for 2026-04-20 CS2 update map migrations. [0 = Force disabled, 1 = Force enabled, 2 = Automatically enabled for maps updated before 2026-04-20 & disabled if updated after]", 2); +CConVar g_cvarMapMigrations20260922("cs2f_mapmigrations_20260922", FCVAR_NONE, "Current mode for 2026-09-22 CS2 update map migrations. [0 = Force disabled, 1 = Force enabled, 2 = Automatically enabled for maps updated before 2026-09-22 & disabled if updated after]", 2); -void CMapMigrations::ApplyGameSettings(uint64 iWorkshopId) +void CMapMigrations::PreLevelLoad(uint64 iWorkshopId) { m_timeMapUpdated = std::numeric_limits::max(); // Don't run on default maps if (iWorkshopId != 0) - CMapMigrationWorkshopDetailsQuery::Create(iWorkshopId); -} - -void CMapMigrations::OnRoundPrestart() -{ - m_vecModelEntitiesUsingRendermodeEnum.clear(); - m_vecEquippedWeapons.clear(); + { + if (m_mapUpdateTimes.contains(iWorkshopId)) + m_timeMapUpdated = m_mapUpdateTimes[iWorkshopId]; + else + Message("Skipping pre-load map migrations for %llu, update time is not available\n", iWorkshopId); + } } -void CMapMigrations::OnEntitySpawned_Pre(CBaseEntity* pEntity, const CEntityKeyValues* pKeyValues) +void CMapMigrations::ApplyGameSettings(uint64 iWorkshopId) { - // Stupid workaround for CEntityKeyValues being inaccessible after entity spawn - // We need access to this in 2026-01-21 rendermode migrations when called from UpdateMapUpdateTime - if (pEntity->AsBaseModelEntity() && V_StringToInt32(pKeyValues->GetString("rendermode"), -1, NULL, NULL, PARSING_FLAG_SKIP_WARNING) == -1) - m_vecModelEntitiesUsingRendermodeEnum.push_back(pEntity->GetHandle()); + // If map update time wasn't available before map load, try to get the update time anyways, for later migrations + if (iWorkshopId != 0 && !m_mapUpdateTimes.contains(iWorkshopId)) + CMapSystemWorkshopDetailsQuery::Create(iWorkshopId); } -void CMapMigrations::OnEntitySpawned_Post(CBaseEntity* pEntity) +void CMapMigrations::OnRoundPrestart() { - RunMigrations(pEntity); + m_vecEquippedWeapons.clear(); } void CMapMigrations::OnEquipWeapon(CBasePlayerWeapon* pWeapon) @@ -67,43 +67,59 @@ void CMapMigrations::OnEquipWeapon(CBasePlayerWeapon* pWeapon) Migrations_20260420(pWeapon); } -void CMapMigrations::RunMigrations(CBaseEntity* pEntity) +bool CMapMigrations::Detour_CEntityIdentity_AcceptInput(CEntityIdentity* pThis, CUtlSymbolLarge* pInputName, CEntityInstance* pActivator, CEntityInstance* pCaller, variant_t* value) { + if (g_cvarMapMigrations20260922.Get() == 1 || (g_cvarMapMigrations20260922.Get() == 2 && m_timeMapUpdated < g_time20260922)) + return Migrations_20260922(pThis, pInputName, pActivator, pCaller, value); + + return false; +} + +void CMapMigrations::RunMigrations(CUtlVector* pVecEntityKeyValues) +{ + if (g_cvarMapMigrations20260121.Get() > 0) + Migrations_Rendermode(pVecEntityKeyValues); + if (g_cvarMapMigrations20260121.Get() == 1 || (g_cvarMapMigrations20260121.Get() == 2 && m_timeMapUpdated < g_time20260121)) - Migrations_20260121(pEntity); + Migrations_20260121(pVecEntityKeyValues); } -void CMapMigrations::Migrations_20260121(CBaseEntity* pEntity) +void CMapMigrations::Migrations_Rendermode(CUtlVector* pVecEntityKeyValues) { - if (!V_strcasecmp(pEntity->GetClassname(), "func_door_rotating")) + FOR_EACH_VEC(*pVecEntityKeyValues, i) { - uint32 spawnFlags = pEntity->m_spawnflags(); + auto pKeyValues = (*pVecEntityKeyValues)[i]; - if (!(spawnFlags & SF_DOOR_ONEWAY)) - pEntity->m_spawnflags = spawnFlags + SF_DOOR_ONEWAY; - } - - CBaseModelEntity* pModelEntity = pEntity->AsBaseModelEntity(); + if (!pKeyValues->HasValue("rendermode")) + continue; - if (pModelEntity) - { - // Also need to make sure the entity is using index-based rendermodes, and not enum-based ones (which will already automatically migrate correctly) - // This differentiation is lost after entity spawn, so we had to check the original keyvalue earlier instead - for (int i = 0; i < m_vecModelEntitiesUsingRendermodeEnum.size(); i++) - if (m_vecModelEntitiesUsingRendermodeEnum[i] == pModelEntity->GetHandle()) - return; + int renderMode = V_StringToInt32(pKeyValues->GetString("rendermode"), -1, NULL, NULL, PARSING_FLAG_SKIP_WARNING); - RenderMode_t renderMode = pModelEntity->m_nRenderMode(); + // Enum-named render modes already migrate correctly + if (renderMode == -1) + continue; - // Legacy kRenderTransAlpha if (renderMode == 4) - pModelEntity->m_nRenderMode = kRenderTransAlpha; - // Legacy kRenderNone + pKeyValues->SetString("rendermode", "kRenderTransAlpha"); else if (renderMode == 10) - pModelEntity->m_nRenderMode = kRenderNone; + pKeyValues->SetString("rendermode", "kRenderNone"); // All other removed render modes, fall back to normal else if (renderMode > kRenderNormal) - pModelEntity->m_nRenderMode = kRenderNormal; + pKeyValues->SetString("rendermode", "kRenderNormal"); + } +} + +void CMapMigrations::Migrations_20260121(CUtlVector* pVecEntityKeyValues) +{ + FOR_EACH_VEC(*pVecEntityKeyValues, i) + { + auto pKeyValues = (*pVecEntityKeyValues)[i]; + + if (!V_strcasecmp(pKeyValues->GetString("classname"), "func_door_rotating") && pKeyValues->HasValue("spawnflags")) + { + uint32 spawnFlags = pKeyValues->GetUint("spawnflags"); + pKeyValues->SetUint("spawnflags", spawnFlags | SF_DOOR_ONEWAY); + } } } @@ -151,60 +167,32 @@ void CMapMigrations::Migrations_20260420(CBasePlayerWeapon* pWeapon) } } -bool CMapMigrations::Migrations20260420Enabled() -{ - return g_cvarMapMigrations20260420.Get() == 1 || (g_cvarMapMigrations20260420.Get() == 2 && m_timeMapUpdated < g_time20260420); -} - -void CMapMigrations::UpdateMapUpdateTime(time_t timeMapUpdated) -{ - m_timeMapUpdated = timeMapUpdated; - - CBaseEntity* pTarget = nullptr; - - // May be called late, so also check any existing entities first - while ((pTarget = UTIL_FindEntityByClassname(pTarget, "*"))) - RunMigrations(pTarget); -} - -std::shared_ptr CMapMigrationWorkshopDetailsQuery::Create(uint64 iWorkshopId) +bool CMapMigrations::Migrations_20260922(CEntityIdentity* pThis, CUtlSymbolLarge* pInputName, CEntityInstance* pActivator, CEntityInstance* pCaller, variant_t* value) { - if (!GetSteamUGC()) + // Fix stringified Pulse argument packs formatting float decimals breaking logic_case matching post 2026-09-22 CS2 update + if (value->m_type == FIELD_FLOAT32 && !V_strcasecmp(pInputName->String(), "InValue") && !V_strcasecmp(pThis->GetClassname(), "logic_case")) { - Panic("Map migrations failed to find current map update time: null ISteamUGC\n"); - return nullptr; - } + char szValue[32]; + V_snprintf(szValue, sizeof(szValue), "%g", value->m_float32); - uint64 iWorkshopIDArray[1] = {iWorkshopId}; - UGCQueryHandle_t hQuery = GetSteamUGC()->CreateQueryUGCDetailsRequest(iWorkshopIDArray, 1); - - if (hQuery == k_UGCQueryHandleInvalid) - { - Panic("Map migrations failed to find current map update time: failed to query workshop map information for ID %llu\n", iWorkshopId); - return nullptr; + // Back up through CEntityInstance_AcceptInput so we can just worry about variant_t + ((CBaseEntity*)pThis->m_pInstance)->AcceptInput(pInputName->String(), szValue, pActivator, pCaller); + return true; } - GetSteamUGC()->SetAllowCachedResponse(hQuery, 0); - SteamAPICall_t hCall = GetSteamUGC()->SendQueryUGCRequest(hQuery); - - auto pQuery = std::make_shared(hQuery, iWorkshopId); - g_pMapMigrations->AddWorkshopDetailsQuery(pQuery); - pQuery->m_CallResult.Set(hCall, pQuery.get(), &CMapMigrationWorkshopDetailsQuery::OnQueryCompleted); - - return pQuery; + return false; } -void CMapMigrationWorkshopDetailsQuery::OnQueryCompleted(SteamUGCQueryCompleted_t* pCompletedQuery, bool bFailed) +bool CMapMigrations::Migrations20260420Enabled() { - SteamUGCDetails_t details; - - if (bFailed || pCompletedQuery->m_eResult != k_EResultOK || pCompletedQuery->m_unNumResultsReturned < 1 || !GetSteamUGC()->GetQueryUGCResult(pCompletedQuery->m_handle, 0, &details) || details.m_eResult != k_EResultOK) - Panic("Map migrations failed to find current map update time: failed to query workshop map information for ID %llu\n", m_iWorkshopId); - else - g_pMapMigrations->UpdateMapUpdateTime(details.m_rtimeUpdated); + return g_cvarMapMigrations20260420.Get() == 1 || (g_cvarMapMigrations20260420.Get() == 2 && m_timeMapUpdated < g_time20260420); +} - if (GetSteamUGC()) - GetSteamUGC()->ReleaseQueryUGCRequest(m_hQuery); +void CMapMigrations::UpdateMapUpdateTime(uint64 iWorkshopId, time_t timeMapUpdated) +{ + m_mapUpdateTimes[iWorkshopId] = timeMapUpdated; - g_pMapMigrations->RemoveWorkshopDetailsQuery(shared_from_this()); + // If we get triggered through ApplyGameSettings + if (g_pMapVoteSystem->GetCurrentMap() && g_pMapVoteSystem->GetCurrentMap()->GetWorkshopId() == iWorkshopId) + m_timeMapUpdated = timeMapUpdated; } \ No newline at end of file diff --git a/src/mapmigrations.h b/src/mapmigrations.h index 6d4cca139..9c57da3a4 100644 --- a/src/mapmigrations.h +++ b/src/mapmigrations.h @@ -23,8 +23,6 @@ #include "convar.h" #include "cs2_sdk/entity/ccsweaponbase.h" #include "ehandle.h" -#include "entitysystem.h" -#include "steam/isteamugc.h" #include #undef max @@ -32,43 +30,25 @@ extern CConVar g_cvarMapMigrations20260121; #define SF_DOOR_ONEWAY 16 -class CMapMigrationWorkshopDetailsQuery : public std::enable_shared_from_this -{ -public: - CMapMigrationWorkshopDetailsQuery(UGCQueryHandle_t hQuery, uint64 iWorkshopId) : - m_hQuery(hQuery), m_iWorkshopId(iWorkshopId) - {} - - static std::shared_ptr Create(uint64 iWorkshopId); - -private: - void OnQueryCompleted(SteamUGCQueryCompleted_t* pCompletedQuery, bool bFailed); - - UGCQueryHandle_t m_hQuery; - CCallResult m_CallResult; - uint64 m_iWorkshopId; -}; - class CMapMigrations { public: + void PreLevelLoad(uint64 iWorkshopId); void ApplyGameSettings(uint64 iWorkshopId); void OnRoundPrestart(); - void OnEntitySpawned_Pre(CBaseEntity* pEntity, const CEntityKeyValues* pKeyValues); - void OnEntitySpawned_Post(CBaseEntity* pEntity); void OnEquipWeapon(CBasePlayerWeapon* pWeapon); - void RunMigrations(CBaseEntity* pEntity); - void Migrations_20260121(CBaseEntity* pEntity); + bool Detour_CEntityIdentity_AcceptInput(CEntityIdentity* pThis, CUtlSymbolLarge* pInputName, CEntityInstance* pActivator, CEntityInstance* pCaller, variant_t* value); + void RunMigrations(CUtlVector* pVecEntityKeyValues); + void Migrations_Rendermode(CUtlVector* pVecEntityKeyValues); + void Migrations_20260121(CUtlVector* pVecEntityKeyValues); void Migrations_20260420(CBasePlayerWeapon* pWeapon); + bool Migrations_20260922(CEntityIdentity* pThis, CUtlSymbolLarge* pInputName, CEntityInstance* pActivator, CEntityInstance* pCaller, variant_t* value); bool Migrations20260420Enabled(); - void UpdateMapUpdateTime(time_t timeMapUpdated); - void AddWorkshopDetailsQuery(std::shared_ptr pQuery) { m_vecWorkshopDetailsQueries.push_back(pQuery); } - void RemoveWorkshopDetailsQuery(std::shared_ptr pQuery) { m_vecWorkshopDetailsQueries.erase(std::remove(m_vecWorkshopDetailsQueries.begin(), m_vecWorkshopDetailsQueries.end(), pQuery), m_vecWorkshopDetailsQueries.end()); } + void UpdateMapUpdateTime(uint64 iWorkshopId, time_t timeMapUpdated); private: time_t m_timeMapUpdated = std::numeric_limits::max(); - std::vector> m_vecWorkshopDetailsQueries; - std::vector> m_vecModelEntitiesUsingRendermodeEnum; + std::unordered_map m_mapUpdateTimes; std::vector> m_vecEquippedWeapons; }; diff --git a/src/mempatch.cpp b/src/mempatch.cpp index 0cec70b55..2dcd025e0 100644 --- a/src/mempatch.cpp +++ b/src/mempatch.cpp @@ -19,13 +19,14 @@ #include "mempatch.h" #include "common.h" +#include "khook.hpp" #include "tier0/dbg.h" #include "tier1/strtools.h" #include "utils/module.h" #include "tier0/memdbgon.h" -bool CMemPatch::PerformPatch(CGameConfig* gameConfig) +bool CMemPatch::PerformPatch() { // We're patched already if (m_pOriginalBytes) @@ -34,25 +35,25 @@ bool CMemPatch::PerformPatch(CGameConfig* gameConfig) // If we already have an address, no need to look for it again if (!m_pPatchAddress) { - m_pPatchAddress = (uintptr_t)gameConfig->ResolveSignature(m_pSignatureName); + m_pPatchAddress = (uintptr_t)g_GameConfig->ResolveSignature(m_pSignatureName); if (!m_pPatchAddress) return false; } - const char* patch = gameConfig->GetPatch(m_pszName); + const char* patch = g_GameConfig->GetPatch(m_pszName); if (!patch) { Panic("Failed to find patch for %s\n", m_pszName); return false; } - m_pPatch = gameConfig->IDASigToUint8Array(patch, m_iPatchLength); + m_pPatch = g_GameConfig->IDASigToUint8Array(patch, m_iPatchLength); if (!m_pPatch) return false; if (V_strcmp(m_pOffsetName, "")) { - m_iOffset = gameConfig->GetOffset(m_pOffsetName); + m_iOffset = g_GameConfig->GetOffset(m_pOffsetName); if (m_iOffset == -1) { Panic("Failed to find offset %s for patch %s\n", m_pOffsetName, m_pszName); diff --git a/src/mempatch.h b/src/mempatch.h index ef4899bc0..1d21ec122 100644 --- a/src/mempatch.h +++ b/src/mempatch.h @@ -38,7 +38,7 @@ class CMemPatch m_iOffset = 0; } - bool PerformPatch(CGameConfig* gameConfig); + bool PerformPatch(); void UndoPatch(); uintptr_t GetPatchAddress() { return m_pPatchAddress; } diff --git a/src/patches.cpp b/src/patches.cpp index 573820cda..55df3bac2 100644 --- a/src/patches.cpp +++ b/src/patches.cpp @@ -40,18 +40,18 @@ CConVar cs2f_movement_unlocker_enable("cs2f_movement_unlocker_enable", FCV [](CConVar* cvar, CSplitScreenSlot slot, const bool* new_val, const bool* old_val) { // Movement unlocker is always the first patch if (*new_val) - g_CommonPatches[0].PerformPatch(g_GameConfig); + g_CommonPatches[0].PerformPatch(); else g_CommonPatches[0].UndoPatch(); }); -bool InitPatches(CGameConfig* g_GameConfig) +bool InitPatches() { bool success = true; // Skip first two patches (movement unlocker & bot nav ignore), they are patched elsewhere for (int i = 2; i < sizeof(g_CommonPatches) / sizeof(*g_CommonPatches); i++) - if (!g_CommonPatches[i].PerformPatch(g_GameConfig)) + if (!g_CommonPatches[i].PerformPatch()) success = false; return success; diff --git a/src/patches.h b/src/patches.h index 0fd2b2015..7edf24784 100644 --- a/src/patches.h +++ b/src/patches.h @@ -5,5 +5,5 @@ extern CMemPatch g_CommonPatches[]; -bool InitPatches(CGameConfig* gameConfig); +bool InitPatches(); void UndoPatches(); diff --git a/src/playermanager.cpp b/src/playermanager.cpp index 3bfe9c84d..8aa6da755 100644 --- a/src/playermanager.cpp +++ b/src/playermanager.cpp @@ -123,6 +123,24 @@ void ZEPlayer::OnAuthenticated() g_pUserPreferencesSystem->PullPreferences(GetPlayerSlot().Get()); SetSteamIdAttribute(); + + CCSPlayerController* pController = CCSPlayerController::FromSlot(GetPlayerSlot()); + + if (!g_cvarEnableMapSteamIds.Get() || !pController) + return; + + IGameEvent* pEvent = g_gameEventManager->CreateEvent("player_connect"); + + if (!pEvent) + return; + + pEvent->SetString("name", pController->GetPlayerName().c_str()); + pEvent->SetPlayer("userid", GetPlayerSlot()); + pEvent->SetBool("bot", false); + // Needs a custom name, because CS2 strips keys like xuid/networkid etc from logic_eventlistener output + pEvent->SetUint64("steam_id", GetSteamId64()); + + g_gameEventManager->FireEvent(pEvent, true); } void ZEPlayer::CheckInfractions() @@ -637,6 +655,82 @@ void ZEPlayer::CreatePointOrient() pOrient->AcceptInput("SetTarget", "!activator", pPawn); } +void ZEPlayer::ToggleThirdPerson(float flDistance) +{ + CCSPlayerController* pController = CCSPlayerController::FromSlot(GetPlayerSlot()); + + if (!pController) + return; + + CCSPlayerPawn* pPawn = pController->GetPlayerPawn(); + + if (!pPawn) + return; + + auto pCameraService = pPawn->GetCameraService(); + + if (!pCameraService) + return; + + CCSCustomPlayerCamera* pCamera = (CCSCustomPlayerCamera*)pCameraService->m_hViewEntity().Get(); + + if (pCamera) + { + // Map is taking control of the camera, let it be + if (pCamera->AsPointViewControl() || !CloseEnough(pCamera->m_vecFollowOffset().z, 0.001337)) + return; + + // Camera is ours, disable it + pCamera->m_nCameraMode = CUSTOM_CAMERA_MODE_DISABLED; + pCameraService->m_hViewEntity = nullptr; + + ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "You have disabled thirdperson."); + + return; + } + else if (!m_hCustomCamera.Get()) + { + // Try to find the player's camera if the map spawned it + CCSCustomPlayerCamera* pEnt = nullptr; + + while ((pEnt = (CCSCustomPlayerCamera*)UTIL_FindEntityByClassname(pEnt, "custom_player_camera"))) + { + if (pEnt->m_hPawn.Get() == pPawn) + { + pCamera = pEnt; + break; + } + } + + if (!pCamera) + { + // No camera found, spawn one + pCamera = CreateEntityByName("custom_player_camera"); + pCamera->DispatchSpawn(); + } + + m_hCustomCamera = pCamera; + } + else + { + // Player is in first person and our camera exists, use it + pCamera = m_hCustomCamera; + } + + pCamera->m_hPawn = pPawn; + pCamera->m_hFollowEntity = pPawn; + pCamera->m_nCameraMode = CUSTOM_CAMERA_MODE_FOLLOW_POSITION; + pCamera->m_bFollowEyes = true; + pCamera->m_vecFollowOffset = Vector(0.f, 0.f, 0.001337f); // HACK: Random tiny value to mark this as our camera + pCamera->m_vecCameraOffset = Vector(flDistance, pPawn->m_bLeftHanded ? 15.f : -15.f, 0.f); + pCamera->m_bClipCameraOffset = true; + pCamera->m_flCameraOffsetReturnStrength = 1.f; + + pCameraService->m_hViewEntity = pCamera; + + ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "You have enabled thirdperson."); +} + void ZEPlayer::CreateEntwatchHud() { CCSPlayerController* pController = CCSPlayerController::FromSlot(GetPlayerSlot()); diff --git a/src/playermanager.h b/src/playermanager.h index 6480dab08..d09c76484 100644 --- a/src/playermanager.h +++ b/src/playermanager.h @@ -23,6 +23,7 @@ #include "entity/cparticlesystem.h" #include "entity/cpointorient.h" #include "entity/cpointworldtext.h" +#include "entity/customplayercamera.h" #include "entity/lights.h" #include "gamesystem.h" #include "in_buttons.h" @@ -34,6 +35,7 @@ extern CConVar g_cvarFlashLightTransmitOthers; extern CConVar g_cvarFlashLightAttachment; +extern CConVar g_cvarEnableMapSteamIds; #define NO_TARGET_BLOCKS (0) #define NO_RANDOM (1 << 1) @@ -343,6 +345,7 @@ class ZEPlayer void SetSteamIdAttribute(); void CreateEntwatchHud(); void CreatePointOrient(); + void ToggleThirdPerson(float flDistance = -65.f); private: bool m_bAuthenticated; @@ -395,6 +398,7 @@ class ZEPlayer int m_iButtonWatchMode; CHandle m_hPointOrient; CHandle m_hEntwatchHud; + CHandle m_hCustomCamera; int m_iEntwatchHudMode; Color m_colorEntwatchHud; float m_flEntwatchHudX; diff --git a/src/topdefender.cpp b/src/topdefender.cpp index ddc7d026e..c9a4514ab 100644 --- a/src/topdefender.cpp +++ b/src/topdefender.cpp @@ -41,7 +41,7 @@ CConVar g_cvarTopDefenderPrint("cs2f_topdefender_print", FCVAR_NONE, "Whet CConVar g_cvarTopDefenderRate("cs2f_topdefender_rate", FCVAR_NONE, "How often TopDefender stats get updated", 1.0f, true, 0.1f, false, 0.0f); CConVar g_cvarTopDefenderThreshold("cs2f_topdefender_threshold", FCVAR_NONE, "Damage threshold for Top Defenders to be shown on round end", 1000, true, 0, false, 0); CConVar g_cvarTopDefenderScore("cs2f_topdefender_score", FCVAR_NONE, "Score given to the top defender", 5000, true, 0, false, 0); -CConVar g_cvarTopDefenderClanTag("cs2f_topdefender_clantag", FCVAR_NONE, "Clan tag given to the top defender", "[Top Defender]"); +CConVar g_cvarTopDefenderClanTag("cs2f_topdefender_clantag", FCVAR_NONE, "Clan tag given to the top defender", "Top Defender"); // Array sorting function bool SortTD(ZEPlayerHandle a, ZEPlayerHandle b) diff --git a/src/utils/khook_helpers.h b/src/utils/khook_helpers.h new file mode 100644 index 000000000..fd0048560 --- /dev/null +++ b/src/utils/khook_helpers.h @@ -0,0 +1,206 @@ +/** + * ============================================================================= + * CS2Fixes + * Copyright (C) 2023-2026 Source2ZE + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once +#include "../gameconfig.h" + +class CKHookBase +{ +public: + virtual void Configure() = 0; +}; + +extern std::vector& GetKHookList(); +void InitKHooks(); + +template +class CKHookFunction : public CKHookBase +{ +public: + using fnCallback = KHook::Return (*)(ARGS...); + + CKHookFunction(const char* pSignature, fnCallback cbPre, fnCallback cbPost) : + m_hook(cbPre, cbPost), m_pSignatureName(pSignature) + { + GetKHookList().push_back(this); + } + + virtual void Configure() override + { + auto address = g_GameConfig->ResolveSignature(m_pSignatureName); + + if (!address) + { + g_bRequiredInitLoaded = false; + return; + } + + m_hook.Configure(address); + + Message("Hooked %s at 0x%p\n", m_pSignatureName, address); + } + + RETURN CallOriginal(ARGS... args) + { + return m_hook.CallOriginal(args...); + } + +private: + KHook::Function m_hook; + const char* m_pSignatureName; +}; + +template +auto MakeKHookFunction(const char* pSignature, KHook::Return (*cbPre)(ARGS...), std::nullptr_t cbPost) +{ + return std::make_unique>(pSignature, cbPre, nullptr); +} + +template +auto MakeKHookFunction(const char* pSignature, std::nullptr_t cbPre, KHook::Return (*cbPost)(ARGS...)) +{ + return std::make_unique>(pSignature, nullptr, cbPost); +} + +template +auto MakeKHookFunction(const char* pSignature, KHook::Return (*cbPre)(ARGS...), KHook::Return (*cbPost)(ARGS...)) +{ + return std::make_unique>(pSignature, cbPre, cbPost); +} + +#define KHOOK_FUNCTION(name, pre, post) \ + static auto hook##name = MakeKHookFunction(#name, pre, post) + +template +class CKHookMember : public CKHookBase +{ +public: + using fnCallback = KHook::Return (*)(CLASS*, ARGS...); + + CKHookMember(const char* pSignature, fnCallback cbPre, fnCallback cbPost) : + m_hook(cbPre, cbPost), m_pSignatureName(pSignature) + { + GetKHookList().push_back(this); + } + + virtual void Configure() override + { + auto address = g_GameConfig->ResolveSignature(m_pSignatureName); + + if (!address) + { + g_bRequiredInitLoaded = false; + return; + } + + m_hook.Configure(address); + + Message("Hooked %s at 0x%p\n", m_pSignatureName, address); + } + + RETURN CallOriginal(CLASS* this_ptr, ARGS... args) + { + return m_hook.CallOriginal(this_ptr, args...); + } + +private: + KHook::Member m_hook; + const char* m_pSignatureName; +}; + +template +auto MakeKHookMember(const char* pSignature, KHook::Return (*cbPre)(CLASS*, ARGS...), std::nullptr_t cbPost) +{ + return std::make_unique>(pSignature, cbPre, nullptr); +} + +template +auto MakeKHookMember(const char* pSignature, std::nullptr_t cbPre, KHook::Return (*cbPost)(CLASS*, ARGS...)) +{ + return std::make_unique>(pSignature, nullptr, cbPost); +} + +template +auto MakeKHookMember(const char* pSignature, KHook::Return (*cbPre)(CLASS*, ARGS...), KHook::Return (*cbPost)(CLASS*, ARGS...)) +{ + return std::make_unique>(pSignature, cbPre, cbPost); +} + +#define KHOOK_MEMBER(name, pre, post) \ + static auto hook##name = MakeKHookMember(#name, pre, post) + +template +class CKHookVirtual : public CKHookBase +{ +public: + using fnCallback = KHook::Return (*)(CLASS*, ARGS...); + + CKHookVirtual(RETURN (CLASS::*pFunction)(ARGS...), CLASS*& pInstance, fnCallback cbPre, fnCallback cbPost) : + m_hook(pFunction, cbPre, cbPost), m_pInstance(pInstance) + { + GetKHookList().push_back(this); + } + + ~CKHookVirtual() + { + if (m_hook.IsActive()) + m_hook.Remove(m_pInstance); + } + + virtual void Configure() override + { + if (!m_pInstance) + { + g_bRequiredInitLoaded = false; + return; + } + + m_hook.Add(m_pInstance); + } + + RETURN CallOriginal(CLASS* this_ptr, ARGS... args) + { + return m_hook.CallOriginal(this_ptr, args...); + } + +private: + KHook::Virtual m_hook; + CLASS*& m_pInstance; +}; + +template +auto MakeKHookVirtual(RETURN (CLASS::*pFunction)(ARGS...), CLASS*& pInstance, KHook::Return (*cbPre)(CLASS*, ARGS...), std::nullptr_t cbPost) +{ + return std::make_unique>(pFunction, pInstance, cbPre, nullptr); +} + +template +auto MakeKHookVirtual(RETURN (CLASS::*pFunction)(ARGS...), CLASS*& pInstance, std::nullptr_t cbPre, KHook::Return (*cbPost)(CLASS*, ARGS...)) +{ + return std::make_unique>(pFunction, pInstance, nullptr, cbPost); +} + +template +auto MakeKHookVirtual(RETURN (CLASS::*pFunction)(ARGS...), CLASS*& pInstance, KHook::Return (*cbPre)(CLASS*, ARGS...), KHook::Return (*cbPost)(CLASS*, ARGS...)) +{ + return std::make_unique>(pFunction, pInstance, cbPre, cbPost); +} + +#define KHOOK_VIRTUAL(name, function, instance, pre, post) \ + static auto hook##name = MakeKHookVirtual(function, instance, pre, post) diff --git a/src/utils/module.h b/src/utils/module.h index 0bd831bff..543af18dd 100644 --- a/src/utils/module.h +++ b/src/utils/module.h @@ -31,13 +31,6 @@ #include #endif -enum SigError -{ - SIG_OK, - SIG_NOT_FOUND, - SIG_FOUND_MULTIPLE, -}; - // equivalent to FindSignature, but allows for multiple signatures to be found and iterated over class SignatureIterator { @@ -53,7 +46,7 @@ class SignatureIterator for (size_t i = 0; i < m_iSize; i++) { size_t Matches = 0; - while (*(m_pCurrent + i + Matches) == m_pSignature[Matches] || (allowWildcard && m_pSignature[Matches] == '\x2A')) + while (*(m_pCurrent + i + Matches) == m_pSignature[Matches] || (allowWildcard && m_pSignature[Matches] == '?')) { Matches++; if (Matches == m_iSigLength) @@ -108,40 +101,6 @@ class CModule Message("Initialized module %s base: 0x%p | size: %d\n", m_pszModule, m_base, m_size); } - void* FindSignature(const byte* pData, size_t iSigLength, int& error) - { - unsigned char* pMemory; - void* return_addr = nullptr; - error = 0; - - pMemory = (byte*)m_base; - - for (size_t i = 0; i < m_size; i++) - { - size_t Matches = 0; - while (i + Matches < m_size && (*(pMemory + i + Matches) == pData[Matches] || pData[Matches] == '\x2A')) - { - Matches++; - if (Matches == iSigLength) - { - if (return_addr) - { - error = SIG_FOUND_MULTIPLE; - return return_addr; - } - - return_addr = (void*)(pMemory + i); - break; - } - } - } - - if (!return_addr) - error = SIG_NOT_FOUND; - - return return_addr; - } - void* FindInterface(const char* name) { CreateInterfaceFn fn = (CreateInterfaceFn)dlsym(m_hModule, "CreateInterface"); diff --git a/src/virtual_hooks.cpp b/src/virtual_hooks.cpp new file mode 100644 index 000000000..d21fdb224 --- /dev/null +++ b/src/virtual_hooks.cpp @@ -0,0 +1,965 @@ +/** + * ============================================================================= + * CS2Fixes + * Copyright (C) 2023-2026 Source2ZE + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "virtual_hooks.h" +#include "adminsystem.h" +#include "cchecktransmitinfo.h" +#include "cfgparser.h" +#include "commands.h" +#include "common.h" +#include "cs2fixes.h" +#include "cs_gameevents.pb.h" +#include "cstrike15_usermessages.pb.h" +#include "ctimer.h" +#include "engine/IEngineService.h" +#include "entities.h" +#include "entity/ccsplayercontroller.h" +#include "entity/customhudlayout.h" +#include "entity/services.h" +#include "entitylistener.h" +#include "entitysystem.h" +#include "entwatch.h" +#include "eventlistener.h" +#include "gameconfig.h" +#include "gameevents.pb.h" +#include "icvar.h" +#include "idlemanager.h" +#include "iserver.h" +#include "khook_helpers.h" +#include "leader.h" +#include "map_votes.h" +#include "mapmigrations.h" +#include "module.h" +#include "networkstringtabledefs.h" +#include "panoramavote.h" +#include "playermanager.h" +#include "serversideclient.h" +#include "tier0/vprof.h" +#include "usermessages.pb.h" +#include "votemanager.h" +#include "zombiereborn.h" + +#include "tier0/memdbgon.h" + +class GameSessionConfiguration_t +{}; + +KHOOK_VIRTUAL(GameFrame, &IServerGameDLL::GameFrame, g_pSource2Server, nullptr, Hook_GameFrame_Post); +KHOOK_VIRTUAL(GameServerSteamAPIActivated, &IServerGameDLL::GameServerSteamAPIActivated, g_pSource2Server, Hook_GameServerSteamAPIActivated, nullptr); +KHOOK_VIRTUAL(ApplyGameSettings, &IServerGameDLL::ApplyGameSettings, g_pSource2Server, Hook_ApplyGameSettings, nullptr); +KHOOK_VIRTUAL(ClientActive, &IServerGameClients::ClientActive, g_pSource2GameClients, nullptr, Hook_ClientActive_Post); +KHOOK_VIRTUAL(ClientDisconnect, &IServerGameClients::ClientDisconnect, g_pSource2GameClients, nullptr, Hook_ClientDisconnect_Post); +KHOOK_VIRTUAL(ClientPutInServer, &IServerGameClients::ClientPutInServer, g_pSource2GameClients, nullptr, Hook_ClientPutInServer_Post); +KHOOK_VIRTUAL(ClientSettingsChanged, &IServerGameClients::ClientSettingsChanged, g_pSource2GameClients, Hook_ClientSettingsChanged, nullptr); +KHOOK_VIRTUAL(OnClientConnected, &IServerGameClients::OnClientConnected, g_pSource2GameClients, Hook_OnClientConnected, nullptr); +KHOOK_VIRTUAL(ClientConnect, &IServerGameClients::ClientConnect, g_pSource2GameClients, Hook_ClientConnect, nullptr); +KHOOK_VIRTUAL(ClientCommand, &IServerGameClients::ClientCommand, g_pSource2GameClients, Hook_ClientCommand, nullptr); +KHOOK_VIRTUAL(ClientSvcUserMessage, &IServerGameClients::ClientSvcUserMessage, g_pSource2GameClients, Hook_ClientSvcUserMessage, nullptr); +KHOOK_VIRTUAL(PostEventAbstract, &IGameEventSystem::PostEventAbstract, g_gameEventSystem, Hook_PostEventAbstract, nullptr); +KHOOK_VIRTUAL(StartupServer, &INetworkServerService::StartupServer, g_pNetworkServerService, nullptr, Hook_StartupServer_Post); +KHOOK_VIRTUAL(CheckTransmit, &ISource2GameEntities::CheckTransmit, g_pSource2GameEntities, nullptr, Hook_CheckTransmit_Post); +KHOOK_VIRTUAL(DispatchConCommand, &ICvar::DispatchConCommand, g_pCVar, Hook_DispatchConCommand, nullptr); +KHOOK_VIRTUAL(SwitchToLoop, &IEngineServiceMgr::SwitchToLoop, g_pEngineServiceMgr, Hook_SwitchToLoop, nullptr); + +KHook::Virtual createWorkshopMapGroupHook(0U, Hook_CreateWorkshopMapGroup, nullptr); +KHook::Virtual loadEventsFromFileHook(&IGameEventManager2::LoadEventsFromFile, Hook_LoadEventsFromFile, nullptr); +KHook::Virtual processVoiceDataHook(&CServerSideClient::ProcessVoiceData, Hook_ProcessVoiceData, nullptr); +KHook::Virtual setGameSpawnGroupMgrHook(&INetworkGameServer::SetGameSpawnGroupMgr, Hook_SetGameSpawnGroupMgr, nullptr); +KHook::Virtual*, bool> getTouchingListHook(0U, nullptr, Hook_GetTouchingList_Post); +KHook::Virtual checkMovingGroundHook(0U, Hook_CheckMovingGround, nullptr); +KHook::Virtual dropWeaponHook(0U, nullptr, Hook_DropWeapon_Post); +KHook::Virtual playerEquipUseHook(0U, Hook_PlayerEquipUse, nullptr); +KHook::Virtual playerEquipPrecacheHook(0U, nullptr, Hook_PlayerEquipPrecache_Post); +KHook::Virtual triggerGravityPrecacheHook(0U, nullptr, Hook_TriggerGravityPrecache_Post); +KHook::Virtual triggerGravityEndTouchHook(0U, nullptr, Hook_TriggerGravityEndTouch_Post); +KHook::Virtual onTakeDamageAliveHook(0U, Hook_OnTakeDamage_Alive, nullptr); +KHook::Virtual playerPawnTeleportHook(0U, Hook_CCSPlayerPawn_Teleport, Hook_CCSPlayerPawn_Teleport_Post); + +IGameEventManager2* g_pCGameEventManagerVTable = nullptr; +CVPhys2World* g_pCVPhys2WorldVTable = nullptr; +CCSPlayer_MovementServices* g_pCCSPlayer_MovementServicesVTable = nullptr; +CCSPlayer_WeaponServices* g_pCCSPlayer_WeaponServicesVTable = nullptr; +CGamePlayerEquip* g_pCGamePlayerEquipVTable = nullptr; +CTriggerGravity* g_pTriggerGravityVTable = nullptr; +CCSPlayerPawn* g_pCCSPlayerPawnVTable = nullptr; +CServerSideClient* g_pCServerSideClientVTable = nullptr; + +template +bool SetupVirtualHook(KHook::Virtual& hook, const char* name, CLASS* pInstance = nullptr) +{ + int offset = g_GameConfig->GetOffset(name); + if (offset == -1) + { + Panic("Failed to find offset for %s\n", name); + g_bRequiredInitLoaded = false; + return false; + } + + hook.Configure(offset); + + if (pInstance) + hook.Add(pInstance); + + return true; +} + +template +void SetupGlobalVirtualHook(KHook::Virtual& hook, CLASS*& pVTable, CModule* module, const char* className, const char* offsetName = nullptr) +{ + if (!pVTable) + pVTable = (CLASS*)module->FindVirtualTable(className); + + if (!pVTable) + { + Panic("Failed to find %s vtable\n", className); + g_bRequiredInitLoaded = false; + return; + } + + if (offsetName && !SetupVirtualHook(hook, offsetName)) + return; + + hook.AddGlobal((CLASS*)&pVTable); +} + +void InitVirtualHooks() +{ + SetupVirtualHook(createWorkshopMapGroupHook, "IGameTypes_CreateWorkshopMapGroup", g_pGameTypes); + + SetupGlobalVirtualHook(loadEventsFromFileHook, g_pCGameEventManagerVTable, modules::server, "CGameEventManager"); + SetupGlobalVirtualHook(processVoiceDataHook, g_pCServerSideClientVTable, modules::engine, "CServerSideClient"); + SetupGlobalVirtualHook(getTouchingListHook, g_pCVPhys2WorldVTable, modules::vphysics2, "CVPhys2World", "CVPhys2World::GetTouchingList"); + SetupGlobalVirtualHook(checkMovingGroundHook, g_pCCSPlayer_MovementServicesVTable, modules::server, "CCSPlayer_MovementServices", "CCSPlayer_MovementServices::CheckMovingGround"); + SetupGlobalVirtualHook(dropWeaponHook, g_pCCSPlayer_WeaponServicesVTable, modules::server, "CCSPlayer_WeaponServices", "CCSPlayer_WeaponServices::DropWeapon"); + SetupGlobalVirtualHook(playerEquipUseHook, g_pCGamePlayerEquipVTable, modules::server, "CGamePlayerEquip", "CBaseEntity::Use"); + SetupGlobalVirtualHook(playerEquipPrecacheHook, g_pCGamePlayerEquipVTable, modules::server, "CGamePlayerEquip", "CBaseEntity::Precache"); + SetupGlobalVirtualHook(triggerGravityPrecacheHook, g_pTriggerGravityVTable, modules::server, "CTriggerGravity", "CBaseEntity::Precache"); + SetupGlobalVirtualHook(triggerGravityEndTouchHook, g_pTriggerGravityVTable, modules::server, "CTriggerGravity", "CBaseEntity::EndTouch"); + SetupGlobalVirtualHook(onTakeDamageAliveHook, g_pCCSPlayerPawnVTable, modules::server, "CCSPlayerPawn", "CCSPlayerPawn::OnTakeDamage_Alive"); + SetupGlobalVirtualHook(playerPawnTeleportHook, g_pCCSPlayerPawnVTable, modules::server, "CCSPlayerPawn", "Teleport"); +} + +void RemoveVirtualHooks() +{ + loadEventsFromFileHook.RemoveGlobal((IGameEventManager2*)&g_pCGameEventManagerVTable); + processVoiceDataHook.RemoveGlobal((CServerSideClient*)&g_pCServerSideClientVTable); + setGameSpawnGroupMgrHook.Remove(GetNetworkGameServer()); + createWorkshopMapGroupHook.Remove(g_pGameTypes); + getTouchingListHook.RemoveGlobal((CVPhys2World*)&g_pCVPhys2WorldVTable); + checkMovingGroundHook.RemoveGlobal((CCSPlayer_MovementServices*)&g_pCCSPlayer_MovementServicesVTable); + dropWeaponHook.RemoveGlobal((CCSPlayer_WeaponServices*)&g_pCCSPlayer_WeaponServicesVTable); + playerEquipUseHook.RemoveGlobal((CGamePlayerEquip*)&g_pCGamePlayerEquipVTable); + playerEquipPrecacheHook.RemoveGlobal((CGamePlayerEquip*)&g_pCGamePlayerEquipVTable); + triggerGravityPrecacheHook.RemoveGlobal((CTriggerGravity*)&g_pTriggerGravityVTable); + triggerGravityEndTouchHook.RemoveGlobal((CTriggerGravity*)&g_pTriggerGravityVTable); + onTakeDamageAliveHook.RemoveGlobal((CCSPlayerPawn*)&g_pCCSPlayerPawnVTable); + playerPawnTeleportHook.RemoveGlobal((CCSPlayerPawn*)&g_pCCSPlayerPawnVTable); +} + +KHook::Return Hook_GameFrame_Post(IServerGameDLL* pThis, bool simulating, bool bFirstTick, bool bLastTick) +{ + /** + * simulating: + * *********** + * true | game is ticking + * false | game is not ticking + */ + + VPROF_BUDGET("CS2Fixes::Hook_GameFramePost", "CS2FixesPerFrame"); + + if (!GetGlobals()) + return {KHook::Action::Ignore}; + + if (simulating && g_bHasTicked) + g_flUniversalTime += GetGlobals()->curtime - g_flLastTickedTime; + + g_flLastTickedTime = GetGlobals()->curtime; + g_bHasTicked = true; + + RunTimers(); + EntityHandler_OnGameFramePost(simulating, GetGlobals()->tickcount); + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_GameServerSteamAPIActivated(IServerGameDLL* pThis) +{ + g_playerManager->OnSteamAPIActivated(); + + if (g_cvarVoteManagerEnable.Get() && !g_pMapVoteSystem->IsMapListLoaded()) + g_pMapVoteSystem->LoadMapList(); + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_ApplyGameSettings(IServerGameDLL* pThis, KeyValues* pKV) +{ + const char* pszMapName; + uint64 iWorkshopId; + + if (pKV->FindKey("launchoptions") && pKV->FindKey("launchoptions")->FindKey("levelname")) + pszMapName = pKV->FindKey("launchoptions")->GetString("levelname"); + else + pszMapName = ""; + + if (pKV->FindKey("launchoptions") && pKV->FindKey("launchoptions")->FindKey("customgamemode")) + iWorkshopId = pKV->FindKey("launchoptions")->GetUint64("customgamemode"); + else + iWorkshopId = 0; + + g_pCfgParser->ApplyGameSettings(pszMapName); + g_pMapVoteSystem->ApplyGameSettings(pszMapName, iWorkshopId); + g_pMapMigrations->ApplyGameSettings(iWorkshopId); + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_ClientActive_Post(IServerGameClients* pThis, CPlayerSlot slot, bool bLoadGame, const char* pszName, uint64 xuid) +{ + Message("Hook_ClientActive(%d, %d, \"%s\", %lli)\n", slot, bLoadGame, pszName, xuid); + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_ClientDisconnect_Post(IServerGameClients* pThis, CPlayerSlot slot, ENetworkDisconnectionReason reason, const char* pszName, uint64 xuid, const char* pszNetworkID) +{ + Message("Hook_ClientDisconnect(%d, %d, \"%s\", %lli)\n", slot, reason, pszName, xuid); + + CCSPlayerController* player = CCSPlayerController::FromSlot(slot); + + if (g_cvarEnableZR.Get()) + { + // Controller team num is not valid post-disconnect, so just check both teams + if (!ZR_CheckTeamWinConditions(CS_TEAM_T)) + ZR_CheckTeamWinConditions(CS_TEAM_CT); + } + + ZEPlayer* pPlayer = g_playerManager->GetPlayer(slot); + + if (!pPlayer) + return {KHook::Action::Ignore}; + + // Dont add to c_listdc clients that are downloading MultiAddonManager stuff or were present during a map change + if (reason != NETWORK_DISCONNECT_LOOPSHUTDOWN && reason != NETWORK_DISCONNECT_SHUTDOWN) + g_pAdminSystem->AddDisconnectedPlayer(pszName, xuid, pPlayer ? pPlayer->GetIpAddress() : ""); + + CCSCustomHudLayout::OnClientDisconnect(slot.Get()); + g_playerManager->OnClientDisconnect(slot); + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_ClientPutInServer_Post(IServerGameClients* pThis, CPlayerSlot slot, const char* pszName, int type, uint64 xuid) +{ + Message("Hook_ClientPutInServer(%d, \"%s\", %d, %d, %lli)\n", slot, pszName, type, xuid); + + if (!g_playerManager->GetPlayer(slot)) + return {KHook::Action::Ignore}; + + g_playerManager->OnClientPutInServer(slot); + + if (g_cvarEnableZR.Get()) + ZR_Hook_ClientPutInServer(slot, pszName, type, xuid); + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_ClientSettingsChanged(IServerGameClients* pThis, CPlayerSlot slot) +{ +#ifdef _DEBUG + Message("Hook_ClientSettingsChanged(%d)\n", slot); +#endif + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_OnClientConnected(IServerGameClients* pThis, CPlayerSlot slot, const char* pszName, uint64 xuid, const char* pszNetworkID, const char* pszAddress, bool bFakePlayer) +{ + Message("Hook_OnClientConnected(%d, \"%s\", %lli, \"%s\", \"%s\", %d)\n", slot, pszName, xuid, pszNetworkID, pszAddress, bFakePlayer); + + static ConVarRefAbstract tv_name("tv_name"); + const char* pszTvName = tv_name.GetString().Get(); + + // Ideally we would use CServerSideClient::IsHLTV().. but it doesn't work :( + if (bFakePlayer && V_strcmp(pszName, pszTvName)) + g_playerManager->OnBotConnected(slot); + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_ClientConnect(IServerGameClients* pThis, CPlayerSlot slot, const char* pszName, uint64 xuid, const char* pszNetworkID, bool unk1, CBufferString* pRejectReason) +{ + Message("Hook_ClientConnect(%d, \"%s\", %lli, \"%s\", %d, \"%s\")\n", slot, pszName, xuid, pszNetworkID, unk1, pRejectReason->Get()); + + // Player is banned + if (!g_playerManager->OnClientConnected(slot, xuid, pszNetworkID)) + return {KHook::Action::Supersede, false}; + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_ClientCommand(IServerGameClients* pThis, CPlayerSlot slot, const CCommand& args) +{ +#ifdef _DEBUG + Message("Hook_ClientCommand(%d, \"%s\")\n", slot, args.GetCommandString()); +#endif + + ZEPlayer* pPlayer = g_playerManager->GetPlayer(slot); + + if (g_cvarIdleKickTime.Get() > 0.0f) + { + CCSPlayerController* pController = CCSPlayerController::FromSlot(slot); + if (pPlayer && pController) + { + // Only spectators doing spectator commands reset idle timer + if (pController->m_iTeamNum() == CS_TEAM_SPECTATOR && (!V_stricmp(args[0], "spec_mode") || !V_stricmp(args[0], "spec_prev") || !V_stricmp(args[0], "spec_next"))) + pPlayer->UpdateLastInputTime(); + } + } + + if (g_cvarVoteManagerEnable.Get() && V_stricmp(args[0], "endmatch_votenextmap") == 0 && args.ArgC() == 2) + { + if (g_pMapVoteSystem->RegisterPlayerVote(slot, atoi(args[1]))) + return {KHook::Action::Ignore}; + else + return {KHook::Action::Supersede}; + } + + if (g_cvarEnableZR.Get() && slot != -1 && !V_strnicmp(args.Arg(0), "jointeam", 8)) + { + ZR_Hook_ClientCommand_JoinTeam(slot, args); + return {KHook::Action::Supersede}; + } + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_ClientSvcUserMessage(IServerGameClients* pThis, CPlayerSlot slot, int um_type, uint32 size, const void* buf) +{ + auto pController = CCSPlayerController::FromSlot(slot); + + if (!pController) + return {KHook::Action::Ignore}; + + if (um_type == CS_UM_CustomHudClicked) + { + CCSUsrMsg_CustomHudClicked message; + + if (message.ParseFromArray(buf, size)) + { + CHandle hLayout = CBaseHandle::FromPackedInt(message.custom_hud_layout()); + + if (hLayout.Get()) + hLayout->OnClick(pController, message.button_id()); + } + } + + return {KHook::Action::Ignore}; +} + +CConVar g_cvarBlockParticleMsgs("cs2f_block_particle_msgs", FCVAR_NONE, "Whether to block CUserMsg_ParticleManager messages to fix lag/crashes, experimental", false); + +KHook::Return Hook_PostEventAbstract(IGameEventSystem* pThis, CSplitScreenSlot nSlot, bool bLocalOnly, int nClientCount, const uint64* clients, + INetworkMessageInternal* pEvent, const CNetMessage* pData, unsigned long nSize, NetChannelBufType_t bufType) +{ + // Message( "Hook_PostEvent(%d, %d, %d, %lli)\n", nSlot, bLocalOnly, nClientCount, clients ); + NetMessageInfo_t* info = pEvent->GetNetMessageInfo(); + + if (g_cvarEnableStopSound.Get() && info->m_MessageId == GE_FireBulletsId) + { + if (g_playerManager->GetSilenceSoundMask()) + { + // Post the silenced sound to those who use silencesound + // Creating a new event object requires us to include the protobuf c files which I didn't feel like doing yet + // So instead just edit the event in place and reset later + auto msg = const_cast(pData)->ToPB(); + + int32_t weapon_id = msg->weapon_id(); + int32_t sound_type = msg->sound_type(); + int32_t item_def_index = msg->item_def_index(); + + // original weapon_id will override new settings if not removed + msg->set_weapon_id(0); + msg->set_sound_type(9); + msg->set_item_def_index(61); // weapon_usp_silencer + + uint64 clientMask = *(uint64*)clients & g_playerManager->GetSilenceSoundMask(); + + hookPostEventAbstract->CallOriginal(pThis, nSlot, bLocalOnly, nClientCount, &clientMask, pEvent, msg, nSize, bufType); + + msg->set_weapon_id(weapon_id); + msg->set_sound_type(sound_type); + msg->set_item_def_index(item_def_index); + } + + // Filter out people using stop/silence sound from the original event + *(uint64*)clients &= ~g_playerManager->GetStopSoundMask(); + *(uint64*)clients &= ~g_playerManager->GetSilenceSoundMask(); + } + else if (info->m_MessageId == GE_PlaceDecalEvent) + { + *(uint64*)clients &= ~g_playerManager->GetStopDecalsMask(); + } + else if (info->m_MessageId == GE_Source1LegacyGameEvent) + { + if (g_cvarEnableLeader.Get()) + Leader_PostEventAbstract_Source1LegacyGameEvent(clients, pData); + } + else if (info->m_MessageId == UM_Shake) + { + auto pPBData = const_cast(pData)->ToPB(); + if (g_cvarMaxShakeAmp.Get() >= 0 && pPBData->amplitude() > g_cvarMaxShakeAmp.Get()) + pPBData->set_amplitude(g_cvarMaxShakeAmp.Get()); + + // remove client with noshake from the event + if (g_cvarEnableNoShake.Get()) + *(uint64*)clients &= ~g_playerManager->GetNoShakeMask(); + } + else if (info->m_MessageId == GE_SosStartSoundEvent) + { + auto msg = const_cast(pData)->ToPB(); + + if (g_cvarEnableZR.Get()) + ZR_PostEventAbstract_SosStartSoundEvent(clients, msg); + + if (g_cvarEnableStopSound.Get()) + { + static std::set soundEventHashes; + + ExecuteOnce( + soundEventHashes.insert(GetSoundEventHash("Weapon_sg556.ZoomIn")); + soundEventHashes.insert(GetSoundEventHash("Weapon_sg556.ZoomOut")); + soundEventHashes.insert(GetSoundEventHash("Weapon_AUG.ZoomIn")); + soundEventHashes.insert(GetSoundEventHash("Weapon_AUG.ZoomOut")); + soundEventHashes.insert(GetSoundEventHash("Weapon_SSG08.Zoom")); + soundEventHashes.insert(GetSoundEventHash("Weapon_SSG08.ZoomOut")); + soundEventHashes.insert(GetSoundEventHash("Weapon_SCAR20.Zoom")); + soundEventHashes.insert(GetSoundEventHash("Weapon_SCAR20.ZoomOut")); + soundEventHashes.insert(GetSoundEventHash("Weapon_G3SG1.Zoom")); + soundEventHashes.insert(GetSoundEventHash("Weapon_G3SG1.ZoomOut")); + soundEventHashes.insert(GetSoundEventHash("Weapon_AWP.Zoom")); + soundEventHashes.insert(GetSoundEventHash("Weapon_AWP.ZoomOut")); + soundEventHashes.insert(GetSoundEventHash("Weapon_Revolver.Prepare")); + soundEventHashes.insert(GetSoundEventHash("Weapon.AutoSemiAutoSwitch"));); + + if (!soundEventHashes.contains(msg->soundevent_hash())) + return {KHook::Action::Ignore}; + + uint64 stopSoundMask = g_playerManager->GetStopSoundMask(); + uint64 silenceSoundMask = g_playerManager->GetSilenceSoundMask(); + + if (!msg->has_source_entity_index()) + return {KHook::Action::Ignore}; + + CBaseEntity* pSourceEntity = (CBaseEntity*)g_pEntitySystem->GetEntityInstance(CEntityIndex(msg->source_entity_index())); + int playerSlot = -1; + + if (!pSourceEntity) + return {KHook::Action::Ignore}; + + if (pSourceEntity->IsPawn() && ((CCSPlayerPawn*)pSourceEntity)->GetController()) + { + playerSlot = ((CCSPlayerPawn*)pSourceEntity)->GetController()->GetPlayerSlot(); + } + else if (!V_strncasecmp(pSourceEntity->GetClassname(), "weapon_", 7)) + { + CCSPlayerPawn* pPawn = (CCSPlayerPawn*)pSourceEntity->m_hOwnerEntity().Get(); + + if (pPawn && pPawn->IsPawn() && pPawn->GetController()) + playerSlot = pPawn->GetController()->GetPlayerSlot(); + } + + // Remove player who triggered this sound from masks + // Because some of these sounds never get played locally (Zoom's, Knife Hit/Stab) + if (playerSlot != -1 && g_playerManager->IsPlayerUsingStopSound(playerSlot)) + stopSoundMask &= ~((uint64)1 << playerSlot); + + if (playerSlot != -1 && g_playerManager->IsPlayerUsingSilenceSound(playerSlot)) + silenceSoundMask &= ~((uint64)1 << playerSlot); + + // Filter out people using stop/silence sound from hearing this sound from other players + *(uint64*)clients &= ~stopSoundMask; + *(uint64*)clients &= ~silenceSoundMask; + } + } + else if (info->m_MessageId == UM_ParticleManager) + { + // These messages were previously unused, but recently started being used for weapon particles in the AG2 update + // Unfortunately, this new system seems extremely unoptimized for 64 players, and was causing severe performance issues & vector overflow client crashes + if (g_cvarBlockParticleMsgs.Get()) + *(uint64*)clients = 0; + } + + return {KHook::Action::Ignore}; +} + +CConVar g_cvarMotdUrl("cs2f_motd_url", FCVAR_NONE, "Server MOTD URL, shows up as a \"Server Website\" button in scoreboard", ""); + +KHook::Return Hook_StartupServer_Post(INetworkServerService* pThis, const GameSessionConfiguration_t& config, ISource2WorldSession* pSession, const char* pszMapName) +{ + g_pEntitySystem = GameEntitySystem(); + g_pEntitySystem->AddListenerEntity(g_pEntityListener); + + if (GetNetworkGameServer()) + setGameSpawnGroupMgrHook.Add(GetNetworkGameServer()); + + Message("Hook_StartupServer: %s\n", pszMapName); + + RegisterEventListeners(); + + if (g_bHasTicked) + RemoveTimers(TIMERFLAG_MAP); + + g_bHasTicked = false; + + g_pPanoramaVoteHandler->Reset(); + g_pVoteManager->VoteManager_Init(); + g_pIdleSystem->Reset(); + + INetworkStringTable* pInfoPanelTable = g_pNetworkStringTableServer->FindTable("InfoPanel"); + + if (pInfoPanelTable && V_strcmp(g_cvarMotdUrl.Get(), "")) + { + SetStringUserDataRequest_t pUserData; + pUserData.m_pRawData = (void*)g_cvarMotdUrl.Get().Get(); + pUserData.m_cbDataSize = g_cvarMotdUrl.Get().Length() + 1; + + pInfoPanelTable->AddString(true, "motd", &pUserData); + } + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_CheckTransmit_Post(ISource2GameEntities* pThis, CCheckTransmitInfo** ppInfoList, int infoCount, CBitVec<16384>& unionTransmitEdicts, + CBitVec<16384>&, const Entity2Networkable_t** pNetworkables, const uint16* pEntityIndicies, int nEntities) +{ + if (!g_pEntitySystem || !GetGlobals()) + return {KHook::Action::Ignore}; + + VPROF("CS2Fixes::Hook_CheckTransmit"); + + for (int i = 0; i < infoCount; i++) + { + auto& pInfo = (CCheckTransmitInfoExtended*&)(ppInfoList[i]); + CCSPlayerController* pSelfController = CCSPlayerController::FromSlot(pInfo->m_nPlayerSlot); + + if (!pSelfController || !pSelfController->IsConnected()) + continue; + + auto pSelfZEPlayer = g_playerManager->GetPlayer(pInfo->m_nPlayerSlot); + + if (!pSelfZEPlayer) + continue; + + for (int j = 0; j < GetGlobals()->maxClients; j++) + { + CCSPlayerController* pController = CCSPlayerController::FromSlot(j); + // Always transmit to themselves + if (!pController || pController->m_bIsHLTV || j == pInfo->m_nPlayerSlot.Get()) + continue; + + // Don't transmit other players' flashlights + CBarnLight* pFlashLight = pController->IsConnected() ? g_playerManager->GetPlayer(j)->GetFlashLight() : nullptr; + + if (!g_cvarFlashLightTransmitOthers.Get() && pFlashLight) + pInfo->m_pTransmitEntity->Clear(pFlashLight->entindex()); + + if (g_cvarEnableEntWatch.Get() && g_pEWHandler->IsConfigLoaded()) + { + // Don't transmit other players' entwatch hud + CPointWorldText* pHud = pController->IsConnected() ? g_playerManager->GetPlayer(j)->GetEntwatchHud() : nullptr; + if (pHud) + pInfo->m_pTransmitEntity->Clear(pHud->entindex()); + } + + // Always transmit other players if spectating + if (!g_cvarEnableHide.Get() || pSelfController->GetPawnState() == STATE_OBSERVER_MODE) + continue; + + // Get the actual pawn as the player could be currently spectating + CCSPlayerPawn* pPawn = pController->GetPlayerPawn(); + + if (!pPawn) + continue; + + // Do not hide leaders or item holders to other players + ZEPlayer* pOtherZEPlayer = g_playerManager->GetPlayer(j); + if (pSelfZEPlayer->ShouldBlockTransmit(j) && pOtherZEPlayer && !pOtherZEPlayer->IsLeader() && g_pEWHandler->FindItemInstanceByOwner(j, false, 0) == -1) + { + pInfo->m_pTransmitEntity->Clear(pPawn->entindex()); + pInfo->m_pTransmitNonPlayers->Set(pPawn->entindex()); + } + } + + // Don't transmit glow model to it's owner + CBaseModelEntity* pGlowModel = pSelfZEPlayer->GetGlowModel(); + + if (pGlowModel) + pInfo->m_pTransmitEntity->Clear(pGlowModel->entindex()); + } + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_DispatchConCommand(ICvar* pThis, ConCommandRef cmdHandle, const CCommandContext& ctx, const CCommand& args) +{ + VPROF_BUDGET("CS2Fixes::Hook_DispatchConCommand", "ConCommands"); + + if (!g_pEntitySystem) + return {KHook::Action::Ignore}; + + auto iCommandPlayerSlot = ctx.GetPlayerSlot(); + + if (!g_cvarEnableCommands.Get()) + return {KHook::Action::Ignore}; + + bool bSay = !V_stricmp(args.Arg(0), "say"); + bool bTeamSay = !V_stricmp(args.Arg(0), "say_team"); + + if (iCommandPlayerSlot != -1 && (bSay || bTeamSay)) + { + CCSPlayerController* pController = CCSPlayerController::FromSlot(iCommandPlayerSlot); + ZEPlayer* pPlayer = pController ? pController->GetZEPlayer() : nullptr; + + // Block chat messages from players not fully ingame, can be interpreted as console messages + if (!pPlayer || !pPlayer->IsInGame()) + { + Message("Blocked chat message from user ID %i not fully in game\n", g_pEngineServer2->GetPlayerUserId(iCommandPlayerSlot).Get()); + return {KHook::Action::Supersede}; + } + + bool bGagged = pPlayer->IsGagged(); + bool bFlooding = pPlayer->IsFlooding(); + bool bIsAdmin = pPlayer->IsAdminFlagSet(ADMFLAG_GENERIC); + bool bAdminChat = bTeamSay && *args[1] == '@'; + bool bSilent = *args[1] == '/' || bAdminChat; + bool bCommand = *args[1] == '!' || *args[1] == '/'; + + // Chat messages should generate events regardless + if (pController) + { + IGameEvent* pEvent = g_gameEventManager->CreateEvent("player_chat"); + + if (pEvent) + { + pEvent->SetBool("teamonly", bTeamSay); + pEvent->SetInt("userid", pController->GetPlayerSlot()); + pEvent->SetString("text", args[1]); + + g_gameEventManager->FireEvent(pEvent, true); + } + } + + if (!bGagged && !bSilent && !bFlooding) + { + hookDispatchConCommand->CallOriginal(pThis, cmdHandle, ctx, args); + + // Reset idle time if message is sent to chat + if (g_cvarIdleKickTime.Get() > 0.0f) + pPlayer->UpdateLastInputTime(); + } + else if (bFlooding) + { + if (pController) + ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "You are flooding the server!"); + } + else if (bAdminChat && GetGlobals()) // Admin chat can be sent by anyone but only seen by admins, use flood protection here too + { + // HACK: At this point, we can safely modify the arg buffer as it won't be passed anywhere else + // The string here is originally ("@foo bar"), trim it to be (foo bar) + char* pszMessage = (char*)(args.ArgS() + 2); + pszMessage[V_strlen(pszMessage) - 1] = 0; + + for (int i = 0; i < GetGlobals()->maxClients; i++) + { + ZEPlayer* pPlayer = g_playerManager->GetPlayer(i); + + if (!pPlayer) + continue; + + if (i == iCommandPlayerSlot.Get() || pPlayer->IsAdminFlagSet(ADMFLAG_GENERIC)) + ClientPrint(CCSPlayerController::FromSlot(i), HUD_PRINTTALK, " \4(%sADMINS) %s:\6 %s", bIsAdmin ? "" : "TO ", pController->GetPlayerName().c_str(), pszMessage); + } + } + + // Finally, run the chat command if it is one, so anything will print after the player's message + if (bCommand) + { + char* pszMessage = (char*)(args.ArgS() + 1); + + if (pszMessage[0] == '"' || pszMessage[0] == '!' || pszMessage[0] == '/') + pszMessage += 1; + + // Host_Say at some point removes the trailing " for whatever reason, so we only remove if it was never called + if ((bGagged || bSilent || bFlooding) && pszMessage[V_strlen(pszMessage) - 1] == '"') + pszMessage[V_strlen(pszMessage) - 1] = '\0'; + + ParseChatCommand(pszMessage, pController); + } + + return {KHook::Action::Supersede}; + } + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_SwitchToLoop(IEngineServiceMgr* pThis, const char* pszLoopModeName, KeyValues* pKV, uint32 nId, const char* pszAddonName, bool bUnk) +{ + if (pKV && !V_strcmp(pszLoopModeName, "levelload")) + { + g_pCfgParser->PreLevelLoad(pKV->GetString("levelname", "")); + g_pMapMigrations->PreLevelLoad(pKV->GetUint64("customgamemode", 0)); + } + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_CreateWorkshopMapGroup(IGameTypes* pThis, const char* name, const CUtlStringList& mapList) +{ + if (g_cvarVoteManagerEnable.Get() && g_pMapVoteSystem->IsMapListLoaded()) + return KHook::Recall(nullptr, {KHook::Action::Ignore}, pThis, name, g_pMapVoteSystem->CreateWorkshopMapGroup()); + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_LoadEventsFromFile(IGameEventManager2* pThis, const char* filename, bool bSearchAll) +{ + ExecuteOnce(g_gameEventManager = pThis); + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_ProcessVoiceData(CServerSideClient* pClient, const CCLCMsg_VoiceData_t& msg) +{ + ZEPlayer* pPlayer = g_playerManager->GetPlayer(pClient->GetPlayerSlot()); + + if (!pPlayer) + return {KHook::Action::Ignore}; + + if (pPlayer->IsMuted()) + return {KHook::Action::Supersede, true}; + + if (GetGlobals()) + pPlayer->SetLastVoiceTime(GetGlobals()->curtime); + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_SetGameSpawnGroupMgr(INetworkGameServer* pThis, IGameSpawnGroupMgr* pSpawnGroupMgr) +{ + // This also resets our stored pointer on deletion, since null gets passed into this function, nice! + g_pSpawnGroupMgr = (CSpawnGroupMgrGameSystem*)pSpawnGroupMgr; + + return {KHook::Action::Ignore}; +} + +CConVar g_cvarFixPhysicsPlayerShuffle("cs2f_shuffle_player_physics_sim", FCVAR_NONE, "Whether to enable shuffle player list in physics simulate", false); + +struct TouchLinked_t +{ + uint32_t TouchFlags; + +private: + uint8_t padding_0[20]; + +public: + CBaseHandle SourceHandle; + CBaseHandle TargetHandle; + +private: + uint8_t padding_1[224]; + +public: + [[nodiscard]] bool IsUnTouching() const + { + return !!(TouchFlags & 0x10); + } + + [[nodiscard]] bool IsTouching() const + { + return (!!(TouchFlags & 4)) || (!!(TouchFlags & 8)); + } +}; +static_assert(sizeof(TouchLinked_t) == 256, "Touch_t size mismatch"); +KHook::Return Hook_GetTouchingList_Post(CVPhys2World* pThis, CUtlVector* pList, bool unknown) +{ + if (!g_cvarFixPhysicsPlayerShuffle.Get() || pList->Count() <= 1) + return {KHook::Action::Ignore}; + + // [Kxnrl] + // seems it sorted by flags? + + if (GetGlobals()) + std::srand(GetGlobals()->tickcount); + + // Fisher-Yates shuffle + + std::vector touchingLinks; + std::vector unTouchLinks; + + FOR_EACH_VEC(*pList, i) + { + const auto& link = pList->Element(i); + if (link.IsUnTouching()) + unTouchLinks.push_back(link); + else + touchingLinks.push_back(link); + } + + if (touchingLinks.size() <= 1) + return {KHook::Action::Ignore}; + + for (size_t i = touchingLinks.size() - 1; i > 0; --i) + { + const auto j = std::rand() % (i + 1); + std::swap(touchingLinks[i], touchingLinks[j]); + } + + pList->Purge(); + + for (const auto& link : touchingLinks) + pList->AddToTail(link); + for (const auto& link : unTouchLinks) + pList->AddToTail(link); + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_CheckMovingGround(CCSPlayer_MovementServices* pThis, double frametime) +{ + CCSPlayerPawn* pPawn = pThis->GetPawn(); + + if (!pPawn || !GetGlobals()) + return {KHook::Action::Ignore}; + + CCSPlayerController* pController = pPawn->GetOriginalController(); + + if (!pController) + return {KHook::Action::Ignore}; + + int iSlot = pController->GetPlayerSlot(); + + static int aPlayerTicks[MAXPLAYERS] = {0}; + + // The point of doing this is to avoid running the function (and applying/resetting basevelocity) multiple times per tick + // This can happen when the client or server lags + if (aPlayerTicks[iSlot] == GetGlobals()->tickcount) + return {KHook::Action::Supersede}; + + aPlayerTicks[iSlot] = GetGlobals()->tickcount; + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_DropWeapon_Post(CCSPlayer_WeaponServices* pThis, CBasePlayerWeapon* pWeapon, Vector* pVecTarget, Vector* pVelocity) +{ + if (g_cvarEnableEntWatch.Get()) + EW_DropWeapon(pThis, pWeapon); + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_PlayerEquipUse(CGamePlayerEquip* pThis, InputData_t* pInput) +{ + CGamePlayerEquipHandler::Use(pThis, pInput); + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_PlayerEquipPrecache_Post(CGamePlayerEquip* pThis, CEntityPrecacheContext* param) +{ + const auto kv = param->m_pKeyValues; + CGamePlayerEquipHandler::OnPrecache(pThis, kv); + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_TriggerGravityPrecache_Post(CTriggerGravity* pThis, CEntityPrecacheContext* param) +{ + const auto kv = param->m_pKeyValues; + CTriggerGravityHandler::OnPrecache(pThis, kv); + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_TriggerGravityEndTouch_Post(CTriggerGravity* pThis, CBaseEntity* pOther) +{ + CTriggerGravityHandler::OnEndTouch(pThis, pOther); + + return {KHook::Action::Ignore}; +} + +CConVar g_cvarDropMapWeapons("cs2f_drop_map_weapons", FCVAR_NONE, "Whether to force drop map-spawned weapons on death", false); + +KHook::Return Hook_OnTakeDamage_Alive(CCSPlayerPawn* pPawn, CTakeDamageResult* pDamageResult) +{ + if (g_cvarEnableZR.Get() && ZR_Hook_OnTakeDamage_Alive(pDamageResult->m_pOriginatingInfo, pPawn)) + { + pDamageResult->m_bWasDamageSuppressed = true; + pDamageResult->m_flDamageDealt = 0.0f; + return {KHook::Action::Supersede, false}; + } + + // This is a shit place to be doing this, but player_death event is too late and there is no pre-hook alternative + // Check if this is going to kill the player + if (g_cvarDropMapWeapons.Get() && pPawn && pPawn->m_iHealth() <= 0) + { + if (g_cvarEnableEntWatch.Get()) + { + CCSPlayerController* pController = pPawn->GetOriginalController(); + if (pController) + EW_PlayerDeathPre(pController); + } + + pPawn->DropMapWeapons(); + } + + return {KHook::Action::Ignore}; +} + +float g_fTeleportXAngle; + +KHook::Return Hook_CCSPlayerPawn_Teleport(CCSPlayerPawn* pPawn, const Vector* pPosition, const QAngle* pAngles, const Vector* pVelocity) +{ + if (!pAngles) + return {KHook::Action::Ignore}; + + g_fTeleportXAngle = pAngles->x; + + QAngle* pCastAngles = const_cast(pAngles); + + // Post-AG2, changing x or z angles via Teleport on a playermodel will bug out, go through SnapViewAngles for x later instead + pCastAngles->x = 0.0f; + pCastAngles->z = 0.0f; + + return {KHook::Action::Ignore}; +} + +KHook::Return Hook_CCSPlayerPawn_Teleport_Post(CCSPlayerPawn* pPawn, const Vector* pPosition, const QAngle* pAngles, const Vector* pVelocity) +{ + if (!pAngles) + return {KHook::Action::Ignore}; + + // Revert the x edit, z should always be 0 + QAngle* pCastAngles = const_cast(pAngles); + pCastAngles->x = g_fTeleportXAngle; + + pPawn->SnapViewAngles(pCastAngles); + return {KHook::Action::Ignore}; +} diff --git a/src/virtual_hooks.h b/src/virtual_hooks.h new file mode 100644 index 000000000..46b120428 --- /dev/null +++ b/src/virtual_hooks.h @@ -0,0 +1,75 @@ +/** + * ============================================================================= + * CS2Fixes + * Copyright (C) 2023-2026 Source2ZE + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#include "cs2_sdk/netmessages.h" +#include "engine/igameeventsystem.h" +#include "entity/cgamerules.h" +#include "igameevents.h" +#include "iserver.h" +#include "khook.hpp" + +class CCSPlayer_MovementServices; +class CServerSideClient; +struct TouchLinked_t; +class CCSPlayer_WeaponServices; +class CBasePlayerWeapon; +class CGamePlayerEquip; +class CCSPlayerPawn; +class CVPhys2World; +class CTriggerGravity; +class CGameConfig; +class IEngineServiceMgr; + +void InitVirtualHooks(); +void RemoveVirtualHooks(); + +KHook::Return Hook_GameFrame_Post(IServerGameDLL* pThis, bool simulating, bool bFirstTick, bool bLastTick); +KHook::Return Hook_GameServerSteamAPIActivated(IServerGameDLL* pThis); +KHook::Return Hook_ApplyGameSettings(IServerGameDLL* pThis, KeyValues* pKV); +KHook::Return Hook_ClientActive_Post(IServerGameClients* pThis, CPlayerSlot slot, bool bLoadGame, const char* pszName, uint64 xuid); +KHook::Return Hook_ClientDisconnect_Post(IServerGameClients* pThis, CPlayerSlot slot, ENetworkDisconnectionReason reason, const char* pszName, uint64 xuid, const char* pszNetworkID); +KHook::Return Hook_ClientPutInServer_Post(IServerGameClients* pThis, CPlayerSlot slot, const char* pszName, int type, uint64 xuid); +KHook::Return Hook_ClientSettingsChanged(IServerGameClients* pThis, CPlayerSlot slot); +KHook::Return Hook_OnClientConnected(IServerGameClients* pThis, CPlayerSlot slot, const char* pszName, uint64 xuid, const char* pszNetworkID, const char* pszAddress, bool bFakePlayer); +KHook::Return Hook_ClientConnect(IServerGameClients* pThis, CPlayerSlot slot, const char* pszName, uint64 xuid, const char* pszNetworkID, bool unk1, CBufferString* pRejectReason); +KHook::Return Hook_ClientCommand(IServerGameClients* pThis, CPlayerSlot nSlot, const CCommand& _cmd); +KHook::Return Hook_ClientSvcUserMessage(IServerGameClients* pThis, CPlayerSlot slot, int um_type, uint32 size, const void* buf); +KHook::Return Hook_PostEventAbstract(IGameEventSystem* pThis, CSplitScreenSlot nSlot, bool bLocalOnly, int nClientCount, const uint64* clients, + INetworkMessageInternal* pEvent, const CNetMessage* pData, unsigned long nSize, NetChannelBufType_t bufType); +KHook::Return Hook_StartupServer_Post(INetworkServerService* pThis, const GameSessionConfiguration_t& config, ISource2WorldSession*, const char*); +KHook::Return Hook_CheckTransmit_Post(ISource2GameEntities* pThis, CCheckTransmitInfo** ppInfoList, int infoCount, CBitVec<16384>& unionTransmitEdicts, + CBitVec<16384>&, const Entity2Networkable_t** pNetworkables, const uint16* pEntityIndicies, int nEntities); +KHook::Return Hook_DispatchConCommand(ICvar* pThis, ConCommandRef cmd, const CCommandContext& ctx, const CCommand& args); +KHook::Return Hook_SwitchToLoop(IEngineServiceMgr* pThis, const char* pszLoopModeName, KeyValues* pKV, uint32 nId, const char* pszAddonName, bool bUnk); +KHook::Return Hook_CreateWorkshopMapGroup(IGameTypes* pThis, const char* name, const CUtlStringList& mapList); +KHook::Return Hook_LoadEventsFromFile(IGameEventManager2* pThis, const char* filename, bool bSearchAll); +KHook::Return Hook_ProcessVoiceData(CServerSideClient* pClient, const CCLCMsg_VoiceData_t& msg); +KHook::Return Hook_SetGameSpawnGroupMgr(INetworkGameServer* pThis, IGameSpawnGroupMgr* pSpawnGroupMgr); +KHook::Return Hook_GetTouchingList_Post(CVPhys2World* pThis, CUtlVector* pList, bool unknown); +KHook::Return Hook_CheckMovingGround(CCSPlayer_MovementServices* pThis, double frametime); +KHook::Return Hook_DropWeapon_Post(CCSPlayer_WeaponServices* pThis, CBasePlayerWeapon* pWeapon, Vector* pVecTarget, Vector* pVelocity); +KHook::Return Hook_PlayerEquipUse(CGamePlayerEquip* pThis, InputData_t* pInput); +KHook::Return Hook_PlayerEquipPrecache_Post(CGamePlayerEquip* pThis, CEntityPrecacheContext*); +KHook::Return Hook_TriggerGravityPrecache_Post(CTriggerGravity* pThis, CEntityPrecacheContext* param); +KHook::Return Hook_TriggerGravityEndTouch_Post(CTriggerGravity* pThis, CBaseEntity* pOther); +KHook::Return Hook_OnTakeDamage_Alive(CCSPlayerPawn* pPawn, CTakeDamageResult* pDamageResult); +KHook::Return Hook_CCSPlayerPawn_Teleport(CCSPlayerPawn* pPawn, const Vector* pPosition, const QAngle* pAngles, const Vector* pVelocity); +KHook::Return Hook_CCSPlayerPawn_Teleport_Post(CCSPlayerPawn* pPawn, const Vector* pPosition, const QAngle* pAngles, const Vector* pVelocity); diff --git a/src/zombiereborn.cpp b/src/zombiereborn.cpp index e91796ae3..3935ea955 100644 --- a/src/zombiereborn.cpp +++ b/src/zombiereborn.cpp @@ -971,7 +971,9 @@ void ZR_OnPlayerSpawn(CCSPlayerController* pController) // also set team immediately in case the spawn teleport is team filtered if (bInfect) { - pController->GetPawn()->m_bTakesDamage(false); + if (pController->GetPawn()) + pController->GetPawn()->m_bTakesDamage(false); + pController->SwitchTeam(CS_TEAM_T); } else diff --git a/vendor/funchook/LICENSE b/vendor/funchook/LICENSE deleted file mode 100644 index 2da840da3..000000000 --- a/vendor/funchook/LICENSE +++ /dev/null @@ -1,360 +0,0 @@ -Funchook is distributed under the terms of the GNU General Public -License version 2 or later with the following clarification and -special exception. - -Linking this library statically or dynamically with other modules is -making a combined work based on this library. Thus, the terms and -conditions of the GNU General Public License cover the whole -combination. - -As a special exception, the copyright holders of this library give you -permission to link this library with independent modules to produce an -executable, regardless of the license terms of these independent -modules, and to copy and distribute the resulting executable under -terms of your choice, provided that you also meet, for each linked -independent module, the terms and conditions of the license of that -module. An independent module is a module which is not derived from or -based on this library. If you modify this library, you must extend this -exception to your version of the library. - -===================================================================== - - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/vendor/funchook/README.md b/vendor/funchook/README.md deleted file mode 100644 index 419021f8b..000000000 --- a/vendor/funchook/README.md +++ /dev/null @@ -1,273 +0,0 @@ -Funchook - an API hook library -============================== - -[![tests](https://github.com/kubo/funchook/actions/workflows/run-tests.yml/badge.svg?branch=master)](https://github.com/kubo/funchook/actions/workflows/run-tests.yml) - -This library depends on one of the following disassemblers. - -On x86_64 and x86 -* [diStorm3][] (default) -* [zydis][] (when `-DFUNCHOOK_DISASM=zydis` is passed to the `cmake` command) -* [capstone][] (when `-DFUNCHOOK_DISASM=capstone` is passed to the `cmake` command) - -On arm64 -* [capstone][] - -TODO ----- - -* write documents. - -News ----- - -### 2.0.0 (20XX-XX-XX) - -* Add `funchook_prepare_with_params()` to support prehook. -* Add `funchook_get_arg()` to get arguments in prehook. - -### 1.1.1 (2022-10-02) - -* More permissive check for page allocation mmap ([#25][]) -* Flush instruction cache for arm64. It does nothing for intel CPU. -* Disassember engine - * Upgrade capstone to 4.0.2 - * Upgrade distorm to 3.5.2 -* CMake - * Allow user to specify FUNCHOOK_CPU explicitly ([#19][]) - * Avoid polluting global include and link dirs ([#20][]) - * Use target based compile options for gcc's -Wall ([#21][]) - * Use ExternalProject_add to download captone only ([#30][]) - * Add option FUNCHOOK_INSTALL ([#31][]) - * Use "FUNCHOOK_CPU MATCHES " ([#32][]) -* Documentation - * added example usage from python ([#22][]) -* Fix tests on Android ([#29][]) - -### 1.1.0 (2020-03-22) - -* Arm64 Linux support. [capstone][] is used as the disassembler library on arm64. -* Options to use [zydis][] and [capstone][] as a disassembler library on x86_64 and x86. -* `extern "C"` was added in funchook.h for C++. ([#15][]) -* Libc-compatible functions were removed to simplify code. - -### 1.0.0 (2020-01-19) - -* [diStorm3][] is used as the disassembler library. -* Libc-compatible functions were implemented on Linux in order not to hook function calls issued by funchook itself. - -Supported Platforms -------------------- - -* Linux x86_64 -* Linux x86 -* Linux arm64 (since 1.1.0) -* macOS x86_64 (Functions in executables cannot be hooked when Xcode version >= 11.0. (*1)) -* Windows x64 (except C-runtime functions under [Wine][]) -* Windows 32-bit - -*1 [`mprotect`] fails with EACCES. - -Unsupported Platforms ---------------------- - -* macOS arm64 (*1) - -*1 I received a mail that [`mprotect`] failed with `EINVAL`. Apple seems to prevent executable memory regions from being writable. - -Compilation and installation ------------ - -### Unix - -```shell -$ git clone --recursive https://github.com/kubo/funchook.git -$ mkdir build -$ cd build -$ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/path/to/install/directory ../funchook -$ make -$ make install -``` - -* Available [`CMAKE_BUILD_TYPE`][] values are empty(default), `Debug`, `Release`, `RelWithDebInfo`(release build with debug information) and `MinSizeRel`. -* When [`CMAKE_INSTALL_PREFIX`][] isn't set, funchook is installed at `/usr/local`. - - installed files: - * `${CMAKE_INSTALL_PREFIX}/include/funchook.h` (header file) - * `${CMAKE_INSTALL_PREFIX}/lib/libfunchook.so` (symbolic link to `libfunchook.so.1`) - * `${CMAKE_INSTALL_PREFIX}/lib/libfunchook.so.1` ([soname][]; symbolic link to `libfunchook.so.1.1.0`) - * `${CMAKE_INSTALL_PREFIX}/lib/libfunchook.so.1.1.0` (shared library) - * `${CMAKE_INSTALL_PREFIX}/lib/libfunchook.a` (static library) - -### Windows - -Here is an example to compile funchook with Visual Studio 2017 Win64. -Change the argument of `-G` to use other compilers. - -```shell -$ git clone --recursive https://github.com/kubo/funchook.git -$ mkdir build -$ cd build -$ cmake -G "Visual Studio 15 2017 Win64" -DCMAKE_INSTALL_PREFIX=c:\path\to\install\directory ..\funchook -$ cmake --build . --config Release --target INSTALL -``` - -* Available `-G` arguments (generators) are listed in the output of `cmake --help`. -* Available `--config` arguments are `Debug`(default), `Release`, `RelWithDebInfo` and `MinSizeRel`. -* When [`CMAKE_INSTALL_PREFIX`][] isn't set, funchook is installed at `c:\Program Files\funchook`. - - installed files: - * `${CMAKE_INSTALL_PREFIX}\include\funchook.h` (header file) - * `${CMAKE_INSTALL_PREFIX}\bin\funchook.dll` (shared library) - * `${CMAKE_INSTALL_PREFIX}\bin\funchook.pdb` (debug file for `funchook.dll` when `--config` is `Debug` or `RelWithDebInfo`) - * `${CMAKE_INSTALL_PREFIX}\lib\funchook.lib` (static library) - * `${CMAKE_INSTALL_PREFIX}\lib\funchook_dll.lib` (import library for `funchook.dll`) - -Example -------- - -```c -static ssize_t (*send_func)(int sockfd, const void *buf, size_t len, int flags); -static ssize_t (*recv_func)(int sockfd, void *buf, size_t len, int flags); - -static ssize_t send_hook(int sockfd, const void *buf, size_t len, int flags); -{ - ssize_t rv; - - ... do your task: logging, etc. ... - rv = send_func(sockfd, buf, len, flags); /* call the original send(). */ - ... do your task: logging, checking the return value, etc. ... - return rv; -} - -static ssize_t recv_hook(int sockfd, void *buf, size_t len, int flags); -{ - ssize_t rv; - - ... do your task: logging, etc. ... - rv = recv_func(sockfd, buf, len, flags); /* call the original recv(). */ - ... do your task: logging, checking received data, etc. ... - return rv; -} - -int install_hooks() -{ - funchook_t *funchook = funchook_create(); - int rv; - - /* Prepare hooking. - * The return value is used to call the original send function - * in send_hook. - */ - send_func = send; - rv = funchook_prepare(funchook, (void**)&send_func, send_hook); - if (rv != 0) { - /* error */ - ... - } - - /* ditto */ - recv_func = recv; - rv = funchook_prepare(funchook, (void**)&recv_func, recv_hook); - if (rv != 0) { - /* error */ - ... - } - - /* Install hooks. - * The first 5-byte code of send() and recv() are changed respectively. - */ - rv = funchook_install(funchook, 0); - if (rv != 0) { - /* error */ - ... - } -} - -``` - -Example - Using Python ctypes ------------------------------ -```python -# should work on python 2.7/3 windows/linux - -# load funchook -import ctypes -fh_lib = ctypes.cdll.LoadLibrary('/path/to/funchook/dll/or/so') - -# define signatures -funchook_create = fh_lib.funchook_create -funchook_create.restype = ctypes.c_void_p -funchook_create.argtypes = [] - -funchook_prepare = fh_lib.funchook_prepare -funchook_prepare.restype = ctypes.c_ssize_t -funchook_prepare.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] - -funchook_install = fh_lib.funchook_install -funchook_install.restype = ctypes.c_ssize_t -funchook_install.argtypes = [ctypes.c_void_p, ctypes.c_int] - -PySys_WriteStdout = ctypes.pythonapi.PySys_WriteStdout -PySys_WriteStdout.restype = None -PySys_WriteStdout.argtypes=[ctypes.c_char_p] - -# must keep those references alive, or stuff will be GC'd and weird errors will occur -global orig_write, hook, orig_write_ptr - -# create hook (this function will replace the original function) -hook_type = ctypes.PYFUNCTYPE(None, ctypes.c_char_p) -orig_write = None -def hook_impl(msg): - print('about to write: ' + str(msg)) # do what we want - orig_write(msg) # call the original function - -hook = hook_type(hook_impl) - -fh = funchook_create() -# create a pointer object with the function address -orig_write_ptr = ctypes.c_void_p(ctypes.c_void_p.from_address(ctypes.addressof(PySys_WriteStdout)).value) -# orig_write_ptr.value will get a ptr to the original PySys_WriteStdout and PySys_WriteStdout will now point to the hook -ret = funchook_prepare(fh, ctypes.addressof(orig_write_ptr), hook) -assert not ret, 'ret is ' + str(ret) -ret = funchook_install(fh, 0) -assert not ret, 'ret is ' + str(ret) -orig_write = hook_type.from_address(ctypes.addressof(orig_write_ptr)) -PySys_WriteStdout(b'hi there\n') -``` - -License -------- - -GPLv2 or later with a [GPL linking exception][]. - -You can use funchook in any software. Though funchook is licensed under -the GPL, it doesn't affect outside of funchook due to the linking exception. -You have no need to open your souce code under the GPL except funchook itself. - -If you modify funchook itself and release it, the modifed part must be -open under the GPL with or without the linking exception because funchook -itself is under the GPL. - -[diStorm3][] and [capstone][] are released under the 3-clause BSD license. -[zydis][] is released under the MIT license. They are compatible with the GPL. - -[GPL linking exception]: https://en.wikipedia.org/wiki/GPL_linking_exception -[diStorm3]: https://github.com/gdabah/distorm/ -[zydis]: https://github.com/zyantific/zydis -[capstone]: https://github.com/aquynh/capstone -[Wine]: https://www.winehq.org/ -[`CMAKE_BUILD_TYPE`]: https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html -[`CMAKE_INSTALL_PREFIX`]: https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX.html -[soname]: https://en.wikipedia.org/wiki/Soname -[#15]: https://github.com/kubo/funchook/issues/15 -[#19]: https://github.com/kubo/funchook/pull/19 -[#20]: https://github.com/kubo/funchook/pull/20 -[#21]: https://github.com/kubo/funchook/pull/21 -[#22]: https://github.com/kubo/funchook/pull/22 -[#25]: https://github.com/kubo/funchook/pull/25 -[#29]: https://github.com/kubo/funchook/pull/29 -[#30]: https://github.com/kubo/funchook/pull/30 -[#31]: https://github.com/kubo/funchook/pull/31 -[#32]: https://github.com/kubo/funchook/pull/32 -[`mprotect`]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/mprotect.2.html diff --git a/vendor/funchook/include/funchook.h b/vendor/funchook/include/funchook.h deleted file mode 100644 index 5c6ed34ee..000000000 --- a/vendor/funchook/include/funchook.h +++ /dev/null @@ -1,167 +0,0 @@ -/* - * This file is part of Funchook. - * https://github.com/kubo/funchook - * - * Funchook is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 2 of the License, or (at your - * option) any later version. - * - * As a special exception, the copyright holders of this library give you - * permission to link this library with independent modules to produce an - * executable, regardless of the license terms of these independent - * modules, and to copy and distribute the resulting executable under - * terms of your choice, provided that you also meet, for each linked - * independent module, the terms and conditions of the license of that - * module. An independent module is a module which is not derived from or - * based on this library. If you modify this library, you may extend this - * exception to your version of the library, but you are not obliged to - * do so. If you do not wish to do so, delete this exception statement - * from your version. - * - * Funchook is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - * - * You should have received a copy of the GNU General Public License - * along with Funchook. If not, see . - */ -#ifndef FUNCHOOK_H -#define FUNCHOOK_H 1 - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * Only functions with FUNCHOOK_EXPORT are visible from outside of funchook.dll - * or libfunchook.so. Others are invisible. - */ -#ifdef FUNCHOOK_EXPORTS -#if defined(_WIN32) -#define FUNCHOOK_EXPORT __declspec(dllexport) -#elif defined(__GNUC__) -#define FUNCHOOK_EXPORT __attribute__((visibility("default"))) -#endif -#endif /* FUNCHOOK_EXPORTS */ -#ifndef FUNCHOOK_EXPORT -#define FUNCHOOK_EXPORT -#endif - -typedef struct funchook funchook_t; - -#define FUNCHOOK_ERROR_INTERNAL_ERROR -1 -#define FUNCHOOK_ERROR_SUCCESS 0 -#define FUNCHOOK_ERROR_OUT_OF_MEMORY 1 -#define FUNCHOOK_ERROR_ALREADY_INSTALLED 2 -#define FUNCHOOK_ERROR_DISASSEMBLY 3 -#define FUNCHOOK_ERROR_IP_RELATIVE_OFFSET 4 -#define FUNCHOOK_ERROR_CANNOT_FIX_IP_RELATIVE 5 -#define FUNCHOOK_ERROR_FOUND_BACK_JUMP 6 -#define FUNCHOOK_ERROR_TOO_SHORT_INSTRUCTIONS 7 -#define FUNCHOOK_ERROR_MEMORY_ALLOCATION 8 /* memory allocation error */ -#define FUNCHOOK_ERROR_MEMORY_FUNCTION 9 /* other memory function errors */ -#define FUNCHOOK_ERROR_NOT_INSTALLED 10 -#define FUNCHOOK_ERROR_NO_AVAILABLE_REGISTERS 11 -#define FUNCHOOK_ERROR_NO_SPACE_NEAR_TARGET_ADDR 12 - -#define FUNCHOOK_FLAG_THISCALL (1u << 0) -#define FUNCHOOK_FLAG_FASTCALL (1u << 1) - -typedef struct funchook_arg_handle funchook_arg_handle_t; - -typedef struct funchook_info { - void *original_target_func; - void *target_func; - void *trampoline_func; - void *hook_func; - void *user_data; - funchook_arg_handle_t *arg_handle; -} funchook_info_t; - -typedef void (*funchook_hook_t)(funchook_info_t *fi); - -typedef struct { - void *hook_func; - funchook_hook_t prehook; - void *user_data; - unsigned int flags; -} funchook_params_t; - -/** - * Create a funchook handle - * - * @return allocated funchook handle. NULL when out-of-memory. - */ -FUNCHOOK_EXPORT funchook_t *funchook_create(void); - -/** - * Prepare hooking - * - * @param funchook a funchook handle created by funchook_create() - * @param target_func function pointer to be intercepted. The pointer to trampoline function is set on success. - * @param hook_func function pointer which is called istead of target_func - * @return error code. one of FUNCHOOK_ERROR_*. - */ -FUNCHOOK_EXPORT int funchook_prepare(funchook_t *funchook, void **target_func, void *hook_func); - -FUNCHOOK_EXPORT int funchook_prepare_with_params(funchook_t *funchook, - void **target_func, const funchook_params_t *params); - -/** - * Install hooks prepared by funchook_prepare(). - * - * @param funchook a funchook handle created by funchook_create() - * @param flags reserved. Set zero. - * @return error code. one of FUNCHOOK_ERROR_*. - */ -FUNCHOOK_EXPORT int funchook_install(funchook_t *funchook, int flags); - -/** - * Uninstall hooks installed by funchook_install(). - * - * @param funchook a funchook handle created by funchook_create() - * @param flags reserved. Set zero. - * @return error code. one of FUNCHOOK_ERROR_*. - */ -FUNCHOOK_EXPORT int funchook_uninstall(funchook_t *funchook, int flags); - -/** - * Destroy a funchook handle - * - * @param funchook a funchook handle created by funchook_create() - * @return error code. one of FUNCHOOK_ERROR_*. - */ -FUNCHOOK_EXPORT int funchook_destroy(funchook_t *funchook); - -/** - * Get error message - * - * @param funchook a funchook handle created by funchook_create() - * @return pointer to buffer containing error message - */ -FUNCHOOK_EXPORT const char *funchook_error_message(const funchook_t *funchook); - -/** - * Set log file name to debug funchook itself. - * - * @param name log file name - * @return error code. one of FUNCHOOK_ERROR_*. - */ -FUNCHOOK_EXPORT int funchook_set_debug_file(const char *name); - -/* This function is under developemnt. It will be used by C++ template functions later. */ -FUNCHOOK_EXPORT void *funchook_arg_get_int_reg_addr(const funchook_arg_handle_t *arg_handle, int pos); - -/* This function is under developemnt. It will be used by C++ template functions later. */ -FUNCHOOK_EXPORT void *funchook_arg_get_flt_reg_addr(const funchook_arg_handle_t *arg_handle, int pos); - -/* This function is under developemnt. It will be used by C++ template functions later. */ -FUNCHOOK_EXPORT void *funchook_arg_get_stack_addr(const funchook_arg_handle_t *arg_handle, int pos); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif diff --git a/vendor/funchook/lib/Debug/distorm.lib b/vendor/funchook/lib/Debug/distorm.lib deleted file mode 100644 index c6fb39830..000000000 Binary files a/vendor/funchook/lib/Debug/distorm.lib and /dev/null differ diff --git a/vendor/funchook/lib/Debug/distorm.pdb b/vendor/funchook/lib/Debug/distorm.pdb deleted file mode 100644 index c15ddfee1..000000000 Binary files a/vendor/funchook/lib/Debug/distorm.pdb and /dev/null differ diff --git a/vendor/funchook/lib/Debug/funchook.lib b/vendor/funchook/lib/Debug/funchook.lib deleted file mode 100644 index 319115e75..000000000 Binary files a/vendor/funchook/lib/Debug/funchook.lib and /dev/null differ diff --git a/vendor/funchook/lib/Debug/funchook.pdb b/vendor/funchook/lib/Debug/funchook.pdb deleted file mode 100644 index 4f5c4a8f1..000000000 Binary files a/vendor/funchook/lib/Debug/funchook.pdb and /dev/null differ diff --git a/vendor/funchook/lib/Debug/libdistorm.a b/vendor/funchook/lib/Debug/libdistorm.a deleted file mode 100644 index d449bda67..000000000 Binary files a/vendor/funchook/lib/Debug/libdistorm.a and /dev/null differ diff --git a/vendor/funchook/lib/Debug/libfunchook.a b/vendor/funchook/lib/Debug/libfunchook.a deleted file mode 100644 index 572919fb8..000000000 Binary files a/vendor/funchook/lib/Debug/libfunchook.a and /dev/null differ diff --git a/vendor/funchook/lib/Release/distorm.lib b/vendor/funchook/lib/Release/distorm.lib deleted file mode 100644 index 31aa0b3cb..000000000 Binary files a/vendor/funchook/lib/Release/distorm.lib and /dev/null differ diff --git a/vendor/funchook/lib/Release/funchook.lib b/vendor/funchook/lib/Release/funchook.lib deleted file mode 100644 index 7ce895981..000000000 Binary files a/vendor/funchook/lib/Release/funchook.lib and /dev/null differ diff --git a/vendor/funchook/lib/Release/libdistorm.a b/vendor/funchook/lib/Release/libdistorm.a deleted file mode 100644 index 7094a7b76..000000000 Binary files a/vendor/funchook/lib/Release/libdistorm.a and /dev/null differ diff --git a/vendor/funchook/lib/Release/libfunchook.a b/vendor/funchook/lib/Release/libfunchook.a deleted file mode 100644 index 1b25984bd..000000000 Binary files a/vendor/funchook/lib/Release/libfunchook.a and /dev/null differ