diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 19cd75f0..e96a3398 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,7 +39,7 @@ jobs: uses: microsoft/setup-msbuild@v1.1 - name: Generate project files - run: tools/premake5 vs2022 --ac-disable + run: tools/premake5 vs2022 - name: Set up problem matching uses: ammaraskar/msvc-problem-matcher@master diff --git a/CHANGELOG.md b/CHANGELOG.md index 6575345d..725f82b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,38 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.3.0/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.7.1] - 2022-05-03 + +### Added + +- Add ToUpper GSC Function (#216) +- Add StrICmp GSC Function (#216) +- Add IsEndStr GSC Function (#216) +- Add DropAllBots GSC Function (#174) +- Add GSC entity field `entityflags` (#228) +- Add GSC client field `clientflags` (#228) +- Add bg_surfacePenetration Dvar (#241) +- Add bg_bulletRange Dvar (#241) + +### Changed + +- Test clients' native functionality has been restored by default (#162) +- Custom GSC functions can be called correctly from a game script (#216) +- Master server list will be used instead of the node system (load server list faster) (#234) + +### Fixed + +- Fixed issue with mouse acceleration when polling rate is greater than 125Hz (#230) +- Fixed issue with player speed caused by sprinting from the prone position (#232) +- Fixed client crash when cg_chatHeight was set to 0 (#237) +- Fixed GSC function Scr_TableLookupIStringByRow (#162) + +### Known issue + +- HTTPS is not supported for fast downloads at the moment. +- Sound issue fix is experimental as the bug is not fully understood. +- `reloadmenus` command does not free resources used by custom menus. + ## [0.7.0] - 2022-05-01 ### Added diff --git a/README.md b/README.md index fb5dc10a..1309c156 100644 --- a/README.md +++ b/README.md @@ -19,15 +19,9 @@ |:----------------------------|:-----------------------------------------------| | `--copy-to=PATH` | Optional, copy the DLL to a custom folder after build, define the path here if wanted. | | `--copy-pdb` | Copy debug information for binaries as well to the path given via --copy-to. | -| `--ac-disable` | Disable anticheat. | -| `--ac-debug-detections` | Log anticheat detections. | -| `--ac-debug-load-library` | Log libraries that get loaded. | | `--force-unit-tests` | Always compile unit tests. | | `--force-exception-handler` | Install custom unhandled exception handler even for Debug builds. | | `--force-minidump-upload` | Upload minidumps even for Debug builds. | -| `--disable-bitmessage` | Disable use of BitMessage completely. | -| `--disable-base128` | Disable base128 encoding for minidumps. | -| `--no-new-structure` | Do not use new virtual path structure (separating headers and source files). | | `--iw4x-zones` | Zonebuilder generates iw4x zones that cannot be loaded without IW4x specific patches. | ## Command line arguments diff --git a/deps/libtommath b/deps/libtommath index 5108f123..4b473685 160000 --- a/deps/libtommath +++ b/deps/libtommath @@ -1 +1 @@ -Subproject commit 5108f12350b6daa4aa5dbc846517ad1db2f8388a +Subproject commit 4b47368501321c795d5b54d87a5bab35a21a7940 diff --git a/generate.bat b/generate.bat index b3823287..2bad27bb 100644 --- a/generate.bat +++ b/generate.bat @@ -1,4 +1,4 @@ @echo off echo Updating submodules... call git submodule update --init --recursive -call tools\premake5 %* vs2022 --ac-disable +call tools\premake5 %* vs2022 diff --git a/lib/bin/VMProtectSDK32.dll b/lib/bin/VMProtectSDK32.dll deleted file mode 100644 index 5b32d087..00000000 Binary files a/lib/bin/VMProtectSDK32.dll and /dev/null differ diff --git a/lib/bin/VMProtectSDK32.lib b/lib/bin/VMProtectSDK32.lib deleted file mode 100644 index 1111795a..00000000 Binary files a/lib/bin/VMProtectSDK32.lib and /dev/null differ diff --git a/lib/include/VMProtect/VMProtectSDK.h b/lib/include/VMProtect/VMProtectSDK.h deleted file mode 100644 index 3368ddac..00000000 --- a/lib/include/VMProtect/VMProtectSDK.h +++ /dev/null @@ -1,102 +0,0 @@ -#pragma once - -#if defined(__APPLE__) || defined(__unix__) -#define VMP_IMPORT -#define VMP_API -#define VMP_WCHAR unsigned short -#else -#define VMP_IMPORT __declspec(dllimport) -#define VMP_API __stdcall -#define VMP_WCHAR wchar_t -#ifdef _WIN64 - #pragma comment(lib, "VMProtectSDK64.lib") -#else - #pragma comment(lib, "VMProtectSDK32.lib") -#endif // _WIN64 -#endif // __APPLE__ || __unix__ - -#ifdef __cplusplus -extern "C" { -#endif - -// protection -VMP_IMPORT void VMP_API VMProtectBegin(const char *); -VMP_IMPORT void VMP_API VMProtectBeginVirtualization(const char *); -VMP_IMPORT void VMP_API VMProtectBeginMutation(const char *); -VMP_IMPORT void VMP_API VMProtectBeginUltra(const char *); -VMP_IMPORT void VMP_API VMProtectBeginVirtualizationLockByKey(const char *); -VMP_IMPORT void VMP_API VMProtectBeginUltraLockByKey(const char *); -VMP_IMPORT void VMP_API VMProtectEnd(void); - -// utils -VMP_IMPORT bool VMP_API VMProtectIsProtected(); -VMP_IMPORT bool VMP_API VMProtectIsDebuggerPresent(bool); -VMP_IMPORT bool VMP_API VMProtectIsVirtualMachinePresent(void); -VMP_IMPORT bool VMP_API VMProtectIsValidImageCRC(void); -VMP_IMPORT const char * VMP_API VMProtectDecryptStringA(const char *value); -VMP_IMPORT const VMP_WCHAR * VMP_API VMProtectDecryptStringW(const VMP_WCHAR *value); -VMP_IMPORT bool VMP_API VMProtectFreeString(const void *value); - -// licensing -enum VMProtectSerialStateFlags -{ - SERIAL_STATE_SUCCESS = 0, - SERIAL_STATE_FLAG_CORRUPTED = 0x00000001, - SERIAL_STATE_FLAG_INVALID = 0x00000002, - SERIAL_STATE_FLAG_BLACKLISTED = 0x00000004, - SERIAL_STATE_FLAG_DATE_EXPIRED = 0x00000008, - SERIAL_STATE_FLAG_RUNNING_TIME_OVER = 0x00000010, - SERIAL_STATE_FLAG_BAD_HWID = 0x00000020, - SERIAL_STATE_FLAG_MAX_BUILD_EXPIRED = 0x00000040, -}; - -#pragma pack(push, 1) -typedef struct -{ - unsigned short wYear; - unsigned char bMonth; - unsigned char bDay; -} VMProtectDate; - -typedef struct -{ - int nState; // VMProtectSerialStateFlags - VMP_WCHAR wUserName[256]; // user name - VMP_WCHAR wEMail[256]; // email - VMProtectDate dtExpire; // date of serial number expiration - VMProtectDate dtMaxBuild; // max date of build, that will accept this key - int bRunningTime; // running time in minutes - unsigned char nUserDataLength; // length of user data in bUserData - unsigned char bUserData[255]; // up to 255 bytes of user data -} VMProtectSerialNumberData; -#pragma pack(pop) - -VMP_IMPORT int VMP_API VMProtectSetSerialNumber(const char *serial); -VMP_IMPORT int VMP_API VMProtectGetSerialNumberState(); -VMP_IMPORT bool VMP_API VMProtectGetSerialNumberData(VMProtectSerialNumberData *data, int size); -VMP_IMPORT int VMP_API VMProtectGetCurrentHWID(char *hwid, int size); - -// activation -enum VMProtectActivationFlags -{ - ACTIVATION_OK = 0, - ACTIVATION_SMALL_BUFFER, - ACTIVATION_NO_CONNECTION, - ACTIVATION_BAD_REPLY, - ACTIVATION_BANNED, - ACTIVATION_CORRUPTED, - ACTIVATION_BAD_CODE, - ACTIVATION_ALREADY_USED, - ACTIVATION_SERIAL_UNKNOWN, - ACTIVATION_EXPIRED, - ACTIVATION_NOT_AVAILABLE -}; - -VMP_IMPORT int VMP_API VMProtectActivateLicense(const char *code, char *serial, int size); -VMP_IMPORT int VMP_API VMProtectDeactivateLicense(const char *serial); -VMP_IMPORT int VMP_API VMProtectGetOfflineActivationString(const char *code, char *buf, int size); -VMP_IMPORT int VMP_API VMProtectGetOfflineDeactivationString(const char *serial, char *buf, int size); - -#ifdef __cplusplus -} -#endif diff --git a/premake5.lua b/premake5.lua index 93fa8ce9..47c2b42f 100644 --- a/premake5.lua +++ b/premake5.lua @@ -71,21 +71,6 @@ newoption { description = "Copy debug information for binaries as well to the path given via --copy-to." } -newoption { - trigger = "ac-disable", - description = "Disable anticheat." -} - -newoption { - trigger = "ac-debug-detections", - description = "Log anticheat detections." -} - -newoption { - trigger = "ac-debug-load-library", - description = "Log libraries that get loaded." -} - newoption { trigger = "force-unit-tests", description = "Always compile unit tests." @@ -269,23 +254,11 @@ workspace "iw4x" "./src", "./lib/include", } - syslibdirs { - "./lib/bin", - } resincludedirs { "$(ProjectDir)src" -- fix for VS IDE } -- Debug flags - if _OPTIONS["ac-disable"] then - defines {"DISABLE_ANTICHEAT"} - end - if _OPTIONS["ac-debug-detections"] then - defines {"DEBUG_DETECTIONS"} - end - if _OPTIONS["ac-debug-load-library"] then - defines {"DEBUG_LOAD_LIBRARY"} - end if _OPTIONS["force-unit-tests"] then defines {"FORCE_UNIT_TESTS"} end diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index a591720a..7c64416a 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -69,7 +69,6 @@ namespace Components Loader::Register(new Renderer()); Loader::Register(new UIFeeder()); Loader::Register(new UIScript()); - Loader::Register(new AntiCheat()); Loader::Register(new Changelog()); Loader::Register(new Dedicated()); Loader::Register(new Discovery()); @@ -84,6 +83,7 @@ namespace Components Loader::Register(new ModelSurfs()); Loader::Register(new PlayerName()); Loader::Register(new QuickPatch()); + Loader::Register(new Security()); Loader::Register(new ServerInfo()); Loader::Register(new ServerList()); Loader::Register(new SlowMotion()); @@ -104,8 +104,9 @@ namespace Components Loader::Register(new Movement()); Loader::Register(new Elevators()); Loader::Register(new ClientCommand()); - - Loader::Register(new Client()); + Loader::Register(new ScriptExtension()); + Loader::Register(new RawMouse()); + Loader::Register(new Bullet()); Loader::Pregame = false; } diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index 02ac2007..12ffb1ff 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -85,6 +85,7 @@ namespace Components #include "Modules/Network.hpp" #include "Modules/Theatre.hpp" #include "Modules/QuickPatch.hpp" +#include "Modules/Security.hpp" #include "Modules/Node.hpp" #include "Modules/RCon.hpp" #include "Modules/Party.hpp" // Destroys the order, but requires network classes :D @@ -99,7 +100,6 @@ namespace Components #include "Modules/RawFiles.hpp" #include "Modules/Renderer.hpp" #include "Modules/UIFeeder.hpp" -#include "Modules/AntiCheat.hpp" #include "Modules/Changelog.hpp" #include "Modules/Dedicated.hpp" #include "Modules/Discovery.hpp" @@ -134,6 +134,7 @@ namespace Components #include "Modules/Movement.hpp" #include "Modules/Elevators.hpp" #include "Modules/ClientCommand.hpp" - #include "Modules/Gamepad.hpp" -#include "Modules/Client.hpp" +#include "Modules/ScriptExtension.hpp" +#include "Modules/RawMouse.hpp" +#include "Modules/Bullet.hpp" diff --git a/src/Components/Modules/AntiCheat.cpp b/src/Components/Modules/AntiCheat.cpp deleted file mode 100644 index 496ef495..00000000 --- a/src/Components/Modules/AntiCheat.cpp +++ /dev/null @@ -1,944 +0,0 @@ -#include - -namespace Components -{ - Utils::Time::Interval AntiCheat::LastCheck; - Utils::Hook AntiCheat::CreateThreadHook; - Utils::Hook AntiCheat::LoadLibHook[6]; - Utils::Hook AntiCheat::VirtualProtectHook[2]; - unsigned long AntiCheat::Flags = NO_FLAG; - - std::mutex AntiCheat::ThreadMutex; - std::vector AntiCheat::OwnThreadIds; - std::map> AntiCheat::ThreadHookMap; - - // This function does nothing, it only adds the two passed variables and returns the value - // The only important thing it does is to clean the first parameter, and then return - // By returning, the crash procedure will be called, as it hasn't been cleaned from the stack - __declspec(naked) void AntiCheat::NullSub() - { - __asm - { - push ebp - push ecx - mov ebp, esp - - xor eax, eax - mov eax, [ebp + 8h] - mov ecx, [ebp + 0Ch] - add eax, ecx - - pop ecx - pop ebp - retn 4 - } - } - - void AntiCheat::CrashClient() - { - __VMProtectBeginUltra(""); -#ifdef DEBUG_DETECTIONS - Logger::Flush(); - MessageBoxA(nullptr, "Check the log for more information!", "AntiCheat triggered", MB_ICONERROR); - ExitProcess(0xFFFFFFFF); -#else - static std::thread triggerThread; - if (!triggerThread.joinable()) - { - triggerThread = std::thread([]() - { - std::this_thread::sleep_for(43s); - Utils::Hook::Set(0x41BA2C, 0xEB); - }); - } -#endif - __VMProtectEnd; - } - - void AntiCheat::AssertCalleeModule(void* callee) - { - __VMProtectBeginUltra(""); - HMODULE hModuleSelf = nullptr, hModuleTarget = nullptr, hModuleProcess = GetModuleHandleA(nullptr); - GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(callee), &hModuleTarget); - GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(AntiCheat::AssertCalleeModule), &hModuleSelf); - - if (!hModuleSelf || !hModuleTarget || !hModuleProcess || (hModuleTarget != hModuleSelf && hModuleTarget != hModuleProcess)) - { -#ifdef DEBUG_DETECTIONS - char buffer[MAX_PATH] = { 0 }; - GetModuleFileNameA(hModuleTarget, buffer, sizeof buffer); - - Logger::Print(Utils::String::VA("AntiCheat: Callee assertion failed: %X %s", reinterpret_cast(callee), buffer)); -#endif - - AntiCheat::CrashClient(); - } - __VMProtectEnd; - } - - void AntiCheat::InitLoadLibHook() - { - __VMProtectBeginUltra(""); - static uint8_t kernel32Str[] = { 0xB4, 0x9A, 0x8D, 0xB1, 0x9A, 0x93, 0xCC, 0xCD, 0xD1, 0x9B, 0x93, 0x93 }; // KerNel32.dll - static uint8_t loadLibAStr[] = { 0xB3, 0x90, 0x9E, 0x9B, 0xB3, 0x96, 0x9D, 0x8D, 0x9E, 0x8D, 0x86, 0xBE }; // LoadLibraryA - static uint8_t loadLibWStr[] = { 0xB3, 0x90, 0x9E, 0x9B, 0xB3, 0x96, 0x9D, 0x8D, 0x9E, 0x8D, 0x86, 0xA8 }; // LoadLibraryW - - HMODULE kernel32 = GetModuleHandleA(Utils::String::XOR(std::string(reinterpret_cast(kernel32Str), sizeof kernel32Str), -1).data()); - if (kernel32) - { - FARPROC loadLibA = GetProcAddress(kernel32, Utils::String::XOR(std::string(reinterpret_cast(loadLibAStr), sizeof loadLibAStr), -1).data()); - FARPROC loadLibW = GetProcAddress(kernel32, Utils::String::XOR(std::string(reinterpret_cast(loadLibWStr), sizeof loadLibWStr), -1).data()); - - std::string libExA = Utils::String::XOR(std::string(reinterpret_cast(loadLibAStr), sizeof loadLibAStr), -1); - std::string libExW = Utils::String::XOR(std::string(reinterpret_cast(loadLibWStr), sizeof loadLibWStr), -1); - - libExA.insert(libExA.end() - 1, 'E'); - libExA.insert(libExA.end() - 1, 'x'); - - libExW.insert(libExW.end() - 1, 'E'); - libExW.insert(libExW.end() - 1, 'x'); - - FARPROC loadLibExA = GetProcAddress(kernel32, libExA.data()); - FARPROC loadLibExW = GetProcAddress(kernel32, libExW.data()); - - if (loadLibA && loadLibW && loadLibExA && loadLibExW) - { -#ifdef DEBUG_LOAD_LIBRARY - AntiCheat::LoadLibHook[0].initialize(loadLibA, LoadLibaryAStub, HOOK_JUMP); - AntiCheat::LoadLibHook[1].initialize(loadLibW, LoadLibaryWStub, HOOK_JUMP); - AntiCheat::LoadLibHook[2].initialize(loadLibExA, LoadLibaryExAStub, HOOK_JUMP); - AntiCheat::LoadLibHook[3].initialize(loadLibExW, LoadLibaryExWStub, HOOK_JUMP); -#else - static uint8_t loadLibStub[] = { 0x33, 0xC0, 0xC2, 0x04, 0x00 }; // xor eax, eax; retn 04h - static uint8_t loadLibExStub[] = { 0x33, 0xC0, 0xC2, 0x0C, 0x00 }; // xor eax, eax; retn 0Ch - AntiCheat::LoadLibHook[0].initialize(loadLibA, loadLibStub, HOOK_JUMP); - AntiCheat::LoadLibHook[1].initialize(loadLibW, loadLibStub, HOOK_JUMP); - AntiCheat::LoadLibHook[2].initialize(loadLibExA, loadLibExStub, HOOK_JUMP); - AntiCheat::LoadLibHook[3].initialize(loadLibExW, loadLibExStub, HOOK_JUMP); -#endif - } - } - - static uint8_t ldrLoadDllStub[] = { 0x33, 0xC0, 0xC2, 0x10, 0x00 }; - static uint8_t ldrLoadDll[] = { 0xB3, 0x9B, 0x8D, 0xB3, 0x90, 0x9E, 0x9B, 0xBB, 0x93, 0x93 }; // LdrLoadDll - - HMODULE ntdll = Utils::GetNTDLL(); - //AntiCheat::LoadLibHook[4].initialize(GetProcAddress(ntdll, Utils::String::XOR(std::string(reinterpret_cast(ldrLoadDll), sizeof ldrLoadDll), -1).data()), ldrLoadDllStub, HOOK_JUMP); - - // Patch LdrpLoadDll - Utils::Hook::Signature::Container container; - container.signature = "\x8B\xFF\x55\x8B\xEC\x83\xE4\xF8\x81\xEC\x00\x00\x00\x00\xA1\x00\x00\x00\x00\x33\xC4\x89\x84\x24\x00\x00\x00\x00\x53\x8B\x5D\x10\x56\x57"; - container.mask = "xxxxxxxxxx????x????xxxxx????xxxxxx"; - container.callback = [](char* addr) - { - static uint8_t ldrpLoadDllStub[] = { 0x33, 0xC0, 0xC2, 0x0C, 0x00 }; - AntiCheat::LoadLibHook[5].initialize(addr, ldrpLoadDllStub, HOOK_JUMP); - }; - - Utils::Hook::Signature signature(ntdll, Utils::GetModuleSize(ntdll)); - signature.add(container); - //signature.process(); - - __VMProtectEnd; - } - - void AntiCheat::ReadIntegrityCheck() - { - __VMProtectBeginUltra(""); -#ifdef PROCTECT_PROCESS - static Utils::Time::Interval check; - - if (check.elapsed(20s)) - { - check.update(); - - if (HANDLE h = OpenProcess(PROCESS_VM_READ, FALSE, GetCurrentProcessId())) - { -#ifdef DEBUG_DETECTIONS - Logger::Print("AntiCheat: Process integrity check failed"); -#endif - - CloseHandle(h); - AntiCheat::CrashClient(); - } - } - - // Set the integrity flag - AntiCheat::Flags |= AntiCheat::IntergrityFlag::READ_INTEGRITY_CHECK; -#endif - __VMProtectEnd; - } - - void AntiCheat::FlagIntegrityCheck() - { - __VMProtectBeginUltra(""); - static Utils::Time::Interval check; - - if (check.elapsed(30s)) - { - check.update(); - - unsigned long flags = ((AntiCheat::IntergrityFlag::MAX_FLAG - 1) << 1) - 1; - - if (AntiCheat::Flags != flags) - { -#ifdef DEBUG_DETECTIONS - Logger::Print(Utils::String::VA("AntiCheat: Flag integrity check failed: %X", AntiCheat::Flags)); -#endif - - AntiCheat::CrashClient(); - } - } - __VMProtectEnd; - } - - void AntiCheat::ScanIntegrityCheck() - { - __VMProtectBeginUltra(""); - // If there was no check within the last 40 seconds, crash! - if (AntiCheat::LastCheck.elapsed(40s)) - { -#ifdef DEBUG_DETECTIONS - Logger::Print("AntiCheat: Integrity check failed"); -#endif - - AntiCheat::CrashClient(); - } - - // Set the integrity flag - AntiCheat::Flags |= AntiCheat::IntergrityFlag::SCAN_INTEGRITY_CHECK; - __VMProtectEnd; - } - - void AntiCheat::PerformScan() - { - __VMProtectBeginUltra(""); - static std::optional hashVal; - - // Perform check only every 20 seconds - if (!AntiCheat::LastCheck.elapsed(20s)) return; - AntiCheat::LastCheck.update(); - - // Hash .text segment - // Add 1 to each value, so searching in memory doesn't reveal anything - size_t textSize = 0x2D6001; - char* textBase = reinterpret_cast(0x401001); - - unsigned int hash = Utils::Cryptography::JenkinsOneAtATime::Compute(textBase - 1, textSize - 1); - - // Set the hash, if none is set - if (!hashVal.has_value()) - { - hashVal.emplace(hash); - } - // Crash if the hashes don't match - else if (hashVal.value() != hash) - { -#ifdef DEBUG_DETECTIONS - Logger::Print("AntiCheat: Memory scan failed"); -#endif - - AntiCheat::CrashClient(); - } - - // Set the memory scan flag - AntiCheat::Flags |= AntiCheat::IntergrityFlag::MEMORY_SCAN; - __VMProtectEnd; - } - - void AntiCheat::QuickCodeScanner1() - { - __VMProtectBeginUltra(""); - static Utils::Time::Interval interval; - static std::optional hashVal; - - if (!interval.elapsed(32s)) return; - interval.update(); - - // Hash .text segment - // Add 1 to each value, so searching in memory doesn't reveal anything - size_t textSize = 0x2D5FFF; - char* textBase = reinterpret_cast(0x400FFF); - unsigned int hash = Utils::Cryptography::JenkinsOneAtATime::Compute(textBase + 1, textSize + 1); - - if (hashVal.has_value() && hash != hashVal.value()) - { - Utils::Hook::Set(0x42A667, 0x90); // Crash - } - - hashVal.emplace(hash); - __VMProtectEnd; - } - - void AntiCheat::QuickCodeScanner2() - { - __VMProtectBeginUltra(""); - static Utils::Time::Interval interval; - static std::optional hashVal; - - if (!interval.elapsed(42s)) return; - interval.update(); - - // Hash .text segment - unsigned int hash = Utils::Cryptography::JenkinsOneAtATime::Compute(reinterpret_cast(0x401000), 0x2D6000); - if (hashVal.has_value() && hash != hashVal.value()) - { - Utils::Hook::Set(0x40797C, 0x90); // Crash - } - - hashVal.emplace(hash); - __VMProtectEnd; - } - -#ifdef DEBUG_LOAD_LIBRARY - HANDLE AntiCheat::LoadLibary(std::wstring library, HANDLE file, DWORD flags, void* callee) - { - HMODULE module; - char buffer[MAX_PATH] = { 0 }; - - GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(callee), &module); - GetModuleFileNameA(module, buffer, sizeof buffer); - - MessageBoxA(nullptr, Utils::String::VA("Loading library %s via %s %X", std::string(library.begin(), library.end()).data(), buffer, reinterpret_cast(callee)), nullptr, 0); - - AntiCheat::LoadLibHook[3].uninstall(); - HANDLE h = LoadLibraryExW(library.data(), file, flags); - AntiCheat::LoadLibHook[3].install(); - return h; - } - - HANDLE WINAPI AntiCheat::LoadLibaryAStub(const char* library) - { - std::string lib(library); - return AntiCheat::LoadLibary(std::wstring(lib.begin(), lib.end()), nullptr, 0, _ReturnAddress()); - } - - HANDLE WINAPI AntiCheat::LoadLibaryWStub(const wchar_t* library) - { - return AntiCheat::LoadLibary(library, nullptr, 0, _ReturnAddress()); - } - - HANDLE WINAPI AntiCheat::LoadLibaryExAStub(const char* library, HANDLE file, DWORD flags) - { - std::string lib(library); - return AntiCheat::LoadLibary(std::wstring(lib.begin(), lib.end()), file, flags, _ReturnAddress()); - } - - HANDLE WINAPI AntiCheat::LoadLibaryExWStub(const wchar_t* library, HANDLE file, DWORD flags) - { - return AntiCheat::LoadLibary(library, file, flags, _ReturnAddress()); - } -#endif - - void AntiCheat::UninstallLibHook() - { - for (int i = 0; i < ARRAYSIZE(AntiCheat::LoadLibHook); ++i) - { - AntiCheat::LoadLibHook[i].uninstall(); - } - } - - void AntiCheat::InstallLibHook() - { - for (int i = 0; i < ARRAYSIZE(AntiCheat::LoadLibHook); ++i) - { - AntiCheat::LoadLibHook[i].install(); - } - } - - void AntiCheat::PatchWinAPI() - { - LibUnlocker _; - - // Initialize directx - Utils::Hook::Call(0x5078C0)(); - } - - void AntiCheat::SoundInitStub(int a1, int a2, int a3) - { - LibUnlocker _; - Game::SND_Init(a1, a2, a3); - } - - void AntiCheat::SoundInitDriverStub() - { - LibUnlocker _; - Game::SND_InitDriver(); - } - - void AntiCheat::LostD3DStub() - { - LibUnlocker _; - - // Reset directx - Utils::Hook::Call(0x508070)(); - } - - __declspec(naked) void AntiCheat::CinematicStub() - { - __asm - { - pushad - call AntiCheat::UninstallLibHook - popad - - call Game::R_Cinematic_StartPlayback_Now - - pushad - call AntiCheat::InstallLibHook - popad - - retn - } - } - - __declspec(naked) void AntiCheat::DObjGetWorldTagPosStub() - { - __asm - { - pushad - push [esp + 20h] - - call AntiCheat::AssertCalleeModule - - pop esi - popad - - push ecx - mov ecx, [esp + 10h] - - push 426585h - retn - } - } - - __declspec(naked) void AntiCheat::AimTargetGetTagPosStub() - { - __asm - { - pushad - push [esp + 20h] - - call AntiCheat::AssertCalleeModule - - pop esi - popad - - sub esp, 14h - cmp dword ptr[esi + 0E0h], 1 - push 56AC6Ah - ret - } - } - - bool AntiCheat::IsPageChangeAllowed(void* callee, void* addr, size_t len) - { - __VMProtectBeginUltra(""); - HMODULE hModuleSelf = nullptr, hModuleTarget = nullptr, hModuleMain = GetModuleHandle(nullptr); - GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(callee), &hModuleTarget); - GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(AntiCheat::IsPageChangeAllowed), &hModuleSelf); - - size_t mainSize = Utils::GetModuleSize(hModuleMain), selfSize = Utils::GetModuleSize(hModuleSelf); - DWORD self = DWORD(hModuleSelf), main = DWORD(hModuleMain), address = DWORD(addr); - - // If the address that should be changed is within our module or the main binary, then we need to check if we are changing it or someone else - if (Utils::HasIntercection(self, selfSize, address, len) || Utils::HasIntercection(main, mainSize, address, len)) - { - if (!hModuleSelf || !hModuleTarget || (hModuleTarget != hModuleSelf)) - { - return false; - } - } - - __VMProtectEnd; - return true; - } - - BOOL WINAPI AntiCheat::VirtualProtectStub(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect) - { - __VMProtectBeginUltra(""); - if (!AntiCheat::IsPageChangeAllowed(_ReturnAddress(), lpAddress, dwSize)) return FALSE; - - AntiCheat::VirtualProtectHook[0].uninstall(false); - BOOL result = VirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldProtect); - AntiCheat::VirtualProtectHook[0].install(false); - - __VMProtectEnd; - return result; - } - - BOOL WINAPI AntiCheat::VirtualProtectExStub(HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect) - { - __VMProtectBeginUltra(""); - if (GetCurrentProcessId() == GetProcessId(hProcess) && !AntiCheat::IsPageChangeAllowed(_ReturnAddress(), lpAddress, dwSize)) return FALSE; - - AntiCheat::VirtualProtectHook[1].uninstall(false); - BOOL result = VirtualProtectEx(hProcess, lpAddress, dwSize, flNewProtect, lpflOldProtect); - AntiCheat::VirtualProtectHook[1].install(false); - - __VMProtectEnd; - return result; - } - - unsigned long AntiCheat::ProtectProcess() - { -#ifdef PROCTECT_PROCESS - __VMProtectBeginUltra(""); - - Utils::Memory::Allocator allocator; - - HANDLE hToken = nullptr; - if (!OpenProcessToken(GetCurrentProcess(), /*TOKEN_ADJUST_PRIVILEGES | */TOKEN_READ, &hToken)) - { - if (!OpenThreadToken(GetCurrentThread(), /*TOKEN_ADJUST_PRIVILEGES | */TOKEN_READ, TRUE, &hToken)) - { - return GetLastError(); - } - } - - auto freeSid = [](void* sid) - { - if (sid) - { - FreeSid(reinterpret_cast(sid)); - } - }; - - allocator.reference(hToken, [](void* hToken) - { - if (hToken) - { - CloseHandle(hToken); - } - }); - - //AntiCheat::AcquireDebugPrivilege(hToken); - - DWORD dwSize = 0; - PVOID pTokenInfo = nullptr; - if (GetTokenInformation(hToken, TokenUser, nullptr, 0, &dwSize) || GetLastError() != ERROR_INSUFFICIENT_BUFFER) return GetLastError(); - - if (dwSize) - { - pTokenInfo = allocator.allocate(dwSize); - if (!pTokenInfo) return GetLastError(); - } - - if (!GetTokenInformation(hToken, TokenUser, pTokenInfo, dwSize, &dwSize) || !pTokenInfo) return GetLastError(); - - PSID psidCurUser = reinterpret_cast(pTokenInfo)->User.Sid; - - PSID psidEveryone = nullptr; - SID_IDENTIFIER_AUTHORITY sidEveryone = SECURITY_WORLD_SID_AUTHORITY; - if (!AllocateAndInitializeSid(&sidEveryone, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &psidEveryone) || !psidEveryone) return GetLastError(); - allocator.reference(psidEveryone, freeSid); - - PSID psidSystem = nullptr; - SID_IDENTIFIER_AUTHORITY sidSystem = SECURITY_NT_AUTHORITY; - if (!AllocateAndInitializeSid(&sidSystem, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &psidSystem) || !psidSystem) return GetLastError(); - allocator.reference(psidSystem, freeSid); - - PSID psidAdmins = nullptr; - SID_IDENTIFIER_AUTHORITY sidAdministrators = SECURITY_NT_AUTHORITY; - if (!AllocateAndInitializeSid(&sidAdministrators, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &psidAdmins) || !psidAdmins) return GetLastError(); - allocator.reference(psidAdmins, freeSid); - - const PSID psidArray[] = - { - psidEveryone, /* Deny most rights to everyone */ - psidCurUser, /* Allow what was not denied */ - psidSystem, /* Full control */ - psidAdmins, /* Full control */ - }; - - // Determine required size of the ACL - dwSize = sizeof(ACL); - - // First the DENY, then the ALLOW - dwSize += GetLengthSid(psidArray[0]); - dwSize += sizeof(ACCESS_DENIED_ACE) - sizeof(DWORD); - - for (UINT i = 1; i < _countof(psidArray); ++i) - { - // DWORD is the SidStart field, which is not used for absolute format - dwSize += GetLengthSid(psidArray[i]); - dwSize += sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD); - } - - PACL pDacl = reinterpret_cast(allocator.allocate(dwSize)); - if (!pDacl || !InitializeAcl(pDacl, dwSize, ACL_REVISION)) return GetLastError(); - - // Just give access to what steam needs - //static const DWORD dwPoison = 0UL | ~(SYNCHRONIZE | GENERIC_EXECUTE | GENERIC_ALL); - static const DWORD dwPoison = - /*READ_CONTROL |*/ WRITE_DAC | WRITE_OWNER | - PROCESS_CREATE_PROCESS | PROCESS_CREATE_THREAD | - PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION | - PROCESS_SET_QUOTA | PROCESS_SET_INFORMATION | - PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE | - // In addition to protected process - PROCESS_SUSPEND_RESUME | PROCESS_TERMINATE; - - if (!AddAccessDeniedAce(pDacl, ACL_REVISION, dwPoison, psidArray[0])) return GetLastError(); - - // Standard and specific rights not explicitly denied - //static const DWORD dwAllowed = 0UL | SYNCHRONIZE; - static const DWORD dwAllowed = (~dwPoison & 0x1FFF) | SYNCHRONIZE; - if (!AddAccessAllowedAce(pDacl, ACL_REVISION, dwAllowed, psidArray[1])) return GetLastError(); - - // Because of ACE ordering, System will effectively have dwAllowed even - // though the ACE specifies PROCESS_ALL_ACCESS (unless software uses - // SeDebugPrivilege or SeTcbName and increases access). - // As an exercise, check behavior of tools such as Process Explorer under XP, - // Vista, and above. Vista and above should exhibit slightly different behavior - // due to Restricted tokens. - if (!AddAccessAllowedAce(pDacl, ACL_REVISION, PROCESS_ALL_ACCESS, psidArray[2])) return GetLastError(); - - // Because of ACE ordering, Administrators will effectively have dwAllowed - // even though the ACE specifies PROCESS_ALL_ACCESS (unless the Administrator - // invokes 'discretionary security' by taking ownership and increasing access). - // As an exercise, check behavior of tools such as Process Explorer under XP, - // Vista, and above. Vista and above should exhibit slightly different behavior - // due to Restricted tokens. - if (!AddAccessAllowedAce(pDacl, ACL_REVISION, PROCESS_ALL_ACCESS, psidArray[3])) return GetLastError(); - - PSECURITY_DESCRIPTOR pSecDesc = allocator.allocate(); - if (!pSecDesc) return GetLastError(); - - // InitializeSecurityDescriptor initializes a security descriptor in - // absolute format, rather than self-relative format. See - // http://msdn.microsoft.com/en-us/library/aa378863(VS.85).aspx - if (!InitializeSecurityDescriptor(pSecDesc, SECURITY_DESCRIPTOR_REVISION)) return GetLastError(); - if (!SetSecurityDescriptorDacl(pSecDesc, TRUE, pDacl, FALSE)) return GetLastError(); - - __VMProtectEnd; - - return SetSecurityInfo( - GetCurrentProcess(), - SE_KERNEL_OBJECT, // process object - OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, - psidCurUser, // NULL, // Owner SID - nullptr, // Group SID - pDacl, - nullptr // SACL - ); -#else - return 0; -#endif - } - - void AntiCheat::AcquireDebugPrivilege(HANDLE hToken) - { - __VMProtectBeginUltra(""); - - LUID luid; - TOKEN_PRIVILEGES tp = { 0 }; - DWORD cb = sizeof(TOKEN_PRIVILEGES); - if (!LookupPrivilegeValueA(nullptr, SE_DEBUG_NAME, &luid)) return; - - tp.PrivilegeCount = 1; - tp.Privileges[0].Luid = luid; - tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - AdjustTokenPrivileges(hToken, FALSE, &tp, cb, nullptr, nullptr); - //if (GetLastError() != ERROR_SUCCESS) return; - - __VMProtectEnd; - } - - void AntiCheat::PatchVirtualProtect(void* vp, void* vpex) - { - __VMProtectBeginUltra(""); - AntiCheat::VirtualProtectHook[1].initialize(vpex, AntiCheat::VirtualProtectExStub, HOOK_JUMP)->install(true, true); - AntiCheat::VirtualProtectHook[0].initialize(vp, AntiCheat::VirtualProtectStub, HOOK_JUMP)->install(true, true); - __VMProtectEnd; - } - - NTSTATUS NTAPI AntiCheat::NtCreateThreadExStub(PHANDLE phThread, ACCESS_MASK desiredAccess, LPVOID objectAttributes, HANDLE processHandle, LPTHREAD_START_ROUTINE startAddress, LPVOID parameter, BOOL createSuspended, DWORD stackZeroBits, DWORD sizeOfStackCommit, DWORD sizeOfStackReserve, LPVOID bytesBuffer) - { - __VMProtectBeginUltra(""); - - HANDLE hThread = nullptr; - std::lock_guard _(AntiCheat::ThreadMutex); - - AntiCheat::CreateThreadHook.uninstall(); - NTSTATUS result = NtCreateThreadEx_t(AntiCheat::CreateThreadHook.getAddress())(&hThread, desiredAccess, objectAttributes, processHandle, startAddress, parameter, createSuspended, stackZeroBits, sizeOfStackCommit, sizeOfStackReserve, bytesBuffer); - AntiCheat::CreateThreadHook.install(); - - if (phThread) *phThread = hThread; - - if (GetProcessId(processHandle) == GetCurrentProcessId()) - { - AntiCheat::OwnThreadIds.push_back(GetThreadId(hThread)); - } - - __VMProtectEnd; - - return result; - } - - void AntiCheat::PatchThreadCreation() - { - __VMProtectBeginUltra(""); - - HMODULE ntdll = Utils::GetNTDLL(); - if (ntdll) - { - static uint8_t ntCreateThreadEx[] = { 0xB1, 0x8B, 0xBC, 0x8D, 0x9A, 0x9E, 0x8B, 0x9A, 0xAB, 0x97, 0x8D, 0x9A, 0x9E, 0x9B, 0xBA, 0x87 }; // NtCreateThreadEx - FARPROC createThread = GetProcAddress(ntdll, Utils::String::XOR(std::string(reinterpret_cast(ntCreateThreadEx), sizeof ntCreateThreadEx), -1).data()); - if (createThread) - { - AntiCheat::CreateThreadHook.initialize(createThread, AntiCheat::NtCreateThreadExStub, HOOK_JUMP)->install(); - } - } - - __VMProtectEnd; - } - - int AntiCheat::ValidateThreadTermination(void* addr) - { - __VMProtectBeginUltra(""); - { - std::lock_guard _(AntiCheat::ThreadMutex); - - DWORD id = GetCurrentThreadId(); - auto threadHook = AntiCheat::ThreadHookMap.find(id); - if (threadHook != AntiCheat::ThreadHookMap.end()) - { - threadHook->second->uninstall(false); - AntiCheat::ThreadHookMap.erase(threadHook); // Uninstall and delete the hook - return 1; // Kill - } - } - - while (true) - { - std::lock_guard _(AntiCheat::ThreadMutex); - - // It would be better to wait for the thread - // but we don't know if there are multiple hooks at the same address - bool found = false; - for (auto threadHook : AntiCheat::ThreadHookMap) - { - if (threadHook.second->getAddress() == addr) - { - found = true; - break; - } - } - - if (!found) break; - std::this_thread::sleep_for(10ms); - } - - __VMProtectEnd; - - return 0; // Don't kill - } - - __declspec(naked) void AntiCheat::ThreadEntryPointStub() - { - __asm - { - push eax - push eax - pushad - - // Reinitialize the return address - mov eax, [esp + 28h] - sub eax, 5 - mov [esp + 28h], eax - - push eax - call AntiCheat::ValidateThreadTermination - add esp, 4h - - mov [esp + 20h], eax - - popad - - pop eax - - test eax, eax - jz dontKill - - pop eax - add esp, 4h // Remove return address (simulate a jump hook) - retn - - dontKill: - pop eax - retn - } - } - - void AntiCheat::VerifyThreadIntegrity() - { - __VMProtectBeginUltra(""); - bool kill = true; - { - std::lock_guard _(AntiCheat::ThreadMutex); - - auto threadHook = std::find(AntiCheat::OwnThreadIds.begin(), AntiCheat::OwnThreadIds.end(), GetCurrentThreadId()); - if (threadHook != AntiCheat::OwnThreadIds.end()) - { - AntiCheat::OwnThreadIds.erase(threadHook); - kill = false; - } - } - - if (kill) - { - static bool first = true; - if (first) first = false; // We can't control the main thread, as it's spawned externally - else - { - std::lock_guard _(AntiCheat::ThreadMutex); - - HMODULE ntdll = Utils::GetNTDLL(), targetModule; - if (!ntdll) return; // :( - - void* address = Utils::GetThreadStartAddress(GetCurrentThread()); - if (address) - { - GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(address), &targetModule); - if (targetModule == ntdll) return; // Better not kill kernel threads - - DWORD id = GetCurrentThreadId(); - { - auto threadHook = AntiCheat::ThreadHookMap.find(id); - if (threadHook != AntiCheat::ThreadHookMap.end()) - { - threadHook->second->uninstall(false); - AntiCheat::ThreadHookMap.erase(threadHook); - } - } - - std::shared_ptr hook = std::make_shared(); - AntiCheat::ThreadHookMap[id] = hook; - - // Hook the entry point of the thread to properly terminate it - hook->initialize(address, AntiCheat::ThreadEntryPointStub, HOOK_CALL)->install(true, true); - } - } - } - __VMProtectEnd; - } - - void AntiCheat::SystemTimeDiff(LPSYSTEMTIME stA, LPSYSTEMTIME stB, LPSYSTEMTIME stC) { - FILETIME ftA, ftB, ftC; - ULARGE_INTEGER uiA, uiB, uiC; - - SystemTimeToFileTime(stA, &ftA); - SystemTimeToFileTime(stB, &ftB); - uiA.HighPart = ftA.dwHighDateTime; - uiA.LowPart = ftA.dwLowDateTime; - uiB.HighPart = ftB.dwHighDateTime; - uiB.LowPart = ftB.dwLowDateTime; - - uiC.QuadPart = uiA.QuadPart - uiB.QuadPart; - - ftC.dwHighDateTime = uiC.HighPart; - ftC.dwLowDateTime = uiC.LowPart; - FileTimeToSystemTime(&ftC, stC); - } - - void AntiCheat::CheckStartupTime() - { - __VMProtectBeginUltra(""); - FILETIME creation, exit, kernel, user; - SYSTEMTIME current, creationSt, diffSt; - - GetSystemTime(¤t); - GetProcessTimes(GetCurrentProcess(), &creation, &exit, &kernel, &user); - - FileTimeToSystemTime(&creation, &creationSt); - AntiCheat::SystemTimeDiff(¤t, &creationSt, &diffSt); - -#ifdef DEBUG - char buf[512]; - snprintf(buf, 512, "creation: %d:%d:%d:%d\n", creationSt.wHour, creationSt.wMinute, creationSt.wSecond, creationSt.wMilliseconds); - OutputDebugStringA(buf); - - snprintf(buf, 512, "current: %d:%d:%d:%d\n", current.wHour, current.wMinute, current.wSecond, current.wMilliseconds); - OutputDebugStringA(buf); - - snprintf(buf, 512, "diff: %d:%d:%d:%d\n", diffSt.wHour, diffSt.wMinute, diffSt.wSecond, diffSt.wMilliseconds); - OutputDebugStringA(buf); -#endif - - // crash client if they are using process suspension to inject dlls during startup (aka before we got to here) - // maybe tweak this value depending on what the above logging reveals during testing, - // but 5 seconds seems about right for now - int time = diffSt.wMilliseconds + (diffSt.wSecond * 1000) + (diffSt.wMinute * 1000 * 60); - if (time > 5000) { - Components::AntiCheat::CrashClient(); - } - - // use below for logging when using StartSuspended.exe - // FILE* f = fopen("times.txt", "a"); - // fwrite(buf, 1, strlen(buf), f); - // fclose(f); - - __VMProtectEnd; - } - - AntiCheat::AntiCheat() - { - __VMProtectBeginUltra(""); - - time(nullptr); - AntiCheat::Flags = NO_FLAG; - -#ifndef DISABLE_ANTICHEAT - - Utils::Hook(0x507BD5, AntiCheat::PatchWinAPI, HOOK_CALL).install()->quick(); - Utils::Hook(0x5082FD, AntiCheat::LostD3DStub, HOOK_CALL).install()->quick(); - Utils::Hook(0x51C76C, AntiCheat::CinematicStub, HOOK_CALL).install()->quick(); - Utils::Hook(0x418209, AntiCheat::SoundInitStub, HOOK_CALL).install()->quick(); - Utils::Hook(0x60BE9D, AntiCheat::SoundInitStub, HOOK_CALL).install()->quick(); - Utils::Hook(0x60BE8E, AntiCheat::SoundInitDriverStub, HOOK_CALL).install()->quick(); - Utils::Hook(0x418204, AntiCheat::SoundInitDriverStub, HOOK_CALL).install()->quick(); - Scheduler::OnFrame(AntiCheat::PerformScan, true); - - // Detect aimbots - Utils::Hook(0x426580, AntiCheat::DObjGetWorldTagPosStub, HOOK_JUMP).install()->quick(); - Utils::Hook(0x56AC60, AntiCheat::AimTargetGetTagPosStub, HOOK_JUMP).install()->quick(); - - // TODO: Probably move that :P - if (!Dedicated::IsEnabled()) - { - AntiCheat::InitLoadLibHook(); - } - - // Prevent external processes from accessing our memory - AntiCheat::ProtectProcess(); - Renderer::OnDeviceRecoveryEnd([]() - { - AntiCheat::ProtectProcess(); - }); - - // Set the integrity flag - AntiCheat::Flags |= AntiCheat::IntergrityFlag::INITIALIZATION; - -#endif - - __VMProtectEnd; - } - - AntiCheat::~AntiCheat() - { - AntiCheat::Flags = NO_FLAG; - AntiCheat::OwnThreadIds.clear(); - AntiCheat::ThreadHookMap.clear(); - - for (int i = 0; i < ARRAYSIZE(AntiCheat::LoadLibHook); ++i) - { - AntiCheat::LoadLibHook[i].uninstall(); - } - - for (int i = 0; i < ARRAYSIZE(AntiCheat::VirtualProtectHook); ++i) - { - AntiCheat::VirtualProtectHook[i].uninstall(false); - } - } -} diff --git a/src/Components/Modules/AntiCheat.hpp b/src/Components/Modules/AntiCheat.hpp deleted file mode 100644 index 9cbb2ff3..00000000 --- a/src/Components/Modules/AntiCheat.hpp +++ /dev/null @@ -1,120 +0,0 @@ -#pragma once - -#ifndef DEBUG -// Hide AntiCheat in embeded symbol names -#define AntiCheat SubComponent -#else -# ifndef DISABLE_ANTICHEAT -# define DISABLE_ANTICHEAT -# endif -#endif - -// Uncomment to enable process protection (conflicts with steam!) -#define PROCTECT_PROCESS - -namespace Components -{ - class AntiCheat : public Component - { - public: - AntiCheat(); - ~AntiCheat(); - - class LibUnlocker - { - public: - LibUnlocker() - { - UninstallLibHook(); - } - ~LibUnlocker() - { - InstallLibHook(); - } - }; - - static void CrashClient(); - - static void InitLoadLibHook(); - - static void ReadIntegrityCheck(); - static void ScanIntegrityCheck(); - static void FlagIntegrityCheck(); - - static unsigned long ProtectProcess(); - - static void PatchVirtualProtect(void* vp, void* vpex); - static void PatchThreadCreation(); - - static void VerifyThreadIntegrity(); - - static void QuickCodeScanner1(); - static void QuickCodeScanner2(); - - static void UninstallLibHook(); - static void InstallLibHook(); - - static void CheckStartupTime(); - static void SystemTimeDiff(LPSYSTEMTIME stA, LPSYSTEMTIME stB, LPSYSTEMTIME stC); - - private: - enum IntergrityFlag - { - NO_FLAG = (0), - INITIALIZATION = (1 << 0), - MEMORY_SCAN = (1 << 1), - SCAN_INTEGRITY_CHECK = (1 << 2), - -#ifdef PROCTECT_PROCESS - READ_INTEGRITY_CHECK = (1 << 3), -#endif - - MAX_FLAG, - }; - - static Utils::Time::Interval LastCheck; - static unsigned long Flags; - - static void PerformScan(); - static void PatchWinAPI(); - - static void NullSub(); - - static bool IsPageChangeAllowed(void* callee, void* addr, size_t len); - static void AssertCalleeModule(void* callee); - -#ifdef DEBUG_LOAD_LIBRARY - static HANDLE LoadLibary(std::wstring library, HANDLE file, DWORD flags, void* callee); - static HANDLE WINAPI LoadLibaryAStub(const char* library); - static HANDLE WINAPI LoadLibaryWStub(const wchar_t* library); - static HANDLE WINAPI LoadLibaryExAStub(const char* library, HANDLE file, DWORD flags); - static HANDLE WINAPI LoadLibaryExWStub(const wchar_t* library, HANDLE file, DWORD flags); -#endif - - static BOOL WINAPI VirtualProtectStub(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect); - static BOOL WINAPI VirtualProtectExStub(HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect); - - static void LostD3DStub(); - static void CinematicStub(); - static void SoundInitStub(int a1, int a2, int a3); - static void SoundInitDriverStub(); - - static void DObjGetWorldTagPosStub(); - static void AimTargetGetTagPosStub(); - - static void AcquireDebugPrivilege(HANDLE hToken); - - static NTSTATUS NTAPI NtCreateThreadExStub(PHANDLE hThread, ACCESS_MASK desiredAccess, LPVOID objectAttributes, HANDLE processHandle, LPTHREAD_START_ROUTINE startAddress, LPVOID parameter, BOOL createSuspended, DWORD stackZeroBits, DWORD sizeOfStackCommit, DWORD sizeOfStackReserve, LPVOID bytesBuffer); - static int ValidateThreadTermination(void* addr); - static void ThreadEntryPointStub(); - - static std::mutex ThreadMutex; - static std::vector OwnThreadIds; - static std::map> ThreadHookMap; - - static Utils::Hook CreateThreadHook; - static Utils::Hook LoadLibHook[6]; - static Utils::Hook VirtualProtectHook[2]; - }; -} - diff --git a/src/Components/Modules/AssetHandler.cpp b/src/Components/Modules/AssetHandler.cpp index b3c7057a..06a03471 100644 --- a/src/Components/Modules/AssetHandler.cpp +++ b/src/Components/Modules/AssetHandler.cpp @@ -520,7 +520,7 @@ namespace Components { for (auto& asset : AssetHandler::EmptyAssets) { - Game::Sys_Error(25, reinterpret_cast(0x724428), Game::DB_GetXAssetTypeName(asset.first), asset.second.data()); + Game::Com_PrintWarning(25, reinterpret_cast(0x724428), Game::DB_GetXAssetTypeName(asset.first), asset.second.data()); } AssetHandler::EmptyAssets.clear(); diff --git a/src/Components/Modules/Bans.cpp b/src/Components/Modules/Bans.cpp index d179cc7b..8e43cc2d 100644 --- a/src/Components/Modules/Bans.cpp +++ b/src/Components/Modules/Bans.cpp @@ -174,7 +174,7 @@ namespace Components return; } - if (*Game::svs_numclients <= num) + if (*Game::svs_clientCount <= num) { Logger::Print("Player %d is not on the server\n", num); return; @@ -185,9 +185,9 @@ namespace Components SteamID guid; guid.bits = client->steamID; - Bans::InsertBan({ guid, client->netchan.remoteAddress.ip }); + Bans::InsertBan({guid, client->netchan.remoteAddress.ip}); - Game::SV_KickClientError(client, reason); + Game::SV_GameDropClient(num, reason.data()); } void Bans::UnbanClient(SteamID id) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index d177c573..edccc68b 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -1,104 +1,61 @@ #include -#define KEY_MASK_FIRE 1 -#define KEY_MASK_SPRINT 2 -#define KEY_MASK_MELEE 4 -#define KEY_MASK_RELOAD 16 -#define KEY_MASK_LEANLEFT 64 -#define KEY_MASK_LEANRIGHT 128 -#define KEY_MASK_PRONE 256 -#define KEY_MASK_CROUCH 512 -#define KEY_MASK_JUMP 1024 -#define KEY_MASK_ADS_MODE 2048 -#define KEY_MASK_TEMP_ACTION 4096 -#define KEY_MASK_HOLDBREATH 8192 -#define KEY_MASK_FRAG 16384 -#define KEY_MASK_SMOKE 32768 -#define KEY_MASK_NIGHTVISION 262144 -#define KEY_MASK_ADS 524288 -#define KEY_MASK_USE 0x28 - -#define MAX_G_BOTAI_ENTRIES 18 - namespace Components { std::vector Bots::BotNames; - typedef struct BotMovementInfo_t + struct BotMovementInfo { - /* Actions */ - int buttons; - /* Movement */ - int8 forward; - int8 right; - /* Weapon */ - unsigned short weapon; - } BotMovementInfo_t; + int buttons; // Actions + int8_t forward; + int8_t right; + uint16_t weapon; + bool active; + }; - static BotMovementInfo_t g_botai[MAX_G_BOTAI_ENTRIES]; + static BotMovementInfo g_botai[18]; - struct BotAction_t + struct BotAction { - const char* action; + std::string action; int key; }; - static const BotAction_t BotActions[] = + static const BotAction BotActions[] = { - { "gostand", KEY_MASK_JUMP }, - { "gocrouch", KEY_MASK_CROUCH }, - { "goprone", KEY_MASK_PRONE }, - { "fire", KEY_MASK_FIRE }, - { "melee", KEY_MASK_MELEE }, - { "frag", KEY_MASK_FRAG }, - { "smoke", KEY_MASK_SMOKE }, - { "reload", KEY_MASK_RELOAD }, - { "sprint", KEY_MASK_SPRINT }, - { "leanleft", KEY_MASK_LEANLEFT }, - { "leanright", KEY_MASK_LEANRIGHT }, - { "ads", KEY_MASK_ADS_MODE }, - { "holdbreath", KEY_MASK_HOLDBREATH }, - { "use", KEY_MASK_USE }, - { "0", 8 }, - { "1", 32 }, - { "2", 65536 }, - { "3", 131072 }, - { "4", 1048576 }, - { "5", 2097152 }, - { "6", 4194304 }, - { "7", 8388608 }, - { "8", 16777216 }, - { "9", 33554432 }, + { "gostand", Game::usercmdButtonBits::CMD_BUTTON_UP }, + { "gocrouch", Game::usercmdButtonBits::CMD_BUTTON_CROUCH }, + { "goprone", Game::usercmdButtonBits::CMD_BUTTON_PRONE }, + { "fire", Game::usercmdButtonBits::CMD_BUTTON_ATTACK }, + { "melee", Game::usercmdButtonBits::CMD_BUTTON_MELEE }, + { "frag", Game::usercmdButtonBits::CMD_BUTTON_FRAG }, + { "smoke", Game::usercmdButtonBits::CMD_BUTTON_OFFHAND_SECONDARY }, + { "reload", Game::usercmdButtonBits::CMD_BUTTON_RELOAD }, + { "sprint", Game::usercmdButtonBits::CMD_BUTTON_SPRINT }, + { "leanleft", Game::usercmdButtonBits::CMD_BUTTON_LEAN_LEFT }, + { "leanright", Game::usercmdButtonBits::CMD_BUTTON_LEAN_RIGHT }, + { "ads", Game::usercmdButtonBits::CMD_BUTTON_ADS }, + { "holdbreath", Game::usercmdButtonBits::CMD_BUTTON_BREATH }, + { "usereload", Game::usercmdButtonBits::CMD_BUTTON_USE_RELOAD }, + { "activate", Game::usercmdButtonBits::CMD_BUTTON_ACTIVATE }, }; - unsigned int Bots::GetClientNum(Game::client_s* cl) + int Bots::BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port) { - unsigned int num; - - num = ((byte*)cl - (byte*)Game::svs_clients) / sizeof(Game::client_s); - - return num; - } - - bool Bots::IsValidClientNum(unsigned int cNum) - { - return (cNum >= 0) && (cNum < (unsigned int)*Game::svs_numclients); - } - - void Bots::BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port) - { - static int botId = 0; + static size_t botId = 0; + static bool loadedNames = false; // Load file only once const char* botName; - if (Bots::BotNames.empty()) + if (Bots::BotNames.empty() && !loadedNames) { FileSystem::File bots("bots.txt"); + loadedNames = true; if (bots.exists()) { - std::vector names = Utils::String::Split(bots.getBuffer(), '\n'); + auto names = Utils::String::Split(bots.getBuffer(), '\n'); - for (auto name : names) + for (auto& name : names) { Utils::String::Replace(name, "\r", ""); name = Utils::String::Trim(name); @@ -121,276 +78,253 @@ namespace Components botName = Utils::String::VA("bot%d", ++botId); } - strncpy_s(buffer, 0x400, Utils::String::VA(connectString, num, botName, protocol, checksum, statVer, statStuff, port), 0x400); + return _snprintf_s(buffer, 0x400, _TRUNCATE, connectString, num, botName, protocol, checksum, statVer, statStuff, port); } void Bots::Spawn(unsigned int count) { - for (unsigned int i = 0; i < count; ++i) + for (auto i = 0u; i < count; ++i) { Scheduler::OnDelay([]() { - for (int i = 0; i < 3; ++i) + auto* ent = Game::SV_AddTestClient(); + if (ent == nullptr) + return; + + Scheduler::OnDelay([ent]() { - Game::gentity_t* entRef = Game::SV_AddTestClient(); - if (entRef) + Game::Scr_AddString("autoassign"); + Game::Scr_AddString("team_marinesopfor"); + Game::Scr_Notify(ent, Game::SL_GetString("menuresponse", 0), 2); + + Scheduler::OnDelay([ent]() { - Scheduler::OnDelay([entRef]() - { - Game::Scr_AddString("autoassign"); - Game::Scr_AddString("team_marinesopfor"); - Game::Scr_Notify(entRef, Game::SL_GetString("menuresponse", 0), 2); - - Scheduler::OnDelay([entRef]() - { - Game::Scr_AddString(Utils::String::VA("class%d", Utils::Cryptography::Rand::GenerateInt() % 5)); - Game::Scr_AddString("changeclass"); - Game::Scr_Notify(entRef, Game::SL_GetString("menuresponse", 0), 2); - }, 1s); - }, 1s); - - break; - } - } + Game::Scr_AddString(Utils::String::VA("class%u", Utils::Cryptography::Rand::GenerateInt() % 5u)); + Game::Scr_AddString("changeclass"); + Game::Scr_Notify(ent, Game::SL_GetString("menuresponse", 0), 2); + }, 1s); + }, 1s); }, 500ms * (i + 1)); } } void Bots::AddMethods() { - Script::AddFunction("SetPing", [](Game::scr_entref_t id) // gsc: self SetPing() + Script::AddMethod("SetPing", [](Game::scr_entref_t entref) // gsc: self SetPing() { - if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_INTEGER) - { - Game::Scr_Error("^1SetPing: Needs one integer parameter!\n"); - return; - } - auto ping = Game::Scr_GetInt(0); - if (ping < 0 || ping > 999) - { - Game::Scr_Error("^1SetPing: Ping needs to between 0 and 999!\n"); - return; - } + ping = std::clamp(ping, 0, 999); - Game::gentity_t* gentity = Script::getEntFromEntRef(id); - Game::client_t* client = Script::getClientFromEnt(gentity); - unsigned int clientNum = GetClientNum(client); + const auto* ent = Game::GetPlayerEntity(entref); + auto* client = Script::GetClient(ent); - if (!Bots::IsValidClientNum(clientNum)) - { - Game::Scr_Error("^1SetPing: Need to call on a player entity!\n"); - return; - } - - if (client->state < 3) - { - Game::Scr_Error("^1SetPing: Need to call on a connected player!\n"); - return; - } - - if (!client->isBot) + if (!client->bIsTestClient) { Game::Scr_Error("^1SetPing: Can only call on a bot!\n"); return; } - client->ping = (short)ping; + client->ping = static_cast(ping); }); - Script::AddFunction("isBot", [](Game::scr_entref_t id) // Usage: isBot(); + Script::AddMethod("IsTestClient", [](Game::scr_entref_t entref) // Usage: IsTestClient(); { - Game::gentity_t* gentity = Script::getEntFromEntRef(id); - Game::client_t* client = Script::getClientFromEnt(gentity); - unsigned int clientNum = GetClientNum(client); + const auto* gentity = Game::GetPlayerEntity(entref); + const auto* client = Script::GetClient(gentity); - if (!Bots::IsValidClientNum(clientNum)) - { - Game::Scr_Error("^1isBot: Need to call on a player entity!\n"); - return; - } - - if (client->state < 3) - { - Game::Scr_Error("^1isBot: Needs to be connected.\n"); - return; - } - - Game::Scr_AddInt(client->isBot); + Game::Scr_AddBool(client->bIsTestClient == 1); }); - Script::AddFunction("botStop", [](Game::scr_entref_t id) // Usage: botStop(); + Script::AddMethod("BotStop", [](Game::scr_entref_t entref) // Usage: BotStop(); { - Game::gentity_t* gentity = Script::getEntFromEntRef(id); - Game::client_t* client = Script::getClientFromEnt(gentity); - unsigned int clientNum = GetClientNum(client); + const auto* ent = Game::GetPlayerEntity(entref); + const auto* client = Script::GetClient(ent); - if (!Bots::IsValidClientNum(clientNum)) + if (!client->bIsTestClient) { - Game::Scr_Error("^1botStop: Need to call on a player entity!\n"); + Game::Scr_Error("^1BotStop: Can only call on a bot!\n"); return; } - if (client->state < 3) - { - Game::Scr_Error("^1botStop: Needs to be connected.\n"); - return; - } - - if (!client->isBot) - { - Game::Scr_Error("^1botStop: Can only call on a bot!\n"); - return; - } - - g_botai[clientNum] = { 0 }; - g_botai[clientNum].weapon = 1; + g_botai[entref.entnum] = {0}; + g_botai[entref.entnum].weapon = 1; + g_botai[entref.entnum].active = true; }); - Script::AddFunction("botWeapon", [](Game::scr_entref_t id) // Usage: botWeapon(); + Script::AddMethod("BotWeapon", [](Game::scr_entref_t entref) // Usage: BotWeapon(); { - if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) + const auto* weapon = Game::Scr_GetString(0); + + const auto* ent = Game::GetPlayerEntity(entref); + const auto* client = Script::GetClient(ent); + + if (!client->bIsTestClient) { - Game::Scr_Error("^1botWeapon: Needs one string parameter!\n"); + Game::Scr_Error("^1BotWeapon: Can only call on a bot!\n"); return; } - auto weapon = Game::Scr_GetString(0); - - Game::gentity_t* gentity = Script::getEntFromEntRef(id); - Game::client_t* client = Script::getClientFromEnt(gentity); - unsigned int clientNum = GetClientNum(client); - - if (!Bots::IsValidClientNum(clientNum)) + if (weapon == nullptr || weapon[0] == '\0') { - Game::Scr_Error("^1botWeapon: Need to call on a player entity!\n"); + g_botai[entref.entnum].weapon = 1; return; } - if (client->state < 3) - { - Game::Scr_Error("^1botWeapon: Needs to be connected.\n"); - return; - } - - if (!client->isBot) - { - Game::Scr_Error("^1botWeapon: Can only call on a bot!\n"); - return; - } - - if (weapon == ""s) - { - g_botai[clientNum].weapon = 1; - return; - } - - int weapId = Game::G_GetWeaponIndexForName(weapon); - - g_botai[clientNum].weapon = (unsigned short)weapId; + const auto weapId = Game::G_GetWeaponIndexForName(weapon); + g_botai[entref.entnum].weapon = static_cast(weapId); + g_botai[entref.entnum].active = true; }); - Script::AddFunction("botAction", [](Game::scr_entref_t id) // Usage: botAction(); + Script::AddMethod("BotAction", [](Game::scr_entref_t entref) // Usage: BotAction(); { - if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) + const auto* action = Game::Scr_GetString(0); + + if (action == nullptr) { - Game::Scr_Error("^1botAction: Needs one string parameter!\n"); + Game::Scr_ParamError(0, "^1BotAction: Illegal parameter!\n"); return; } - auto action = Game::Scr_GetString(0); + const auto* ent = Game::GetPlayerEntity(entref); + const auto* client = Script::GetClient(ent); - Game::gentity_t* gentity = Script::getEntFromEntRef(id); - Game::client_t* client = Script::getClientFromEnt(gentity); - unsigned int clientNum = GetClientNum(client); - - if (!Bots::IsValidClientNum(clientNum)) + if (!client->bIsTestClient) { - Game::Scr_Error("^1botAction: Need to call on a player entity!\n"); + Game::Scr_Error("^1BotAction: Can only call on a bot!\n"); return; } - if (client->state < 3) - { - Game::Scr_Error("^1botAction: Needs to be connected.\n"); - return; - } - - if (!client->isBot) - { - Game::Scr_Error("^1botAction: Can only call on a bot!\n"); - return; - } if (action[0] != '+' && action[0] != '-') { - Game::Scr_Error("^1botAction: Sign for action must be '+' or '-'.\n"); + Game::Scr_ParamError(0, "^1BotAction: Sign for action must be '+' or '-'.\n"); return; } - for (size_t i = 0; i < sizeof(BotActions) / sizeof(BotAction_t); ++i) + for (auto i = 0u; i < std::extent_v; ++i) { - if (strcmp(&action[1], BotActions[i].action)) + if (Utils::String::ToLower(&action[1]) != BotActions[i].action) continue; if (action[0] == '+') - g_botai[clientNum].buttons |= BotActions[i].key; + g_botai[entref.entnum].buttons |= BotActions[i].key; else - g_botai[clientNum].buttons &= ~(BotActions[i].key); + g_botai[entref.entnum].buttons &= ~BotActions[i].key; + g_botai[entref.entnum].active = true; return; } - Game::Scr_Error("^1botAction: Unknown action.\n"); + Game::Scr_ParamError(0, "^1BotAction: Unknown action.\n"); }); - Script::AddFunction("botMovement", [](Game::scr_entref_t id) // Usage: botMovement(, ); + Script::AddMethod("BotMovement", [](Game::scr_entref_t entref) // Usage: BotMovement(, ); { - if (Game::Scr_GetNumParam() != 2u || Game::Scr_GetType(0) != Game::VAR_INTEGER || Game::Scr_GetType(1) != Game::VAR_INTEGER) - { - Game::Scr_Error("^1botMovement: Needs two integer parameters!\n"); - return; - } - auto forwardInt = Game::Scr_GetInt(0); auto rightInt = Game::Scr_GetInt(1); - Game::gentity_t* gentity = Script::getEntFromEntRef(id); - Game::client_t* client = Script::getClientFromEnt(gentity); - unsigned int clientNum = GetClientNum(client); + const auto* ent = Game::GetPlayerEntity(entref); + const auto* client = Script::GetClient(ent); - if (!Bots::IsValidClientNum(clientNum)) + if (!client->bIsTestClient) { - Game::Scr_Error("^1botMovement: Need to call on a player entity!\n"); + Game::Scr_Error("^1BotMovement: Can only call on a bot!\n"); return; } - if (client->state < 3) - { - Game::Scr_Error("^1botMovement: Needs to be connected.\n"); - return; - } + forwardInt = std::clamp(forwardInt, std::numeric_limits::min(), std::numeric_limits::max()); + rightInt = std::clamp(rightInt, std::numeric_limits::min(), std::numeric_limits::max()); - if (!client->isBot) - { - Game::Scr_Error("^1botMovement: Can only call on a bot!\n"); - return; - } - - if (forwardInt > 127) - forwardInt = 127; - if (forwardInt < -127) - forwardInt = -127; - if (rightInt > 127) - rightInt = 127; - if (rightInt < -127) - rightInt = -127; - - g_botai[clientNum].forward = (int8)forwardInt; - g_botai[clientNum].right = (int8)rightInt; + g_botai[entref.entnum].forward = static_cast(forwardInt); + g_botai[entref.entnum].right = static_cast(rightInt); + g_botai[entref.entnum].active = true; }); } + void Bots::BotAiAction(Game::client_t* cl) + { + if (cl->gentity == nullptr) + return; + + const auto entnum = cl->gentity->s.number; + + // Keep test client functionality + if (!g_botai[entnum].active) + { + Game::SV_BotUserMove(cl); + return; + } + + Game::usercmd_s userCmd = {0}; + + userCmd.serverTime = *Game::svs_time; + + userCmd.buttons = g_botai[entnum].buttons; + userCmd.forwardmove = g_botai[entnum].forward; + userCmd.rightmove = g_botai[entnum].right; + userCmd.weapon = g_botai[entnum].weapon; + + Game::SV_ClientThink(cl, &userCmd); + } + + constexpr auto SV_BotUserMove = 0x626E50; + __declspec(naked) void Bots::SV_BotUserMove_Hk() + { + __asm + { + pushad + + push edi + call Bots::BotAiAction + add esp, 4 + + popad + ret + } + } + + void Bots::G_SelectWeaponIndex(int clientNum, int iWeaponIndex) + { + if (g_botai[clientNum].active) + { + g_botai[clientNum].weapon = static_cast(iWeaponIndex); + } + } + + __declspec(naked) void Bots::G_SelectWeaponIndex_Hk() + { + __asm + { + pushad + + push [esp + 0x20 + 0x8] + push [esp + 0x20 + 0x8] + call Bots::G_SelectWeaponIndex + add esp, 0x8 + + popad + + // Code skipped by hook + mov eax, [esp + 0x8] + push eax + + push 0x441B85 + retn + } + } + + /* + * Should be called when a client drops from the server + * but not "between levels" (Quake-III-Arena) + */ + void Bots::ClientDisconnect_Hk(int clientNum) + { + g_botai[clientNum].active = false; + + // Call original function + Utils::Hook::Call(0x4AA430)(clientNum); + } + Bots::Bots() { // Replace connect string @@ -399,61 +333,48 @@ namespace Components // Intercept sprintf for the connect string Utils::Hook(0x48ADAB, Bots::BuildConnectString, HOOK_CALL).install()->quick(); - // Stop default behavour of bots spinning and shooting - Utils::Hook(0x627021, 0x4BB9B0, HOOK_CALL).install()->quick(); - Utils::Hook(0x627241, 0x4BB9B0, HOOK_CALL).install()->quick(); + Utils::Hook(0x627021, Bots::SV_BotUserMove_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x627241, Bots::SV_BotUserMove_Hk, HOOK_CALL).install()->quick(); - // zero the bot command array - for (int i = 0; i < MAX_G_BOTAI_ENTRIES; i++) + Utils::Hook(0x441B80, Bots::G_SelectWeaponIndex_Hk, HOOK_JUMP).install()->quick(); + + // Reset BotMovementInfo.active when client is dropped + Utils::Hook(0x625235, Bots::ClientDisconnect_Hk, HOOK_CALL).install()->quick(); + + // Zero the bot command array + for (auto i = 0u; i < std::extent_v; i++) { - g_botai[i] = { 0 }; - g_botai[i].weapon = 1; // prevent the bots from defaulting to the 'none' weapon + g_botai[i] = {0}; + g_botai[i].weapon = 1; // Prevent the bots from defaulting to the 'none' weapon } - // have the bots perform the command every server frame - Scheduler::OnFrame([]() - { - if (!Game::SV_Loaded()) - return; - - int time = *Game::svs_time; - int numClients = *Game::svs_numclients; - for (int i = 0; i < numClients; ++i) - { - Game::client_t* client = &Game::svs_clients[i]; - - if (client->state < 3) - continue; - - if (!client->isBot) - continue; - - Game::usercmd_s ucmd = { 0 }; - - ucmd.serverTime = time; - - ucmd.buttons = g_botai[i].buttons; - ucmd.forwardmove = g_botai[i].forward; - ucmd.rightmove = g_botai[i].right; - ucmd.weapon = g_botai[i].weapon; - - client->deltaMessage = client->netchan.outgoingSequence - 1; - - Game::SV_ClientThink(client, &ucmd); - } - }); - Command::Add("spawnBot", [](Command::Params* params) { - unsigned int count = 1; + auto count = 1u; if (params->size() > 1) { - if (params->get(1) == "all"s) count = static_cast(-1); - else count = atoi(params->get(1)); + if (params->get(1) == "all"s) + { + count = *Game::svs_clientCount; + } + else + { + char* end; + const auto* input = params->get(1); + count = std::strtoul(input, &end, 10); + + if (input == end) + { + Logger::Print("Warning: %s is not a valid input\n" + "Usage: %s optional or optional <\"all\">\n", + input, params->get(0)); + return; + } + } } - count = std::min(18u, count); + count = std::min(static_cast(*Game::svs_clientCount), count); // Check if ingame and host if (!Game::SV_Loaded()) @@ -470,10 +391,14 @@ namespace Components }); Bots::AddMethods(); - } - Bots::~Bots() - { - Bots::BotNames.clear(); + // In case a loaded mod didn't call "BotStop" before the VM shutdown + Script::OnVMShutdown([] + { + for (auto i = 0u; i < std::extent_v; i++) + { + g_botai[i].active = false; + } + }); } } diff --git a/src/Components/Modules/Bots.hpp b/src/Components/Modules/Bots.hpp index ee8381aa..910bacfa 100644 --- a/src/Components/Modules/Bots.hpp +++ b/src/Components/Modules/Bots.hpp @@ -6,17 +6,22 @@ namespace Components { public: Bots(); - ~Bots(); - static unsigned int GetClientNum(Game::client_s*); - static bool IsValidClientNum(unsigned int); private: static std::vector BotNames; - static void BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port); + static int BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port); static void Spawn(unsigned int count); static void AddMethods(); + + static void BotAiAction(Game::client_t* cl); + static void SV_BotUserMove_Hk(); + + static void G_SelectWeaponIndex(int clientNum, int iWeaponIndex); + static void G_SelectWeaponIndex_Hk(); + + static void ClientDisconnect_Hk(int clientNum); }; } diff --git a/src/Components/Modules/Bullet.cpp b/src/Components/Modules/Bullet.cpp new file mode 100644 index 00000000..c07476d7 --- /dev/null +++ b/src/Components/Modules/Bullet.cpp @@ -0,0 +1,60 @@ +#include + +namespace Components +{ + Dvar::Var Bullet::BGSurfacePenetration; + Game::dvar_t* Bullet::BGBulletRange; + + float Bullet::BG_GetSurfacePenetrationDepthStub(const Game::WeaponDef* weapDef, int surfaceType) + { + assert(weapDef != nullptr); + assert(weapDef->penetrateType != Game::PenetrateType::PENETRATE_TYPE_NONE); + assert(weapDef->penetrateType < Game::PenetrateType::PENETRATE_TYPE_COUNT); + assert(static_cast(surfaceType) < Game::materialSurfType_t::SURF_TYPE_COUNT); + + const auto penetrationDepth = BGSurfacePenetration.get(); + if (penetrationDepth > 0.0f) + { + // Custom depth + return penetrationDepth; + } + + // Game's code + if (surfaceType != Game::materialSurfType_t::SURF_TYPE_DEFAULT) + { + return (*Game::penetrationDepthTable)[weapDef->penetrateType][surfaceType]; + } + + return 0.0f; + } + + __declspec(naked) void Bullet::Bullet_FireStub() + { + __asm + { + push eax + mov eax, BGBulletRange + fld dword ptr [eax + 0x10] // dvar_t.current.value + pop eax + + push 0x440346 + retn + } + } + + Bullet::Bullet() + { + Dvar::OnInit([] + { + BGSurfacePenetration = Dvar::Register("bg_surfacePenetration", 0.0f, + 0.0f, std::numeric_limits::max(), Game::dvar_flag::DVAR_CODINFO, + "Set to a value greater than 0 to override the surface penetration depth"); + BGBulletRange = Game::Dvar_RegisterFloat("bg_bulletRange", 8192.0f, + 0.0f, std::numeric_limits::max(), Game::dvar_flag::DVAR_CODINFO, + "Max range used when calculating the bullet end position"); + }); + + Utils::Hook(0x4F6980, BG_GetSurfacePenetrationDepthStub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x440340, Bullet_FireStub, HOOK_JUMP).install()->quick(); + } +} diff --git a/src/Components/Modules/Bullet.hpp b/src/Components/Modules/Bullet.hpp new file mode 100644 index 00000000..c1626205 --- /dev/null +++ b/src/Components/Modules/Bullet.hpp @@ -0,0 +1,19 @@ +#pragma once + +namespace Components +{ + class Bullet : public Component + { + public: + Bullet(); + + private: + static Dvar::Var BGSurfacePenetration; + // Can't use Var class inside assembly stubs + static Game::dvar_t* BGBulletRange; + + static float BG_GetSurfacePenetrationDepthStub(const Game::WeaponDef* weapDef, int surfaceType); + + static void Bullet_FireStub(); + }; +} diff --git a/src/Components/Modules/CardTitles.cpp b/src/Components/Modules/CardTitles.cpp index 396eebf5..e3ac4a5c 100644 --- a/src/Components/Modules/CardTitles.cpp +++ b/src/Components/Modules/CardTitles.cpp @@ -218,8 +218,5 @@ namespace Components // This is placed here in case the anticheat has been disabled! // This checks specifically for launching the process suspended to inject a dll -#if !defined(DISABLE_ANTICHEAT) - AntiCheat::CheckStartupTime(); -#endif } } diff --git a/src/Components/Modules/CardTitles.hpp b/src/Components/Modules/CardTitles.hpp index 0b43bdab..6f02b47a 100644 --- a/src/Components/Modules/CardTitles.hpp +++ b/src/Components/Modules/CardTitles.hpp @@ -11,6 +11,7 @@ namespace Components std::uint8_t padding3[4]; std::int32_t tableColumn; }; + struct playercarddata_s { std::uint32_t padding; diff --git a/src/Components/Modules/Changelog.cpp b/src/Components/Modules/Changelog.cpp index a6da0d83..93b5974d 100644 --- a/src/Components/Modules/Changelog.cpp +++ b/src/Components/Modules/Changelog.cpp @@ -55,9 +55,5 @@ namespace Components // Changelog UIFeeder::Add(62.0f, Changelog::GetChangelogCount, Changelog::GetChangelogText, Changelog::SelectChangelog); - -#ifndef DISABLE_ANTICHEAT - Scheduler::OnFrameAsync(AntiCheat::QuickCodeScanner1); -#endif } } diff --git a/src/Components/Modules/Chat.cpp b/src/Components/Modules/Chat.cpp index baa00267..9979fb89 100644 --- a/src/Components/Modules/Chat.cpp +++ b/src/Components/Modules/Chat.cpp @@ -55,16 +55,16 @@ namespace Components push eax pushad - push[esp + 100h + 28h] + push [esp + 100h + 28h] push eax call Chat::EvaluateSay add esp, 8h - mov[esp + 20h], eax + mov [esp + 20h], eax popad pop eax - mov[esp + 100h + 10h], eax + mov [esp + 100h + 10h], eax jmp PlayerName::CleanStrStub } @@ -97,7 +97,7 @@ namespace Components } void Chat::CheckChatLineEnd(const char*& inputBuffer, char*& lineBuffer, float& len, const int chatHeight, const float chatWidth, char*& lastSpacePos, char*& lastFontIconPos, const int lastColor) - { + { if (len > chatWidth) { if (lastSpacePos && lastSpacePos > lastFontIconPos) @@ -110,6 +110,7 @@ namespace Components inputBuffer += lastFontIconPos - lineBuffer; lineBuffer = lastFontIconPos; } + *lineBuffer = 0; len = 0.0f; Game::cgsArray[0].teamChatMsgTimes[Game::cgsArray[0].teamChatPos % chatHeight] = Game::cgArray[0].time; @@ -122,7 +123,7 @@ namespace Components lastSpacePos = nullptr; lastFontIconPos = nullptr; } - } + } void Chat::CG_AddToTeamChat(const char* text) { @@ -132,7 +133,7 @@ namespace Components const auto chatHeight = (*cg_chatHeight)->current.integer; const auto chatWidth = static_cast(cg_chatWidth.get()); const auto chatTime = (*cg_chatTime)->current.integer; - if (chatHeight < 0 || static_cast(chatHeight) > std::extent_v || chatWidth <= 0 || chatTime <= 0) + if (chatHeight <= 0 || static_cast(chatHeight) > std::extent_v || chatWidth <= 0 || chatTime <= 0) { Game::cgsArray[0].teamLastChatPos = 0; Game::cgsArray[0].teamChatPos = 0; @@ -152,7 +153,7 @@ namespace Components CheckChatLineEnd(text, p, len, chatHeight, chatWidth, lastSpace, lastFontIcon, lastColor); const char* fontIconEndPos = &text[1]; - if(text[0] == TextRenderer::FONT_ICON_SEPARATOR_CHARACTER && TextRenderer::IsFontIcon(fontIconEndPos, fontIconInfo)) + if (text[0] == TextRenderer::FONT_ICON_SEPARATOR_CHARACTER && TextRenderer::IsFontIcon(fontIconEndPos, fontIconInfo)) { // The game calculates width on a per character base. Since the width of a font icon is calculated based on the height of the font // which is roughly double as much as the average width of a character without an additional multiplier the calculated len of the font icon diff --git a/src/Components/Modules/Client.cpp b/src/Components/Modules/Client.cpp deleted file mode 100644 index fc881a36..00000000 --- a/src/Components/Modules/Client.cpp +++ /dev/null @@ -1,176 +0,0 @@ -#include - -namespace Components -{ - void Client::AddFunctions() - { - //File functions - - Script::AddFunction("fileWrite", [](Game::scr_entref_t) // gsc: fileWrite(, , ) - { - std::string path = Game::Scr_GetString(0); - auto text = Game::Scr_GetString(1); - auto mode = Game::Scr_GetString(2); - - if (path.empty()) - { - Game::Com_Printf(0, "^1fileWrite: filepath not defined!\n"); - return; - } - - std::vector queryStrings = { R"(..)", R"(../)", R"(..\)" }; - for (auto i = 0u; i < queryStrings.size(); i++) - { - if (path.find(queryStrings[i]) != std::string::npos) - { - Game::Com_Printf(0, "^1fileWrite: directory traversal is not allowed!\n"); - return; - } - } - - if (mode != "append"s && mode != "write"s) - { - Game::Com_Printf(0, "^3fileWrite: mode not defined or was wrong, defaulting to 'write'\n"); - mode = const_cast("write"); - } - - if (mode == "write"s) - { - FileSystem::FileWriter(path).write(text); - } - else if (mode == "append"s) - { - FileSystem::FileWriter(path, true).write(text); - } - }); - - Script::AddFunction("fileRead", [](Game::scr_entref_t) // gsc: fileRead() - { - std::string path = Game::Scr_GetString(0); - - if (path.empty()) - { - Game::Com_Printf(0, "^1fileRead: filepath not defined!\n"); - return; - } - - std::vector queryStrings = { R"(..)", R"(../)", R"(..\)" }; - for (auto i = 0u; i < queryStrings.size(); i++) - { - if (path.find(queryStrings[i]) != std::string::npos) - { - Game::Com_Printf(0, "^1fileRead: directory traversal is not allowed!\n"); - return; - } - } - - if (!FileSystem::FileReader(path).exists()) - { - Game::Com_Printf(0, "^1fileRead: file not found!\n"); - return; - } - - Game::Scr_AddString(FileSystem::FileReader(path).getBuffer().data()); - }); - - Script::AddFunction("fileExists", [](Game::scr_entref_t) // gsc: fileExists() - { - std::string path = Game::Scr_GetString(0); - - if (path.empty()) - { - Game::Com_Printf(0, "^1fileExists: filepath not defined!\n"); - return; - } - - std::vector queryStrings = { R"(..)", R"(../)", R"(..\)" }; - for (auto i = 0u; i < queryStrings.size(); i++) - { - if (path.find(queryStrings[i]) != std::string::npos) - { - Game::Com_Printf(0, "^1fileExists: directory traversal is not allowed!\n"); - return; - } - } - - Game::Scr_AddInt(FileSystem::FileReader(path).exists()); - }); - - Script::AddFunction("fileRemove", [](Game::scr_entref_t) // gsc: fileRemove() - { - std::string path = Game::Scr_GetString(0); - - if (path.empty()) - { - Game::Com_Printf(0, "^1fileRemove: filepath not defined!\n"); - return; - } - - std::vector queryStrings = { R"(..)", R"(../)", R"(..\)" }; - for (auto i = 0u; i < queryStrings.size(); i++) - { - if (path.find(queryStrings[i]) != std::string::npos) - { - Game::Com_Printf(0, "^1fileRemove: directory traversal is not allowed!\n"); - return; - } - } - - auto p = std::filesystem::path(path); - std::string folder = p.parent_path().string(); - std::string file = p.filename().string(); - Game::Scr_AddInt(FileSystem::DeleteFile(folder, file)); - }); - } - - void Client::AddMethods() - { - // Client methods - - Script::AddFunction("getIp", [](Game::scr_entref_t id) // gsc: self getIp() - { - Game::gentity_t* gentity = Script::getEntFromEntRef(id); - Game::client_t* client = Script::getClientFromEnt(gentity); - - if (client->state >= 3) - { - std::string ip = Game::NET_AdrToString(client->netchan.remoteAddress); - if (ip.find_first_of(":") != std::string::npos) - ip.erase(ip.begin() + ip.find_first_of(":"), ip.end()); // erase port - Game::Scr_AddString(ip.data()); - } - }); - - Script::AddFunction("getPing", [](Game::scr_entref_t id) // gsc: self getPing() - { - Game::gentity_t* gentity = Script::getEntFromEntRef(id); - Game::client_t* client = Script::getClientFromEnt(gentity); - - if (client->state >= 3) - { - int ping = (int)client->ping; - Game::Scr_AddInt(ping); - } - }); - } - - void Client::AddCommands() - { - Command::Add("NULL", [](Command::Params*) - { - return NULL; - }); - } - - Client::Client() - { - Client::AddFunctions(); - Client::AddMethods(); - Client::AddCommands(); - } - - Client::~Client() - { - - } -} \ No newline at end of file diff --git a/src/Components/Modules/Client.hpp b/src/Components/Modules/Client.hpp deleted file mode 100644 index 5c71b0a0..00000000 --- a/src/Components/Modules/Client.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -namespace Components -{ - class Client : public Component - { - public: - Client(); - ~Client(); - - private: - - static void AddFunctions(); - static void AddMethods(); - static void AddCommands(); - }; -} diff --git a/src/Components/Modules/ClientCommand.cpp b/src/Components/Modules/ClientCommand.cpp index 5f7ef964..060fb81f 100644 --- a/src/Components/Modules/ClientCommand.cpp +++ b/src/Components/Modules/ClientCommand.cpp @@ -2,7 +2,7 @@ namespace Components { - std::unordered_map> ClientCommand::FunctionMap; + std::unordered_map> ClientCommand::HandlersSV; bool ClientCommand::CheatsOk(const Game::gentity_s* ent) { @@ -17,7 +17,7 @@ namespace Components if (ent->health < 1) { - Logger::Print("CheatsOk: entity %u must be alive to use this command!\n", entNum); + Logger::Print("CheatsOk: entity %i must be alive to use this command!\n", entNum); Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"GAME_MUSTBEALIVECOMMAND\"", 0x65)); return false; } @@ -25,49 +25,38 @@ namespace Components return true; } - bool ClientCommand::CallbackHandler(Game::gentity_s* ent, const char* cmd) - { - const auto command = Utils::String::ToLower(cmd); - - if (ClientCommand::FunctionMap.find(command) != ClientCommand::FunctionMap.end()) - { - ClientCommand::FunctionMap[command](ent); - return true; - } - - return false; - } - - void ClientCommand::Add(const char* name, Utils::Slot callback) + void ClientCommand::Add(const char* name, std::function callback) { const auto command = Utils::String::ToLower(name); - ClientCommand::FunctionMap[command] = callback; + ClientCommand::HandlersSV[command] = std::move(callback); } void ClientCommand::ClientCommandStub(const int clientNum) { - char cmd[1024]{}; - const auto entity = &Game::g_entities[clientNum]; + const auto ent = &Game::g_entities[clientNum]; - if (entity->client == nullptr) + if (ent->client == nullptr) { Logger::Print("ClientCommand: client %d is not fully in game yet\n", clientNum); return; } - Game::SV_Cmd_ArgvBuffer(0, cmd, sizeof(cmd)); + Command::ServerParams params; + const auto command = Utils::String::ToLower(params.get(0)); - if (!ClientCommand::CallbackHandler(entity, cmd)) + if (const auto got = HandlersSV.find(command); got != HandlersSV.end()) { - // If no callback was found call original game function - Utils::Hook::Call(0x416790)(clientNum); + got->second(ent, ¶ms); + return; } + + Utils::Hook::Call(0x416790)(clientNum); } void ClientCommand::AddCheatCommands() { - ClientCommand::Add("noclip", [](Game::gentity_s* ent) + ClientCommand::Add("noclip", [](Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) { if (!ClientCommand::CheatsOk(ent)) return; @@ -75,13 +64,13 @@ namespace Components ent->client->flags ^= Game::PLAYER_FLAG_NOCLIP; const auto entNum = ent->s.number; - Logger::Print("Noclip toggled for entity %u\n", entNum); + Logger::Print("Noclip toggled for entity %i\n", entNum); Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PLAYER_FLAG_NOCLIP) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF")); }); - ClientCommand::Add("ufo", [](Game::gentity_s* ent) + ClientCommand::Add("ufo", [](Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) { if (!ClientCommand::CheatsOk(ent)) return; @@ -89,13 +78,13 @@ namespace Components ent->client->flags ^= Game::PLAYER_FLAG_UFO; const auto entNum = ent->s.number; - Logger::Print("UFO toggled for entity %u\n", entNum); + Logger::Print("UFO toggled for entity %i\n", entNum); Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PLAYER_FLAG_UFO) ? "GAME_UFOON" : "GAME_UFOOFF")); }); - ClientCommand::Add("god", [](Game::gentity_s* ent) + ClientCommand::Add("god", [](Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) { if (!ClientCommand::CheatsOk(ent)) return; @@ -103,13 +92,13 @@ namespace Components ent->flags ^= Game::FL_GODMODE; const auto entNum = ent->s.number; - Logger::Print("God toggled for entity %u\n", entNum); + Logger::Print("God toggled for entity %i\n", entNum); Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65, (ent->flags & Game::FL_GODMODE) ? "GAME_GODMODE_ON" : "GAME_GODMODE_OFF")); }); - ClientCommand::Add("demigod", [](Game::gentity_s* ent) + ClientCommand::Add("demigod", [](Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) { if (!ClientCommand::CheatsOk(ent)) return; @@ -117,13 +106,13 @@ namespace Components ent->flags ^= Game::FL_DEMI_GODMODE; const auto entNum = ent->s.number; - Logger::Print("Demigod toggled for entity %u\n", entNum); + Logger::Print("Demigod toggled for entity %i\n", entNum); Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65, (ent->flags & Game::FL_DEMI_GODMODE) ? "GAME_DEMI_GODMODE_ON" : "GAME_DEMI_GODMODE_OFF")); }); - ClientCommand::Add("notarget", [](Game::gentity_s* ent) + ClientCommand::Add("notarget", [](Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) { if (!ClientCommand::CheatsOk(ent)) return; @@ -131,23 +120,22 @@ namespace Components ent->flags ^= Game::FL_NOTARGET; const auto entNum = ent->s.number; - Logger::Print("Notarget toggled for entity %u\n", entNum); + Logger::Print("Notarget toggled for entity %i\n", entNum); Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65, (ent->flags & Game::FL_NOTARGET) ? "GAME_NOTARGETON" : "GAME_NOTARGETOFF")); }); - ClientCommand::Add("setviewpos", [](Game::gentity_s* ent) + ClientCommand::Add("setviewpos", [](Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) { assert(ent != nullptr); if (!ClientCommand::CheatsOk(ent)) return; - Command::ServerParams params = {}; Game::vec3_t origin, angles{0.f, 0.f, 0.f}; - if (params.size() < 4 || params.size() > 6) + if (params->size() < 4 || params->size() > 6) { Game::SV_GameSendServerCommand(ent->s.number, 0, Utils::String::VA("%c \"GAME_USAGE\x15: setviewpos x y z [yaw] [pitch]\n\"", 0x65)); @@ -156,149 +144,220 @@ namespace Components for (auto i = 0; i < 3; i++) { - origin[i] = std::strtof(params.get(i + 1), nullptr); + origin[i] = std::strtof(params->get(i + 1), nullptr); } - if (params.size() >= 5) + if (params->size() >= 5) { - angles[1] = std::strtof(params.get(4), nullptr); // Yaw + angles[1] = std::strtof(params->get(4), nullptr); // Yaw } - if (params.size() == 6) + if (params->size() == 6) { - angles[0] = std::strtof(params.get(5), nullptr); // Pitch + angles[0] = std::strtof(params->get(5), nullptr); // Pitch } Game::TeleportPlayer(ent, origin, angles); }); } + void ClientCommand::AddDevelopmentCommands() + { + ClientCommand::Add("dropallbots", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) + { + Game::SV_DropAllBots(); + }); + + ClientCommand::Add("entitylist", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) + { + Game::Svcmd_EntityList_f(); + }); + + ClientCommand::Add("printentities", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) + { + Game::G_PrintEntities(); + }); + + ClientCommand::Add("entitycount", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) + { + Logger::Print("Entity count = %i\n", Game::level->num_entities); + }); + + // Also known as: "vis" + ClientCommand::Add("visionsetnaked", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) + { + if (params->size() < 2) + { + Logger::Print("USAGE: visionSetNaked \n"); + return; + } + + auto duration = 1000; + if (params->size() > 2) + { + const auto input = std::strtof(params->get(2), nullptr); + duration = static_cast(std::floorf(input * 1000.0f + 0.5f)); + } + + assert(ent->client != nullptr); + + constexpr auto visMode = Game::visionSetMode_t::VISIONSET_NORMAL; + const auto* name = params->get(1); + + ent->client->visionDuration[visMode] = duration; + strncpy_s(ent->client->visionName[visMode], + sizeof(Game::gclient_t::visionName[0]) / sizeof(char), name, _TRUNCATE); + + Game::SV_GameSendServerCommand(ent->s.number, 1, + Utils::String::VA("%c \"%s\" %i", Game::MY_CMDS[visMode], name, duration)); + }); + + ClientCommand::Add("visionsetnight", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) + { + if (params->size() < 2) + { + Logger::Print("USAGE: visionSetNight \n"); + return; + } + + auto duration = 1000; + if (params->size() > 2) + { + const auto input = std::strtof(params->get(2), nullptr); + duration = static_cast(std::floorf(input * 1000.0f + 0.5f)); + } + + assert(ent->client != nullptr); + + constexpr auto visMode = Game::visionSetMode_t::VISIONSET_NIGHT; + const auto* name = params->get(1); + + ent->client->visionDuration[visMode] = duration; + strncpy_s(ent->client->visionName[visMode], + sizeof(Game::gclient_t::visionName[0]) / sizeof(char), name, _TRUNCATE); + + Game::SV_GameSendServerCommand(ent->s.number, 1, + Utils::String::VA("%c \"%s\" %i", Game::MY_CMDS[visMode], name, duration)); + }); + + ClientCommand::Add("g_testCmd", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) + { + assert(ent != nullptr); + ent->client->ps.stunTime = 1000 + Game::level->time; // 1000 is the default test stun time + }); + } + void ClientCommand::AddScriptFunctions() { - Script::AddFunction("Noclip", [](Game::scr_entref_t entref) // gsc: Noclip(); + Script::AddMethod("Noclip", [](Game::scr_entref_t entref) // gsc: Noclip(); { - if (entref >= Game::MAX_GENTITIES || Game::g_entities[entref].client == nullptr) - { - Game::Scr_Error(Utils::String::VA("^1NoClip: entity %u is not a client\n", entref)); - return; - } + const auto* ent = Game::GetPlayerEntity(entref); - if (Game::Scr_GetNumParam() == 1u && Game::Scr_GetType(0) == Game::VAR_INTEGER) + if (Game::Scr_GetNumParam() >= 1u) { if (Game::Scr_GetInt(0)) { - Game::g_entities[entref].client->flags |= Game::PLAYER_FLAG_NOCLIP; + ent->client->flags |= Game::PLAYER_FLAG_NOCLIP; } else { - Game::g_entities[entref].client->flags &= ~Game::PLAYER_FLAG_NOCLIP; + ent->client->flags &= ~Game::PLAYER_FLAG_NOCLIP; } } else { - Game::g_entities[entref].client->flags ^= Game::PLAYER_FLAG_NOCLIP; + ent->client->flags ^= Game::PLAYER_FLAG_NOCLIP; } }); - Script::AddFunction("Ufo", [](Game::scr_entref_t entref) // gsc: Ufo(); + Script::AddMethod("Ufo", [](Game::scr_entref_t entref) // gsc: Ufo(); { - if (entref >= Game::MAX_GENTITIES || Game::g_entities[entref].client == nullptr) - { - Game::Scr_Error(Utils::String::VA("^1Ufo: entity %u is not a client\n", entref)); - return; - } + const auto* ent = Game::GetPlayerEntity(entref); - if (Game::Scr_GetNumParam() == 1u && Game::Scr_GetType(0) == Game::VAR_INTEGER) + if (Game::Scr_GetNumParam() >= 1u) { if (Game::Scr_GetInt(0)) { - Game::g_entities[entref].client->flags |= Game::PLAYER_FLAG_UFO; + ent->client->flags |= Game::PLAYER_FLAG_UFO; } else { - Game::g_entities[entref].client->flags &= ~Game::PLAYER_FLAG_UFO; + ent->client->flags &= ~Game::PLAYER_FLAG_UFO; } } else { - Game::g_entities[entref].client->flags ^= Game::PLAYER_FLAG_UFO; + ent->client->flags ^= Game::PLAYER_FLAG_UFO; } }); - Script::AddFunction("God", [](Game::scr_entref_t entref) // gsc: God(); + Script::AddMethod("God", [](Game::scr_entref_t entref) // gsc: God(); { - if (entref >= Game::MAX_GENTITIES) - { - Game::Scr_Error(Utils::String::VA("^1God: entity %u is out of bounds\n", entref)); - return; - } + auto* ent = Game::GetEntity(entref); - if (Game::Scr_GetNumParam() == 1u && Game::Scr_GetType(0) == Game::VAR_INTEGER) + if (Game::Scr_GetNumParam() >= 1u) { if (Game::Scr_GetInt(0)) { - Game::g_entities[entref].flags |= Game::FL_GODMODE; + ent->flags |= Game::FL_GODMODE; } else { - Game::g_entities[entref].flags &= ~Game::FL_GODMODE; + ent->flags &= ~Game::FL_GODMODE; } } else { - Game::g_entities[entref].flags ^= Game::FL_GODMODE; + ent->flags ^= Game::FL_GODMODE; } }); - Script::AddFunction("Demigod", [](Game::scr_entref_t entref) // gsc: Demigod(); + Script::AddMethod("Demigod", [](Game::scr_entref_t entref) // gsc: Demigod(); { - if (entref >= Game::MAX_GENTITIES) - { - Game::Scr_Error(Utils::String::VA("^1Demigod: entity %u is out of bounds\n", entref)); - return; - } + auto* ent = Game::GetEntity(entref); - if (Game::Scr_GetNumParam() == 1u && Game::Scr_GetType(0) == Game::VAR_INTEGER) + if (Game::Scr_GetNumParam() >= 1u) { if (Game::Scr_GetInt(0)) { - Game::g_entities[entref].flags |= Game::FL_DEMI_GODMODE; + ent->flags |= Game::FL_DEMI_GODMODE; } else { - Game::g_entities[entref].flags &= ~Game::FL_DEMI_GODMODE; + ent->flags &= ~Game::FL_DEMI_GODMODE; } } else { - Game::g_entities[entref].flags ^= Game::FL_DEMI_GODMODE; + ent->flags ^= Game::FL_DEMI_GODMODE; } }); - Script::AddFunction("Notarget", [](Game::scr_entref_t entref) // gsc: Notarget(); + Script::AddMethod("Notarget", [](Game::scr_entref_t entref) // gsc: Notarget(); { - if (entref >= Game::MAX_GENTITIES) - { - Game::Scr_Error(Utils::String::VA("^1Notarget: entity %u is out of bounds\n", entref)); - return; - } + auto* ent = Game::GetEntity(entref); - if (Game::Scr_GetNumParam() == 1u && Game::Scr_GetType(0) == Game::VAR_INTEGER) + if (Game::Scr_GetNumParam() >= 1u) { if (Game::Scr_GetInt(0)) { - Game::g_entities[entref].flags |= Game::FL_NOTARGET; + ent->flags |= Game::FL_NOTARGET; } else { - Game::g_entities[entref].flags &= ~Game::FL_NOTARGET; + ent->flags &= ~Game::FL_NOTARGET; } } else { - Game::g_entities[entref].flags ^= Game::FL_NOTARGET; + ent->flags ^= Game::FL_NOTARGET; } }); + + Script::AddFunction("DropAllBots", []() // gsc: DropAllBots(); + { + Game::SV_DropAllBots(); + }); } ClientCommand::ClientCommand() @@ -308,5 +367,8 @@ namespace Components ClientCommand::AddCheatCommands(); ClientCommand::AddScriptFunctions(); +#ifdef _DEBUG + ClientCommand::AddDevelopmentCommands(); +#endif } } diff --git a/src/Components/Modules/ClientCommand.hpp b/src/Components/Modules/ClientCommand.hpp index ca9bc2a3..984b597f 100644 --- a/src/Components/Modules/ClientCommand.hpp +++ b/src/Components/Modules/ClientCommand.hpp @@ -5,19 +5,17 @@ namespace Components class ClientCommand : public Component { public: - typedef void(Callback)(Game::gentity_s* entity); - ClientCommand(); - static void Add(const char* name, Utils::Slot callback); + static void Add(const char* name, std::function callback); static bool CheatsOk(const Game::gentity_s* ent); private: - static std::unordered_map> FunctionMap; + static std::unordered_map> HandlersSV; - static bool CallbackHandler(Game::gentity_s* ent, const char* cmd); static void ClientCommandStub(const int clientNum); static void AddCheatCommands(); + static void AddDevelopmentCommands(); static void AddScriptFunctions(); }; } diff --git a/src/Components/Modules/Command.cpp b/src/Components/Modules/Command.cpp index cbe13615..4e5e99b8 100644 --- a/src/Components/Modules/Command.cpp +++ b/src/Components/Modules/Command.cpp @@ -2,8 +2,8 @@ namespace Components { - std::unordered_map> Command::FunctionMap; - std::unordered_map> Command::FunctionMapSV; + std::unordered_map> Command::FunctionMap; + std::unordered_map> Command::FunctionMapSV; std::string Command::Params::join(const int index) { @@ -60,7 +60,7 @@ namespace Components return Game::sv_cmd_args->argv[this->nesting_][index]; } - void Command::Add(const char* name, Utils::Slot callback) + void Command::Add(const char* name, std::function callback) { const auto command = Utils::String::ToLower(name); @@ -69,10 +69,10 @@ namespace Components Command::AddRaw(name, Command::MainCallback); } - Command::FunctionMap[command] = std::move(callback); + Command::FunctionMap.insert_or_assign(command, std::move(callback)); } - void Command::AddSV(const char* name, Utils::Slot callback) + void Command::AddSV(const char* name, std::function callback) { if (Loader::IsPregame()) { @@ -95,7 +95,7 @@ namespace Components Command::AddRaw(name, Game::Cbuf_AddServerText); } - FunctionMapSV[command] = std::move(callback); + FunctionMapSV.insert_or_assign(command, std::move(callback)); } void Command::AddRaw(const char* name, void(*callback)(), bool key) @@ -127,11 +127,11 @@ namespace Components Game::cmd_function_t* Command::Find(const std::string& command) { - Game::cmd_function_t* cmdFunction = *Game::cmd_functions; + auto* cmdFunction = *Game::cmd_functions; - while (cmdFunction) + while (cmdFunction != nullptr) { - if (cmdFunction->name && cmdFunction->name == command) + if (cmdFunction->name != nullptr && cmdFunction->name == command) { return cmdFunction; } @@ -149,12 +149,10 @@ namespace Components void Command::MainCallback() { - Command::ClientParams params; - + ClientParams params; const auto command = Utils::String::ToLower(params[0]); - const auto got = Command::FunctionMap.find(command); - if (got != Command::FunctionMap.end()) + if (const auto got = FunctionMap.find(command); got != FunctionMap.end()) { got->second(¶ms); } @@ -162,12 +160,10 @@ namespace Components void Command::MainCallbackSV() { - Command::ServerParams params; - + ServerParams params; const auto command = Utils::String::ToLower(params[0]); - const auto got = Command::FunctionMapSV.find(command); - if (got != Command::FunctionMapSV.end()) + if (const auto got = FunctionMapSV.find(command); got != FunctionMapSV.end()) { got->second(¶ms); } diff --git a/src/Components/Modules/Command.hpp b/src/Components/Modules/Command.hpp index d6379a94..ce949cc2 100644 --- a/src/Components/Modules/Command.hpp +++ b/src/Components/Modules/Command.hpp @@ -8,7 +8,8 @@ namespace Components class Params { public: - Params() {}; + Params() = default; + virtual ~Params() = default; virtual int size() = 0; virtual const char* get(int index) = 0; @@ -20,7 +21,7 @@ namespace Components } }; - class ClientParams : public Params + class ClientParams final : public Params { public: ClientParams(); @@ -32,7 +33,7 @@ namespace Components int nesting_; }; - class ServerParams : public Params + class ServerParams final : public Params { public: ServerParams(); @@ -44,14 +45,12 @@ namespace Components int nesting_; }; - typedef void(Callback)(Command::Params* params); - Command(); static Game::cmd_function_t* Allocate(); - static void Add(const char* name, Utils::Slot callback); - static void AddSV(const char* name, Utils::Slot callback); + static void Add(const char* name, std::function callback); + static void AddSV(const char* name, std::function callback); static void AddRaw(const char* name, void(*callback)(), bool key = false); static void AddRawSV(const char* name, void(*callback)()); static void Execute(std::string command, bool sync = true); @@ -59,8 +58,8 @@ namespace Components static Game::cmd_function_t* Find(const std::string& command); private: - static std::unordered_map> FunctionMap; - static std::unordered_map> FunctionMapSV; + static std::unordered_map> FunctionMap; + static std::unordered_map> FunctionMapSV; static void MainCallback(); static void MainCallbackSV(); diff --git a/src/Components/Modules/Console.cpp b/src/Components/Modules/Console.cpp index c50f723c..973679ca 100644 --- a/src/Components/Modules/Console.cpp +++ b/src/Components/Modules/Console.cpp @@ -51,8 +51,8 @@ namespace Components { SetConsoleTitleA(hostname.data()); - int clientCount = 0; - int maxclientCount = *Game::svs_numclients; + auto clientCount = 0; + auto maxclientCount = *Game::svs_clientCount; if (maxclientCount) { diff --git a/src/Components/Modules/Dedicated.cpp b/src/Components/Modules/Dedicated.cpp index b56e9ccb..6043607a 100644 --- a/src/Components/Modules/Dedicated.cpp +++ b/src/Components/Modules/Dedicated.cpp @@ -374,7 +374,6 @@ namespace Components } }); -#ifdef USE_LEGACY_SERVER_LIST // Heartbeats Scheduler::Once(Dedicated::Heartbeat); Scheduler::OnFrame([]() @@ -387,7 +386,6 @@ namespace Components Dedicated::Heartbeat(); } }); -#endif Dvar::OnInit([]() { diff --git a/src/Components/Modules/Discovery.cpp b/src/Components/Modules/Discovery.cpp index 93dbf96f..c71ca293 100644 --- a/src/Components/Modules/Discovery.cpp +++ b/src/Components/Modules/Discovery.cpp @@ -83,16 +83,6 @@ namespace Components ServerList::InsertRequest(address); } }); - - // This is placed here in case the anticheat has been disabled! - // Make sure this is called after the memory scan! -#ifndef DISABLE_ANTICHEAT - Utils::Hook(0x5ACB9E, []() // Somewhere in the renderer, past the scan check - { - AntiCheat::ScanIntegrityCheck(); - return Utils::Hook::Call(0x4AA720)(); - }, HOOK_CALL).install()->quick(); -#endif } void Discovery::preDestroy() diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index 02b1de2a..c9d875da 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -381,7 +381,7 @@ namespace Components { Network::Address address(nc->sa.sa); - for (int i = 0; i < *Game::svs_numclients; ++i) + for (int i = 0; i < *Game::svs_clientCount; ++i) { Game::client_t* client = &Game::svs_clients[i]; @@ -964,13 +964,20 @@ namespace Components Download::ScriptDownloads.clear(); }); - Script::AddFunction("httpGet", [](Game::scr_entref_t) + Script::AddFunction("HttpGet", []() { - if (!Dedicated::IsEnabled() && !Flags::HasFlag("scriptablehttp")) return; - if (Game::Scr_GetNumParam() < 1u) return; + if (!Flags::HasFlag("scriptablehttp")) + return; - std::string url = Game::Scr_GetString(0); - unsigned int object = Game::AllocObject(); + const auto* url = Game::Scr_GetString(0); + + if (url == nullptr) + { + Game::Scr_ParamError(0, "^1HttpGet: Illegal parameter!\n"); + return; + } + + auto object = Game::AllocObject(); Game::Scr_AddObject(object); @@ -978,13 +985,13 @@ namespace Components Game::RemoveRefToObject(object); }); - Script::AddFunction("httpCancel", [](Game::scr_entref_t) + Script::AddFunction("HttpCancel", []() { - if (!Dedicated::IsEnabled() && !Flags::HasFlag("scriptablehttp")) return; - if (Game::Scr_GetNumParam() < 1u) return; + if (!Flags::HasFlag("scriptablehttp")) + return; - unsigned int object = Game::Scr_GetObject(0); - for (auto& download : Download::ScriptDownloads) + const auto object = Game::Scr_GetObject(0); + for (const auto& download : Download::ScriptDownloads) { if (object == download->getObject()) { diff --git a/src/Components/Modules/Exception.cpp b/src/Components/Modules/Exception.cpp index 3700587b..572bfdfd 100644 --- a/src/Components/Modules/Exception.cpp +++ b/src/Components/Modules/Exception.cpp @@ -137,10 +137,6 @@ namespace Components Utils::IO::CreateDir("minidumps"); PathCombineA(filename, "minidumps\\", Utils::String::VA("%s-" VERSION "-%s.dmp", exeFileName, filenameFriendlyTime)); -#ifndef DISABLE_ANTICHEAT - AntiCheat::UninstallLibHook(); -#endif - DWORD fileShare = FILE_SHARE_READ | FILE_SHARE_WRITE; HANDLE hFile = CreateFileA(filename, GENERIC_WRITE | GENERIC_READ, fileShare, nullptr, (fileShare & FILE_SHARE_WRITE) > 0 ? OPEN_ALWAYS : OPEN_EXISTING, NULL, nullptr); MINIDUMP_EXCEPTION_INFORMATION ex = { GetCurrentThreadId(), ExceptionInfo, FALSE }; @@ -157,10 +153,6 @@ namespace Components TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode); } -#ifndef DISABLE_ANTICHEAT - AntiCheat::InstallLibHook(); -#endif - return EXCEPTION_CONTINUE_SEARCH; } diff --git a/src/Components/Modules/FileSystem.cpp b/src/Components/Modules/FileSystem.cpp index fface938..dddf4c42 100644 --- a/src/Components/Modules/FileSystem.cpp +++ b/src/Components/Modules/FileSystem.cpp @@ -28,7 +28,7 @@ namespace Components if (!rawfile || Game::DB_IsXAssetDefault(Game::XAssetType::ASSET_TYPE_RAWFILE, this->filePath.data())) return; this->buffer.resize(Game::DB_GetRawFileLen(rawfile)); - Game::DB_GetRawBuffer(rawfile, const_cast(this->buffer.data()), this->buffer.size()); + Game::DB_GetRawBuffer(rawfile, this->buffer.data(), static_cast(this->buffer.size())); } FileSystem::FileReader::FileReader(const std::string& file) : handle(0), name(file) diff --git a/src/Components/Modules/Friends.cpp b/src/Components/Modules/Friends.cpp index b77a983a..ea15a68b 100644 --- a/src/Components/Modules/Friends.cpp +++ b/src/Components/Modules/Friends.cpp @@ -11,6 +11,10 @@ namespace Components std::recursive_mutex Friends::Mutex; std::vector Friends::FriendsList; + Dvar::Var Friends::UIStreamFriendly; + Dvar::Var Friends::CLAnonymous; + Dvar::Var Friends::CLNotifyFriendState; + void Friends::SortIndividualList(std::vector* list) { std::stable_sort(list->begin(), list->end(), [](Friends::Friend const& friend1, Friends::Friend const& friend2) @@ -111,8 +115,8 @@ namespace Components Friends::SortList(); - const auto notify = Dvar::Var("cl_notifyFriendState").get(); - if (gotOnline && (!notify || (notify && !Game::CL_IsCgameInitialized())) && !Dvar::Var("ui_streamFriendly").get()) + const auto notify = Friends::CLNotifyFriendState.get(); + if (gotOnline && (!notify || (notify && !Game::CL_IsCgameInitialized())) && !Friends::UIStreamFriendly.get()) { Game::Material* material = Friends::CreateAvatar(user); Toast::Show(material, entry->name, "is playing IW4x", 3000, [material]() @@ -124,7 +128,7 @@ namespace Components void Friends::UpdateState(bool force) { - if (Dvar::Var("cl_anonymous").get() || Friends::IsInvisible() || !Steam::Enabled()) return; + if (Friends::CLAnonymous.get() || Friends::IsInvisible() || !Steam::Enabled()) return; if (force) { @@ -228,7 +232,7 @@ namespace Components void Friends::SetPresence(const std::string& key, const std::string& value) { - if (Steam::Proxy::ClientFriends && Steam::Proxy::SteamUtils && !Dvar::Var("cl_anonymous").get() && !Friends::IsInvisible() && Steam::Enabled()) + if (Steam::Proxy::ClientFriends && Steam::Proxy::SteamUtils && !Friends::CLAnonymous.get() && !Friends::IsInvisible() && Steam::Enabled()) { Friends::SetRawPresence(key.data(), value.data()); } @@ -576,10 +580,15 @@ namespace Components { Friends::LoggedOn = false; - if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled() || Monitor::IsEnabled()) return; + if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled() || Monitor::IsEnabled()) + return; - Dvar::Register("cl_anonymous", false, Game::DVAR_ARCHIVE, "Enable invisible mode for Steam"); - Dvar::Register("cl_notifyFriendState", true, Game::DVAR_ARCHIVE, "Update friends about current game status"); + Dvar::OnInit([] + { + Friends::UIStreamFriendly = Dvar::Register("ui_streamFriendly", false, Game::DVAR_ARCHIVE, "Stream friendly UI"); + Friends::CLAnonymous = Dvar::Register("cl_anonymous", false, Game::DVAR_ARCHIVE, "Enable invisible mode for Steam"); + Friends::CLNotifyFriendState = Dvar::Register("cl_notifyFriendState", true, Game::DVAR_ARCHIVE, "Update friends about current game status"); + }); Command::Add("addFriend", [](Command::Params* params) { @@ -712,11 +721,11 @@ namespace Components Friends::InitialState = Steam::Proxy::SteamFriends->GetFriendPersonaState(Steam::Proxy::SteamUser_->GetSteamID()); } - if (Dvar::Var("cl_anonymous").get() || Friends::IsInvisible() || !Steam::Enabled()) + if (Friends::CLAnonymous.get() || Friends::IsInvisible() || !Steam::Enabled()) { if (Steam::Proxy::ClientFriends) { - for (auto id : Friends::GetAppIdList()) + for (const auto id : Friends::GetAppIdList()) { Steam::Proxy::ClientFriends.invoke("ClearRichPresence", id); } diff --git a/src/Components/Modules/Friends.hpp b/src/Components/Modules/Friends.hpp index db1ec171..c4d7b602 100644 --- a/src/Components/Modules/Friends.hpp +++ b/src/Components/Modules/Friends.hpp @@ -25,6 +25,10 @@ namespace Components static bool IsInvisible(); + static Dvar::Var UIStreamFriendly; + static Dvar::Var CLAnonymous; + static Dvar::Var CLNotifyFriendState; + private: #pragma pack(push, 4) struct FriendRichPresenceUpdate diff --git a/src/Components/Modules/Gamepad.cpp b/src/Components/Modules/Gamepad.cpp index f6fc1aef..ca170379 100644 --- a/src/Components/Modules/Gamepad.cpp +++ b/src/Components/Modules/Gamepad.cpp @@ -2,1964 +2,1962 @@ namespace Components { - Game::ButtonToCodeMap_t Gamepad::buttonList[] - { - {Game::GPAD_X, Game::K_BUTTON_X}, - {Game::GPAD_A, Game::K_BUTTON_A}, - {Game::GPAD_B, Game::K_BUTTON_B}, - {Game::GPAD_Y, Game::K_BUTTON_Y}, - {Game::GPAD_L_TRIG, Game::K_BUTTON_LTRIG}, - {Game::GPAD_R_TRIG, Game::K_BUTTON_RTRIG}, - {Game::GPAD_L_SHLDR, Game::K_BUTTON_LSHLDR}, - {Game::GPAD_R_SHLDR, Game::K_BUTTON_RSHLDR}, - {Game::GPAD_START, Game::K_BUTTON_START}, - {Game::GPAD_BACK, Game::K_BUTTON_BACK}, - {Game::GPAD_L3, Game::K_BUTTON_LSTICK}, - {Game::GPAD_R3, Game::K_BUTTON_RSTICK}, - {Game::GPAD_UP, Game::K_DPAD_UP}, - {Game::GPAD_DOWN, Game::K_DPAD_DOWN}, - {Game::GPAD_LEFT, Game::K_DPAD_LEFT}, - {Game::GPAD_RIGHT, Game::K_DPAD_RIGHT} - }; - - Game::StickToCodeMap_t Gamepad::analogStickList[4] - { - {Game::GPAD_LX, Game::K_APAD_RIGHT, Game::K_APAD_LEFT}, - {Game::GPAD_LY, Game::K_APAD_UP, Game::K_APAD_DOWN}, - {Game::GPAD_RX, Game::K_APAD_RIGHT, Game::K_APAD_LEFT}, - {Game::GPAD_RY, Game::K_APAD_UP, Game::K_APAD_DOWN}, - }; - - Game::GamePadStick Gamepad::stickForAxis[Game::GPAD_PHYSAXIS_COUNT] - { - Game::GPAD_RX, - Game::GPAD_RY, - Game::GPAD_LX, - Game::GPAD_LY, - Game::GPAD_INVALID, - Game::GPAD_INVALID - }; - - Game::GamepadPhysicalAxis Gamepad::axisSameStick[Game::GPAD_PHYSAXIS_COUNT] - { - Game::GPAD_PHYSAXIS_RSTICK_Y, - Game::GPAD_PHYSAXIS_RSTICK_X, - Game::GPAD_PHYSAXIS_LSTICK_Y, - Game::GPAD_PHYSAXIS_LSTICK_X, - Game::GPAD_PHYSAXIS_NONE, - Game::GPAD_PHYSAXIS_NONE - }; - - const char* Gamepad::physicalAxisNames[Game::GPAD_PHYSAXIS_COUNT] - { - "A_RSTICK_X", - "A_RSTICK_Y", - "A_LSTICK_X", - "A_LSTICK_Y", - "A_RTRIGGER", - "A_LTRIGGER" - }; - - const char* Gamepad::virtualAxisNames[Game::GPAD_VIRTAXIS_COUNT] - { - "VA_SIDE", - "VA_FORWARD", - "VA_UP", - "VA_YAW", - "VA_PITCH", - "VA_ATTACK" - }; - - const char* Gamepad::gamePadMappingTypeNames[Game::GPAD_MAP_COUNT] - { - "MAP_LINEAR", - "MAP_SQUARED" - }; - - Game::keyNum_t Gamepad::menuScrollButtonList[] - { - Game::K_APAD_UP, - Game::K_APAD_DOWN, - Game::K_APAD_LEFT, - Game::K_APAD_RIGHT, - Game::K_DPAD_UP, - Game::K_DPAD_DOWN, - Game::K_DPAD_LEFT, - Game::K_DPAD_RIGHT - }; - - Game::keyname_t Gamepad::extendedKeyNames[] - { - {"BUTTON_A", Game::K_BUTTON_A}, - {"BUTTON_B", Game::K_BUTTON_B}, - {"BUTTON_X", Game::K_BUTTON_X}, - {"BUTTON_Y", Game::K_BUTTON_Y}, - {"BUTTON_LSHLDR", Game::K_BUTTON_LSHLDR}, - {"BUTTON_RSHLDR", Game::K_BUTTON_RSHLDR}, - {"BUTTON_START", Game::K_BUTTON_START}, - {"BUTTON_BACK", Game::K_BUTTON_BACK}, - {"BUTTON_LSTICK", Game::K_BUTTON_LSTICK}, - {"BUTTON_RSTICK", Game::K_BUTTON_RSTICK}, - {"BUTTON_LTRIG", Game::K_BUTTON_LTRIG}, - {"BUTTON_RTRIG", Game::K_BUTTON_RTRIG}, - {"DPAD_UP", Game::K_DPAD_UP}, - {"DPAD_DOWN", Game::K_DPAD_DOWN}, - {"DPAD_LEFT", Game::K_DPAD_LEFT}, - {"DPAD_RIGHT", Game::K_DPAD_RIGHT}, - }; - - Game::keyname_t Gamepad::extendedLocalizedKeyNamesXenon[] - { - // Material text icons pattern: 0x01 width height material_name_len - {"^\x01\x32\x32\x08""button_a", Game::K_BUTTON_A}, - {"^\x01\x32\x32\x08""button_b", Game::K_BUTTON_B}, - {"^\x01\x32\x32\x08""button_x", Game::K_BUTTON_X}, - {"^\x01\x32\x32\x08""button_y", Game::K_BUTTON_Y}, - {"^\x01\x32\x32\x0D""button_lshldr", Game::K_BUTTON_LSHLDR}, - {"^\x01\x32\x32\x0D""button_rshldr", Game::K_BUTTON_RSHLDR}, - {"^\x01\x32\x32\x0C""button_start", Game::K_BUTTON_START}, - {"^\x01\x32\x32\x0B""button_back", Game::K_BUTTON_BACK}, - {"^\x01\x48\x32\x0D""button_lstick", Game::K_BUTTON_LSTICK}, - {"^\x01\x48\x32\x0D""button_rstick", Game::K_BUTTON_RSTICK}, - {"^\x01\x32\x32\x0C""button_ltrig", Game::K_BUTTON_LTRIG}, - {"^\x01\x32\x32\x0C""button_rtrig", Game::K_BUTTON_RTRIG}, - {"^\x01\x32\x32\x07""dpad_up", Game::K_DPAD_UP}, - {"^\x01\x32\x32\x09""dpad_down", Game::K_DPAD_DOWN}, - {"^\x01\x32\x32\x09""dpad_left", Game::K_DPAD_LEFT}, - {"^\x01\x32\x32\x0A""dpad_right", Game::K_DPAD_RIGHT}, - }; - - Game::keyname_t Gamepad::extendedLocalizedKeyNamesPs3[] - { - // Material text icons pattern: 0x01 width height material_name_len - {"^\x01\x32\x32\x10""button_ps3_cross", Game::K_BUTTON_A}, - {"^\x01\x32\x32\x11""button_ps3_circle", Game::K_BUTTON_B}, - {"^\x01\x32\x32\x11""button_ps3_square", Game::K_BUTTON_X}, - {"^\x01\x32\x32\x13""button_ps3_triangle", Game::K_BUTTON_Y}, - {"^\x01\x32\x32\x0D""button_ps3_l1", Game::K_BUTTON_LSHLDR}, - {"^\x01\x32\x32\x0D""button_ps3_r1", Game::K_BUTTON_RSHLDR}, - {"^\x01\x32\x32\x10""button_ps3_start", Game::K_BUTTON_START}, - {"^\x01\x32\x32\x0F""button_ps3_back", Game::K_BUTTON_BACK}, - {"^\x01\x48\x32\x0D""button_ps3_l3", Game::K_BUTTON_LSTICK}, - {"^\x01\x48\x32\x0D""button_ps3_r3", Game::K_BUTTON_RSTICK}, - {"^\x01\x32\x32\x0D""button_ps3_l2", Game::K_BUTTON_LTRIG}, - {"^\x01\x32\x32\x0D""button_ps3_r2", Game::K_BUTTON_RTRIG}, - {"^\x01\x32\x32\x0B""dpad_ps3_up", Game::K_DPAD_UP}, - {"^\x01\x32\x32\x0D""dpad_ps3_down", Game::K_DPAD_DOWN}, - {"^\x01\x32\x32\x0D""dpad_ps3_left", Game::K_DPAD_LEFT}, - {"^\x01\x32\x32\x0E""dpad_ps3_right", Game::K_DPAD_RIGHT}, - }; - Game::keyname_t Gamepad::combinedKeyNames[Game::KEY_NAME_COUNT + std::extent_v + 1]; - Game::keyname_t Gamepad::combinedLocalizedKeyNamesXenon[Game::KEY_NAME_COUNT + std::extent_v + 1]; - Game::keyname_t Gamepad::combinedLocalizedKeyNamesPs3[Game::KEY_NAME_COUNT + std::extent_v + 1]; - - Gamepad::ControllerMenuKeyMapping Gamepad::controllerMenuKeyMappings[] - { - {Game::K_BUTTON_A, Game::K_ENTER}, - {Game::K_BUTTON_START, Game::K_ENTER}, - {Game::K_BUTTON_B, Game::K_ESCAPE}, - {Game::K_BUTTON_BACK, Game::K_ESCAPE}, - {Game::K_DPAD_UP, Game::K_UPARROW}, - {Game::K_APAD_UP, Game::K_UPARROW}, - {Game::K_DPAD_DOWN, Game::K_DOWNARROW}, - {Game::K_APAD_DOWN, Game::K_DOWNARROW}, - {Game::K_DPAD_LEFT, Game::K_LEFTARROW}, - {Game::K_APAD_LEFT, Game::K_LEFTARROW}, - {Game::K_DPAD_RIGHT, Game::K_RIGHTARROW}, - {Game::K_APAD_RIGHT, Game::K_RIGHTARROW}, - }; - - Gamepad::GamePad Gamepad::gamePads[Game::MAX_GAMEPADS]{}; - Gamepad::GamePadGlobals Gamepad::gamePadGlobals[Game::MAX_GAMEPADS]{{}}; - int Gamepad::gamePadBindingsModifiedFlags = 0; - - Dvar::Var Gamepad::gpad_enabled; - Dvar::Var Gamepad::gpad_debug; - Dvar::Var Gamepad::gpad_present; - Dvar::Var Gamepad::gpad_in_use; - Dvar::Var Gamepad::gpad_style; - Dvar::Var Gamepad::gpad_sticksConfig; - Dvar::Var Gamepad::gpad_buttonConfig; - Dvar::Var Gamepad::gpad_menu_scroll_delay_first; - Dvar::Var Gamepad::gpad_menu_scroll_delay_rest; - Dvar::Var Gamepad::gpad_rumble; - Dvar::Var Gamepad::gpad_stick_pressed_hysteresis; - Dvar::Var Gamepad::gpad_stick_pressed; - Dvar::Var Gamepad::gpad_stick_deadzone_max; - Dvar::Var Gamepad::gpad_stick_deadzone_min; - Dvar::Var Gamepad::gpad_button_deadzone; - Dvar::Var Gamepad::gpad_button_rstick_deflect_max; - Dvar::Var Gamepad::gpad_button_lstick_deflect_max; - Dvar::Var Gamepad::gpad_use_hold_time; - Dvar::Var Gamepad::gpad_lockon_enabled; - Dvar::Var Gamepad::gpad_slowdown_enabled; - Dvar::Var Gamepad::input_viewSensitivity; - Dvar::Var Gamepad::input_invertPitch; - Dvar::Var Gamepad::sv_allowAimAssist; - Dvar::Var Gamepad::aim_turnrate_pitch; - Dvar::Var Gamepad::aim_turnrate_pitch_ads; - Dvar::Var Gamepad::aim_turnrate_yaw; - Dvar::Var Gamepad::aim_turnrate_yaw_ads; - Dvar::Var Gamepad::aim_accel_turnrate_enabled; - Dvar::Var Gamepad::aim_accel_turnrate_lerp; - Dvar::Var Gamepad::aim_input_graph_enabled; - Dvar::Var Gamepad::aim_input_graph_index; - Dvar::Var Gamepad::aim_scale_view_axis; - Dvar::Var Gamepad::cl_bypassMouseInput; - Dvar::Var Gamepad::cg_mapLocationSelectionCursorSpeed; - Dvar::Var Gamepad::aim_aimAssistRangeScale; - Dvar::Var Gamepad::aim_slowdown_enabled; - Dvar::Var Gamepad::aim_slowdown_debug; - Dvar::Var Gamepad::aim_slowdown_pitch_scale; - Dvar::Var Gamepad::aim_slowdown_pitch_scale_ads; - Dvar::Var Gamepad::aim_slowdown_yaw_scale; - Dvar::Var Gamepad::aim_slowdown_yaw_scale_ads; - Dvar::Var Gamepad::aim_lockon_enabled; - Dvar::Var Gamepad::aim_lockon_deflection; - Dvar::Var Gamepad::aim_lockon_pitch_strength; - Dvar::Var Gamepad::aim_lockon_strength; - - Gamepad::GamePadGlobals::GamePadGlobals() - : axes{}, - nextScrollTime(0) - { - for (auto& virtualAxis : axes.virtualAxes) - { - virtualAxis.physicalAxis = Game::GPAD_PHYSAXIS_NONE; - virtualAxis.mapType = Game::GPAD_MAP_NONE; - } - } - - __declspec(naked) void Gamepad::MSG_WriteDeltaUsercmdKeyStub() - { - __asm - { - // fix stack pointer - add esp, 0Ch - - // put both forward move and rightmove values in the movement button - mov dl, byte ptr[edi + 1Ah] // to_forwardMove - mov dh, byte ptr[edi + 1Bh] // to_rightMove - - mov[esp + 30h], dx // to_buttons - - mov dl, byte ptr[ebp + 1Ah] // from_forwardMove - mov dh, byte ptr[ebp + 1Bh] // from_rightMove - - mov[esp + 2Ch], dx // from_buttons - - // return back - push 0x60E40E - retn - } - } - - void Gamepad::ApplyMovement(Game::msg_t* msg, int key, Game::usercmd_s* from, Game::usercmd_s* to) - { - char forward; - char right; - - if (Game::MSG_ReadBit(msg)) - { - short movementBits = static_cast(key ^ Game::MSG_ReadBits(msg, 16)); - - forward = static_cast(movementBits); - right = static_cast(movementBits >> 8); - } - else - { - forward = from->forwardmove; - right = from->rightmove; - } - - to->forwardmove = forward; - to->rightmove = right; - } - - __declspec(naked) void Gamepad::MSG_ReadDeltaUsercmdKeyStub() - { - __asm - { - push ebx // to - push ebp // from - push edi // key - push esi // msg - call ApplyMovement - add esp, 10h - - // return back - push 0x4921BF - ret - } - } - - __declspec(naked) void Gamepad::MSG_ReadDeltaUsercmdKeyStub2() - { - __asm - { - push ebx // to - push ebp // from - push edi // key - push esi // msg - call ApplyMovement - add esp, 10h - - // return back - push 3 - push esi - push 0x492085 - ret - } - } - - bool Gamepad::GPad_Check(const int gamePadIndex, const int portIndex) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePad = gamePads[gamePadIndex]; - - if (XInputGetCapabilities(portIndex, XINPUT_FLAG_GAMEPAD, &gamePad.caps) == ERROR_SUCCESS) - { - gamePad.enabled = true; - gamePad.portIndex = portIndex; - return true; - } - - gamePad.enabled = false; - return false; - } - - void Gamepad::GPad_RefreshAll() - { - auto currentGamePadNum = 0; - - for (auto currentPort = 0; currentPort < XUSER_MAX_COUNT && currentGamePadNum < Game::MAX_GAMEPADS; currentPort++) - { - if (GPad_Check(currentGamePadNum, currentPort)) - currentGamePadNum++; - } - } - - float Gamepad::LinearTrack(const float target, const float current, const float rate, const float deltaTime) - { - const auto err = target - current; - float step; - if (err <= 0.0f) - step = -rate * deltaTime; - else - step = rate * deltaTime; - - if (std::fabs(err) <= 0.001f) - return target; - - if (std::fabs(step) <= std::fabs(err)) - return current + step; - - return target; - } - - bool Gamepad::AimAssist_DoBoundsIntersectCenterBox(const float* clipMins, const float* clipMaxs, const float clipHalfWidth, const float clipHalfHeight) - { - return clipHalfWidth >= clipMins[0] && clipMaxs[0] >= -clipHalfWidth - && clipHalfHeight >= clipMins[1] && clipMaxs[1] >= -clipHalfHeight; - } - - bool Gamepad::AimAssist_IsPlayerUsingOffhand(Game::AimAssistPlayerState* ps) - { - // Check offhand flag - if ((ps->weapFlags & 2) == 0) - return false; - - // If offhand weapon has no id we are not using one - if (!ps->weapIndex) - return false; - - const auto* weaponDef = Game::BG_GetWeaponDef(ps->weapIndex); - - return weaponDef->offhandClass != Game::OFFHAND_CLASS_NONE; - } - - const Game::AimScreenTarget* Gamepad::AimAssist_GetBestTarget(const Game::AimAssistGlobals* aaGlob, const float range, const float regionWidth, const float regionHeight) - { - const auto rangeSqr = range * range; - for (auto targetIndex = 0; targetIndex < aaGlob->screenTargetCount; targetIndex++) - { - const auto* currentTarget = &aaGlob->screenTargets[targetIndex]; - if (currentTarget->distSqr <= rangeSqr && AimAssist_DoBoundsIntersectCenterBox(currentTarget->clipMins, currentTarget->clipMaxs, regionWidth, regionHeight)) - { - return currentTarget; - } - } - - return nullptr; - } - - const Game::AimScreenTarget* Gamepad::AimAssist_GetTargetFromEntity(const Game::AimAssistGlobals* aaGlob, const int entIndex) - { - if (entIndex == Game::AIM_TARGET_INVALID) - return nullptr; - - for (auto targetIndex = 0; targetIndex < aaGlob->screenTargetCount; targetIndex++) - { - const auto* currentTarget = &aaGlob->screenTargets[targetIndex]; - if (currentTarget->entIndex == entIndex) - return currentTarget; - } - - return nullptr; - } - - const Game::AimScreenTarget* Gamepad::AimAssist_GetPrevOrBestTarget(const Game::AimAssistGlobals* aaGlob, const float range, const float regionWidth, const float regionHeight, - const int prevTargetEnt) - { - const auto screenTarget = AimAssist_GetTargetFromEntity(aaGlob, prevTargetEnt); - - if (screenTarget && (range * range) > screenTarget->distSqr && AimAssist_DoBoundsIntersectCenterBox(screenTarget->clipMins, screenTarget->clipMaxs, regionWidth, regionHeight)) - return screenTarget; - - return AimAssist_GetBestTarget(aaGlob, range, regionWidth, regionHeight); - } - - bool Gamepad::AimAssist_IsLockonActive(const int gamePadIndex) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& aaGlob = Game::aaGlobArray[gamePadIndex]; - - if (!aim_lockon_enabled.get() || !gpad_lockon_enabled.get()) - return false; - - if (AimAssist_IsPlayerUsingOffhand(&aaGlob.ps)) - return false; - - if (aaGlob.autoAimActive || aaGlob.autoMeleeState == Game::AIM_MELEE_STATE_UPDATING) - return false; - - return true; - } - - void Gamepad::AimAssist_ApplyLockOn(const Game::AimInput* input, Game::AimOutput* output) - { - assert(input); - assert(input->localClientNum < Game::MAX_GAMEPADS); - auto& aaGlob = Game::aaGlobArray[input->localClientNum]; - - const auto prevTargetEnt = aaGlob.lockOnTargetEnt; - aaGlob.lockOnTargetEnt = Game::AIM_TARGET_INVALID; - - if (!AimAssist_IsLockonActive(input->localClientNum)) - return; - - const auto* weaponDef = Game::BG_GetWeaponDef(aaGlob.ps.weapIndex); - if (weaponDef->requireLockonToFire) - return; - - const auto deflection = aim_lockon_deflection.get(); - if (deflection > std::fabs(input->pitchAxis) && deflection > std::fabs(input->yawAxis) && deflection > std::fabs(input->rightAxis)) - return; - - if (!aaGlob.ps.weapIndex) - return; - - const auto aimAssistRange = AimAssist_Lerp(weaponDef->aimAssistRange, weaponDef->aimAssistRangeAds, aaGlob.adsLerp) * aim_aimAssistRangeScale.get(); - const auto screenTarget = AimAssist_GetPrevOrBestTarget(&aaGlob, aimAssistRange, aaGlob.tweakables.lockOnRegionWidth, aaGlob.tweakables.lockOnRegionHeight, prevTargetEnt); - - if (screenTarget && screenTarget->distSqr > 0.0f) - { - aaGlob.lockOnTargetEnt = screenTarget->entIndex; - const auto arcLength = std::sqrt(screenTarget->distSqr) * static_cast(M_PI); - - const auto pitchTurnRate = - (screenTarget->velocity[0] * aaGlob.viewAxis[2][0] + screenTarget->velocity[1] * aaGlob.viewAxis[2][1] + screenTarget->velocity[2] * aaGlob.viewAxis[2][2] - - (aaGlob.ps.velocity[0] * aaGlob.viewAxis[2][0] + aaGlob.ps.velocity[1] * aaGlob.viewAxis[2][1] + aaGlob.ps.velocity[2] * aaGlob.viewAxis[2][2])) - / arcLength * 180.0f * aim_lockon_pitch_strength.get(); - - const auto yawTurnRate = - (screenTarget->velocity[0] * aaGlob.viewAxis[1][0] + screenTarget->velocity[1] * aaGlob.viewAxis[1][1] + screenTarget->velocity[2] * aaGlob.viewAxis[1][2] - - (aaGlob.ps.velocity[0] * aaGlob.viewAxis[1][0] + aaGlob.ps.velocity[1] * aaGlob.viewAxis[1][1] + aaGlob.ps.velocity[2] * aaGlob.viewAxis[1][2])) - / arcLength * 180.0f * aim_lockon_strength.get(); - - output->pitch -= pitchTurnRate * input->deltaTime; - output->yaw += yawTurnRate * input->deltaTime; - } - } - - void Gamepad::AimAssist_CalcAdjustedAxis(const Game::AimInput* input, float* pitchAxis, float* yawAxis) - { - assert(input); - assert(pitchAxis); - assert(yawAxis); - - const auto graphIndex = aim_input_graph_index.get(); - if (aim_input_graph_enabled.get() && graphIndex >= 0 && static_cast(graphIndex) < Game::AIM_ASSIST_GRAPH_COUNT) - { - const auto deflection = std::sqrt(input->pitchAxis * input->pitchAxis + input->yawAxis * input->yawAxis); - - float fraction; - if (deflection - 1.0f < 0.0f) - fraction = deflection; - else - fraction = 1.0f; - - if (0.0f - deflection >= 0.0f) - fraction = 0.0f; - - const auto graphScale = Game::GraphFloat_GetValue(&Game::aaInputGraph[graphIndex], fraction); - *pitchAxis = input->pitchAxis * graphScale; - *yawAxis = input->yawAxis * graphScale; - } - else - { - *pitchAxis = input->pitchAxis; - *yawAxis = input->yawAxis; - } - - if (aim_scale_view_axis.get()) - { - const auto absPitchAxis = std::fabs(*pitchAxis); - const auto absYawAxis = std::fabs(*yawAxis); - - if (absPitchAxis <= absYawAxis) - *pitchAxis = (1.0f - (absYawAxis - absPitchAxis)) * *pitchAxis; - else - *yawAxis = (1.0f - (absPitchAxis - absYawAxis)) * *yawAxis; - } - } - - bool Gamepad::AimAssist_IsSlowdownActive(const Game::AimAssistPlayerState* ps) - { - if (!aim_slowdown_enabled.get() || !gpad_slowdown_enabled.get()) - return false; - - if (!ps->weapIndex) - return false; - - const auto* weaponDef = Game::BG_GetWeaponDef(ps->weapIndex); - if (weaponDef->requireLockonToFire) - return false; - - if (ps->linkFlags & Game::PLF_WEAPONVIEW_ONLY) - return false; - - if (ps->weaponState >= Game::WEAPON_STUNNED_START && ps->weaponState <= Game::WEAPON_STUNNED_END) - return false; - - if (ps->eFlags & (Game::EF_VEHICLE_ACTIVE | Game::EF_TURRET_ACTIVE_DUCK | Game::EF_TURRET_ACTIVE_PRONE)) - return false; - - if (!ps->hasAmmo) - return false; - - return true; - } - - void Gamepad::AimAssist_CalcSlowdown(const Game::AimInput* input, float* pitchScale, float* yawScale) - { - assert(input); - assert(input->localClientNum < Game::MAX_GAMEPADS); - auto& aaGlob = Game::aaGlobArray[input->localClientNum]; - assert(pitchScale); - assert(yawScale); - - *pitchScale = 1.0f; - *yawScale = 1.0f; - - if (!AimAssist_IsSlowdownActive(&aaGlob.ps)) - return; - - const auto* weaponDef = Game::BG_GetWeaponDef(aaGlob.ps.weapIndex); - const auto aimAssistRange = AimAssist_Lerp(weaponDef->aimAssistRange, weaponDef->aimAssistRangeAds, aaGlob.adsLerp) * aim_aimAssistRangeScale.get(); - const auto screenTarget = AimAssist_GetBestTarget(&aaGlob, aimAssistRange, aaGlob.tweakables.slowdownRegionWidth, aaGlob.tweakables.slowdownRegionHeight); - - if (screenTarget) - { - *pitchScale = AimAssist_Lerp(aim_slowdown_pitch_scale.get(), aim_slowdown_pitch_scale_ads.get(), aaGlob.adsLerp); - *yawScale = AimAssist_Lerp(aim_slowdown_yaw_scale.get(), aim_slowdown_yaw_scale_ads.get(), aaGlob.adsLerp); - } - - if (AimAssist_IsPlayerUsingOffhand(&aaGlob.ps)) - *pitchScale = 1.0f; - } - - float Gamepad::AimAssist_Lerp(const float from, const float to, const float fraction) - { - return (to - from) * fraction + from; - } - - void Gamepad::AimAssist_ApplyTurnRates(const Game::AimInput* input, Game::AimOutput* output) - { - assert(input->localClientNum < Game::MAX_GAMEPADS); - auto& aaGlob = Game::aaGlobArray[input->localClientNum]; - - auto slowdownPitchScale = 0.0f; - auto slowdownYawScale = 0.0f; - float adjustedPitchAxis; - float adjustedYawAxis; - - if (aaGlob.autoMeleeState == Game::AIM_MELEE_STATE_UPDATING) - { - adjustedPitchAxis = 0.0f; - adjustedYawAxis = 0.0f; - slowdownPitchScale = 1.0f; - slowdownYawScale = 1.0f; - } - else - { - AimAssist_CalcAdjustedAxis(input, &adjustedPitchAxis, &adjustedYawAxis); - AimAssist_CalcSlowdown(input, &slowdownPitchScale, &slowdownYawScale); - } - - const auto sensitivity = input_viewSensitivity.get(); - auto pitchTurnRate = AimAssist_Lerp(aim_turnrate_pitch.get(), aim_turnrate_pitch_ads.get(), aaGlob.adsLerp); - pitchTurnRate = slowdownPitchScale * aaGlob.fovTurnRateScale * sensitivity * pitchTurnRate; - auto yawTurnRate = AimAssist_Lerp(aim_turnrate_yaw.get(), aim_turnrate_yaw_ads.get(), aaGlob.adsLerp); - yawTurnRate = slowdownYawScale * aaGlob.fovTurnRateScale * sensitivity * yawTurnRate; - - if (input->pitchMax > 0 && input->pitchMax < pitchTurnRate) - pitchTurnRate = input->pitchMax; - if (input->yawMax > 0 && input->yawMax < yawTurnRate) - yawTurnRate = input->yawMax; - - const auto pitchSign = adjustedPitchAxis >= 0.0f ? 1.0f : -1.0f; - const auto yawSign = adjustedYawAxis >= 0.0f ? 1.0f : -1.0f; - - const auto pitchDelta = std::fabs(adjustedPitchAxis) * pitchTurnRate; - const auto yawDelta = std::fabs(adjustedYawAxis) * yawTurnRate; - - if (!aim_accel_turnrate_enabled.get()) - { - aaGlob.pitchDelta = pitchDelta; - aaGlob.yawDelta = yawDelta; - } - else - { - const auto accel = aim_accel_turnrate_lerp.get() * sensitivity; - if (pitchDelta <= aaGlob.pitchDelta) - aaGlob.pitchDelta = pitchDelta; - else - aaGlob.pitchDelta = LinearTrack(pitchDelta, aaGlob.pitchDelta, accel, input->deltaTime); - - if (yawDelta <= aaGlob.yawDelta) - aaGlob.yawDelta = yawDelta; - else - aaGlob.yawDelta = LinearTrack(yawDelta, aaGlob.yawDelta, accel, input->deltaTime); - } - - output->pitch += aaGlob.pitchDelta * input->deltaTime * pitchSign; - output->yaw += aaGlob.yawDelta * input->deltaTime * yawSign; - } - - void Gamepad::AimAssist_UpdateGamePadInput(const Game::AimInput* input, Game::AimOutput* output) - { - assert(input->localClientNum < Game::MAX_GAMEPADS); - auto& aaGlob = Game::aaGlobArray[input->localClientNum]; - - output->pitch = input->pitch; - output->yaw = input->yaw; - - if (aaGlob.initialized) - { - Game::AimAssist_UpdateTweakables(input->localClientNum); - Game::AimAssist_UpdateAdsLerp(input); - AimAssist_ApplyTurnRates(input, output); - - Game::AimAssist_ApplyAutoMelee(input, output); - AimAssist_ApplyLockOn(input, output); - } - - aaGlob.prevButtons = input->buttons; - } - - void Gamepad::CL_RemoteControlMove_GamePad(const int localClientNum, Game::usercmd_s* cmd) - { - // Buttons are already handled by keyboard input handler - - const auto up = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_FORWARD); - const auto right = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_SIDE); - const auto yaw = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_YAW); - const auto pitch = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_PITCH); - const auto sensitivity = input_viewSensitivity.get(); - - constexpr auto scale = static_cast(std::numeric_limits::max()); - cmd->remoteControlAngles[0] = ClampChar(cmd->remoteControlAngles[0] + static_cast(std::floor(-up * scale * sensitivity)) - + static_cast(std::floor(-pitch * scale * sensitivity))); - cmd->remoteControlAngles[1] = ClampChar(cmd->remoteControlAngles[1] + static_cast(std::floor(-right * scale * sensitivity)) - + static_cast(std::floor(-yaw * scale * sensitivity))); - } - - constexpr auto CL_RemoteControlMove = 0x5A6BA0; - __declspec(naked) void Gamepad::CL_RemoteControlMove_Stub() - { - __asm - { - // Prepare args for our function call - push edi // usercmd - push eax // localClientNum - - call CL_RemoteControlMove - - // Call our function, the args were already prepared earlier - call CL_RemoteControlMove_GamePad - add esp, 0x8 - - ret - } - } - - bool Gamepad::CG_HandleLocationSelectionInput_GamePad(const int localClientNum, Game::usercmd_s* /*cmd*/) - { - // Buttons are already handled by keyboard input handler - - const auto frameTime = static_cast(Game::cgArray[0].frametime) * 0.001f; - const auto mapAspectRatio = Game::cgArray[0].compassMapWorldSize[0] / Game::cgArray[0].compassMapWorldSize[1]; - const auto selectionRequiresAngle = (Game::cgArray[0].predictedPlayerState.locationSelectionInfo & 0x80) != 0; - - auto up = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_FORWARD); - auto right = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_SIDE); - auto magnitude = up * up + right * right; - - if (magnitude > 1.0f) - { - magnitude = std::sqrt(magnitude); - up /= magnitude; - right /= magnitude; - } - - Game::cgArray[0].selectedLocation[0] += right * cg_mapLocationSelectionCursorSpeed.get() * frameTime; - Game::cgArray[0].selectedLocation[1] -= up * mapAspectRatio * cg_mapLocationSelectionCursorSpeed.get() * frameTime; - - if (selectionRequiresAngle) - { - const auto yawUp = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_PITCH); - const auto yawRight = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_YAW); - - if (std::fabs(yawUp) > 0.0f || std::fabs(yawRight) > 0.0f) - { - Game::vec2_t vec - { - yawUp, - -yawRight - }; - - Game::cgArray[0].selectedLocationAngle = Game::AngleNormalize360(Game::vectoyaw(&vec)); - Game::cgArray[0].selectedAngleLocation[0] = Game::cgArray[0].selectedLocation[0]; - Game::cgArray[0].selectedAngleLocation[1] = Game::cgArray[0].selectedLocation[1]; - } - } - else - { - Game::cgArray[0].selectedAngleLocation[0] = Game::cgArray[0].selectedLocation[0]; - Game::cgArray[0].selectedAngleLocation[1] = Game::cgArray[0].selectedLocation[1]; - } - - return true; - } - - constexpr auto CG_HandleLocationSelectionInput = 0x5A67A0; - __declspec(naked) void Gamepad::CG_HandleLocationSelectionInput_Stub() - { - __asm - { - // Prepare args for our function call - push esi // usercmd - push eax // localClientNum - - call CG_HandleLocationSelectionInput - - test al,al - jz exit_handling - - // Call our function, the args were already prepared earlier - call CG_HandleLocationSelectionInput_GamePad - - exit_handling: - add esp, 0x8 - ret - } - } - - bool Gamepad::CG_ShouldUpdateViewAngles(const int localClientNum) - { - return !Game::Key_IsKeyCatcherActive(localClientNum, Game::KEYCATCH_MASK_ANY) || Game::UI_GetActiveMenu(localClientNum) == Game::UIMENU_SCOREBOARD; - } - - float Gamepad::CL_GamepadAxisValue(const int gamePadIndex, const Game::GamepadVirtualAxis virtualAxis) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - assert(virtualAxis > Game::GPAD_VIRTAXIS_NONE && virtualAxis < Game::GPAD_VIRTAXIS_COUNT); - const auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; - - const auto& [physicalAxis, mapType] = gamePadGlobal.axes.virtualAxes[virtualAxis]; - - if (physicalAxis <= Game::GPAD_PHYSAXIS_NONE || physicalAxis >= Game::GPAD_PHYSAXIS_COUNT) - return 0.0f; - - auto axisDeflection = gamePadGlobal.axes.axesValues[physicalAxis]; - - if (mapType == Game::GPAD_MAP_SQUARED) - { - const auto otherAxisSameStick = axisSameStick[physicalAxis]; - - float otherAxisDeflection; - if (otherAxisSameStick <= Game::GPAD_PHYSAXIS_NONE || otherAxisSameStick >= Game::GPAD_PHYSAXIS_COUNT) - otherAxisDeflection = 0.0f; - else - otherAxisDeflection = gamePadGlobal.axes.axesValues[otherAxisSameStick]; - - axisDeflection = std::sqrt(axisDeflection * axisDeflection + otherAxisDeflection * otherAxisDeflection) * axisDeflection; - } - - return axisDeflection; - } - - char Gamepad::ClampChar(const int value) - { - return static_cast(std::clamp(value, std::numeric_limits::min(), std::numeric_limits::max())); - } - - void Gamepad::CL_GamepadMove(const int gamePadIndex, Game::usercmd_s* cmd, const float frameTimeBase) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePad = gamePads[gamePadIndex]; - auto& clientActive = Game::clients[gamePadIndex]; - - if (!gpad_enabled.get() || !gamePad.enabled) - return; - - auto pitch = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_PITCH); - if (!input_invertPitch.get()) - pitch *= -1; - - auto yaw = -CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_YAW); - auto forward = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_FORWARD); - auto side = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_SIDE); - - // The game implements an attack axis at this location. This axis is unused however so for this patch it was not implemented. - //auto attack = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_ATTACK); - - auto moveScale = static_cast(std::numeric_limits::max()); - - if (std::fabs(side) > 0.0f || std::fabs(forward) > 0.0f) - { - const auto length = std::fabs(side) <= std::fabs(forward) - ? side / forward - : forward / side; - moveScale = std::sqrt((length * length) + 1.0f) * moveScale; - } - - const auto forwardMove = static_cast(std::floor(forward * moveScale)); - const auto rightMove = static_cast(std::floor(side * moveScale)); - - cmd->rightmove = ClampChar(cmd->rightmove + rightMove); - cmd->forwardmove = ClampChar(cmd->forwardmove + forwardMove); - - // Swap attack and throw buttons when using controller and akimbo to match "left trigger"="left weapon" and "right trigger"="right weapon" - if(gamePad.inUse && clientActive.snap.ps.weapCommon.lastWeaponHand == Game::WEAPON_HAND_LEFT) - { - auto oldButtons = cmd->buttons; - if (oldButtons & Game::CMD_BUTTON_ATTACK) - cmd->buttons |= Game::CMD_BUTTON_THROW; - else - cmd->buttons &= ~Game::CMD_BUTTON_THROW; - - if (oldButtons & Game::CMD_BUTTON_THROW) - cmd->buttons |= Game::CMD_BUTTON_ATTACK; - else - cmd->buttons &= ~Game::CMD_BUTTON_ATTACK; - } - - // Check for frozen controls. Flag name should start with PMF_ - if (CG_ShouldUpdateViewAngles(gamePadIndex) && (clientActive.snap.ps.pm_flags & Game::PMF_FROZEN) == 0) - { - Game::AimInput aimInput{}; - Game::AimOutput aimOutput{}; - aimInput.deltaTime = frameTimeBase; - aimInput.buttons = cmd->buttons; - aimInput.localClientNum = gamePadIndex; - aimInput.deltaTimeScaled = static_cast(Game::cls->frametime) * 0.001f; - aimInput.pitch = clientActive.clViewangles[0]; - aimInput.pitchAxis = pitch; - aimInput.pitchMax = clientActive.cgameMaxPitchSpeed; - aimInput.yaw = clientActive.clViewangles[1]; - aimInput.yawAxis = yaw; - aimInput.yawMax = clientActive.cgameMaxYawSpeed; - aimInput.forwardAxis = forward; - aimInput.rightAxis = side; - AimAssist_UpdateGamePadInput(&aimInput, &aimOutput); - clientActive.clViewangles[0] = aimOutput.pitch; - clientActive.clViewangles[1] = aimOutput.yaw; - cmd->meleeChargeDist = aimOutput.meleeChargeDist; - cmd->meleeChargeYaw = aimOutput.meleeChargeYaw; - } - } - - constexpr auto CL_MouseMove = 0x5A6240; - __declspec(naked) void Gamepad::CL_MouseMove_Stub() - { - __asm - { - // Prepare args for our function call - push [esp+0x4] // frametime_base - push ebx // cmd - push eax // localClientNum - - push [esp+0x8] // restore frametime_base on the stack - call CL_MouseMove - add esp,4 - - // Call our function, the args were already prepared earlier - call CL_GamepadMove - add esp,0xC - - ret - } - } - - bool Gamepad::Gamepad_ShouldUse(const Game::gentity_s* playerEnt, const unsigned useTime) - { - // Only apply hold time to +usereload keybind - return !(playerEnt->client->buttons & Game::CMD_BUTTON_USE_RELOAD) || useTime >= static_cast(gpad_use_hold_time.get()); - } - - __declspec(naked) void Gamepad::Player_UseEntity_Stub() - { - __asm - { - // Execute overwritten instructions - cmp eax, [ecx + 0x10] - jl skipUse - - // Call our custom check - push eax - pushad - push eax - push edi - call Gamepad_ShouldUse - add esp, 8h - mov [esp + 0x20], eax - popad - pop eax - - // Skip use if custom check returns false - test al, al - jz skipUse - - // perform use - push 0x5FE39B - ret - - skipUse: - push 0x5FE3AF - ret - } - } - - bool Gamepad::Key_IsValidGamePadChar(const int key) - { - return key >= Game::K_FIRSTGAMEPADBUTTON_RANGE_1 && key <= Game::K_LASTGAMEPADBUTTON_RANGE_1 - || key >= Game::K_FIRSTGAMEPADBUTTON_RANGE_2 && key <= Game::K_LASTGAMEPADBUTTON_RANGE_2 - || key >= Game::K_FIRSTGAMEPADBUTTON_RANGE_3 && key <= Game::K_LASTGAMEPADBUTTON_RANGE_3; - } - - void Gamepad::CL_GamepadResetMenuScrollTime(const int gamePadIndex, const int key, const bool down, const unsigned time) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; - - if (!down) - return; - - const auto scrollDelayFirst = gpad_menu_scroll_delay_first.get(); - for (const auto scrollButton : menuScrollButtonList) - { - if (key == scrollButton) - { - gamePadGlobal.nextScrollTime = scrollDelayFirst + time; - return; - } - } - } - - void Gamepad::CL_GamepadGenerateAPad(const int gamePadIndex, const Game::GamepadPhysicalAxis physicalAxis, unsigned time) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - assert(physicalAxis < Game::GPAD_PHYSAXIS_COUNT && physicalAxis >= 0); - - auto& gamePad = gamePads[gamePadIndex]; - - const auto stick = stickForAxis[physicalAxis]; - const auto stickIndex = stick & Game::GPAD_VALUE_MASK; - if (stick != Game::GPAD_INVALID) - { - assert(stickIndex < 4); - const auto& mapping = analogStickList[stickIndex]; - - if (gamePad.stickDown[stickIndex][Game::GPAD_STICK_POS]) - { - const Game::GamePadButtonEvent event = gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_POS] ? Game::GPAD_BUTTON_UPDATE : Game::GPAD_BUTTON_PRESSED; - CL_GamepadButtonEvent(gamePadIndex, mapping.posCode, event, time); - } - else if (gamePad.stickDown[stickIndex][Game::GPAD_STICK_NEG]) - { - const Game::GamePadButtonEvent event = gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_NEG] ? Game::GPAD_BUTTON_UPDATE : Game::GPAD_BUTTON_PRESSED; - CL_GamepadButtonEvent(gamePadIndex, mapping.negCode, event, time); - } - else if (gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_POS]) - { - CL_GamepadButtonEvent(gamePadIndex, mapping.posCode, Game::GPAD_BUTTON_RELEASED, time); - } - else if (gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_NEG]) - { - CL_GamepadButtonEvent(gamePadIndex, mapping.negCode, Game::GPAD_BUTTON_RELEASED, time); - } - } - } - - void Gamepad::CL_GamepadEvent(const int gamePadIndex, const Game::GamepadPhysicalAxis physicalAxis, const float value, const unsigned time) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - assert(physicalAxis < Game::GPAD_PHYSAXIS_COUNT && physicalAxis >= 0); - - auto& gamePad = gamePads[gamePadIndex]; - auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; - - gamePadGlobal.axes.axesValues[physicalAxis] = value; - CL_GamepadGenerateAPad(gamePadIndex, physicalAxis, time); - - if (std::fabs(value) > 0.0f) - { - gamePad.inUse = true; - gpad_in_use.setRaw(true); - } - } - - void Gamepad::UI_GamepadKeyEvent(const int gamePadIndex, const int key, const bool down) - { - for (const auto& mapping : controllerMenuKeyMappings) - { - if (mapping.controllerKey == key) - { - Game::UI_KeyEvent(gamePadIndex, mapping.pcKey, down); - return; - } - } - - // No point in sending unmapped controller keystrokes to the key event handler since it doesn't know how to use it anyway - // Game::UI_KeyEvent(gamePadIndex, key, down); - } - - bool Gamepad::Scoreboard_HandleInput(int gamePadIndex, int key) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& keyState = Game::playerKeys[gamePadIndex]; - - if (keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "togglescores") == 0) - { - Game::Cbuf_AddText(gamePadIndex, "togglescores\n"); - return true; - } - - switch (key) - { - case Game::K_DPAD_UP: - Game::CG_ScrollScoreboardUp(Game::cgArray); - return true; - - case Game::K_DPAD_DOWN: - Game::CG_ScrollScoreboardDown(Game::cgArray); - return true; - - default: - return false; - } - } - - bool Gamepad::CL_CheckForIgnoreDueToRepeat(const int gamePadIndex, const int key, const int repeatCount, const unsigned time) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; - - if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) - { - const int scrollDelayFirst = gpad_menu_scroll_delay_first.get(); - const int scrollDelayRest = gpad_menu_scroll_delay_rest.get(); - - for (const auto menuScrollButton : menuScrollButtonList) - { - if (key == menuScrollButton) - { - if (repeatCount == 1) - { - gamePadGlobal.nextScrollTime = time + scrollDelayFirst; - return false; - } - - if (time > gamePadGlobal.nextScrollTime) - { - gamePadGlobal.nextScrollTime = time + scrollDelayRest; - return false; - } - break; - } - } - } - - return repeatCount > 1; - } - - void Gamepad::CL_GamepadButtonEvent(const int gamePadIndex, const int key, const Game::GamePadButtonEvent buttonEvent, const unsigned time) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - - const auto pressed = buttonEvent == Game::GPAD_BUTTON_PRESSED; - const auto pressedOrUpdated = pressed || buttonEvent == Game::GPAD_BUTTON_UPDATE; - - auto& keyState = Game::playerKeys[gamePadIndex]; - keyState.keys[key].down = pressedOrUpdated; - - if (pressedOrUpdated) - { - if (++keyState.keys[key].repeats == 1) - keyState.anyKeyDown++; - } - else if (buttonEvent == Game::GPAD_BUTTON_RELEASED && keyState.keys[key].repeats > 0) - { - keyState.keys[key].repeats = 0; - if (--keyState.anyKeyDown < 0) - keyState.anyKeyDown = 0; - } - - if (pressedOrUpdated && CL_CheckForIgnoreDueToRepeat(gamePadIndex, key, keyState.keys[key].repeats, time)) - return; - - if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_LOCATION_SELECTION) && pressedOrUpdated) - { - if (key == Game::K_BUTTON_B || keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "+actionslot 4") == 0) - { - keyState.locSelInputState = Game::LOC_SEL_INPUT_CANCEL; - } - else if (key == Game::K_BUTTON_A || keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "+attack") == 0) - { - keyState.locSelInputState = Game::LOC_SEL_INPUT_CONFIRM; - } - return; - } - - const auto activeMenu = Game::UI_GetActiveMenu(gamePadIndex); - if(activeMenu == Game::UIMENU_SCOREBOARD) - { - if (buttonEvent == Game::GPAD_BUTTON_PRESSED && Scoreboard_HandleInput(gamePadIndex, key)) - return; - } - - keyState.locSelInputState = Game::LOC_SEL_INPUT_NONE; - - const auto* keyBinding = keyState.keys[key].binding; - - char cmd[1024]; - if (pressedOrUpdated) - { - if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) - { - UI_GamepadKeyEvent(gamePadIndex, key, pressedOrUpdated); - return; - } - - if (keyBinding) - { - if (keyBinding[0] == '+') - { - sprintf_s(cmd, "%s %i %i\n", keyBinding, key, time); - Game::Cbuf_AddText(gamePadIndex, cmd); - } - else - { - Game::Cbuf_InsertText(gamePadIndex, keyBinding); - } - } - } - else - { - if (keyBinding && keyBinding[0] == '+') - { - sprintf_s(cmd, "-%s %i %i\n", &keyBinding[1], key, time); - Game::Cbuf_AddText(gamePadIndex, cmd); - } - - if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) - { - UI_GamepadKeyEvent(gamePadIndex, key, pressedOrUpdated); - } - } - } - - void Gamepad::CL_GamepadButtonEventForPort(const int gamePadIndex, const int key, const Game::GamePadButtonEvent buttonEvent, const unsigned time) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePad = gamePads[gamePadIndex]; - - gamePad.inUse = true; - gpad_in_use.setRaw(true); - - if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) - CL_GamepadResetMenuScrollTime(gamePadIndex, key, buttonEvent == Game::GPAD_BUTTON_PRESSED, time); - - - CL_GamepadButtonEvent(gamePadIndex, key, buttonEvent, time); - } - - void Gamepad::GPad_ConvertStickToFloat(const short x, const short y, float& outX, float& outY) - { - if(x == 0 && y == 0) - { - outX = 0.0f; - outY = 0.0f; - return; - } - - Game::vec2_t stickVec; - stickVec[0] = static_cast(x) / static_cast(std::numeric_limits::max()); - stickVec[1] = static_cast(y) / static_cast(std::numeric_limits::max()); - - const auto deadZoneTotal = gpad_stick_deadzone_min.get() + gpad_stick_deadzone_max.get(); - auto len = Game::Vec2Normalize(stickVec); - - if (gpad_stick_deadzone_min.get() <= len) - { - if (1.0f - gpad_stick_deadzone_max.get() >= len) - len = (len - gpad_stick_deadzone_min.get()) / (1.0f - deadZoneTotal); - else - len = 1.0f; - } - else - len = 0.0f; - - outX = stickVec[0] * len; - outY = stickVec[1] * len; - } - - float Gamepad::GPad_GetStick(const int gamePadIndex, const Game::GamePadStick stick) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePad = gamePads[gamePadIndex]; - - return gamePad.sticks[stick]; - } - - float Gamepad::GPad_GetButton(const int gamePadIndex, Game::GamePadButton button) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePad = gamePads[gamePadIndex]; - - float value = 0.0f; - - if (button & Game::GPAD_DIGITAL_MASK) - { - const auto buttonValue = button & Game::GPAD_VALUE_MASK; - value = buttonValue & gamePad.digitals ? 1.0f : 0.0f; - } - else if (button & Game::GPAD_ANALOG_MASK) - { - const auto analogIndex = button & Game::GPAD_VALUE_MASK; - if (analogIndex < std::extent_v) - { - value = gamePad.analogs[analogIndex]; - } - } - - return value; - } - - bool Gamepad::GPad_IsButtonPressed(const int gamePadIndex, Game::GamePadButton button) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePad = gamePads[gamePadIndex]; - - bool down = false; - bool lastDown = false; - - if (button & Game::GPAD_DIGITAL_MASK) - { - const auto buttonValue = button & Game::GPAD_VALUE_MASK; - down = (buttonValue & gamePad.digitals) != 0; - lastDown = (buttonValue & gamePad.lastDigitals) != 0; - } - else if (button & Game::GPAD_ANALOG_MASK) - { - const auto analogIndex = button & Game::GPAD_VALUE_MASK; - assert(analogIndex < std::extent_v); - - if (analogIndex < std::extent_v) - { - down = gamePad.analogs[analogIndex] > 0.0f; - lastDown = gamePad.lastAnalogs[analogIndex] > 0.0f; - } - } - - return down && !lastDown; - } - - bool Gamepad::GPad_ButtonRequiresUpdates(const int gamePadIndex, Game::GamePadButton button) - { - return (button & Game::GPAD_ANALOG_MASK || button & Game::GPAD_DPAD_MASK) && GPad_GetButton(gamePadIndex, button) > 0.0f; - } - - bool Gamepad::GPad_IsButtonReleased(int gamePadIndex, Game::GamePadButton button) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePad = gamePads[gamePadIndex]; - - bool down = false; - bool lastDown = false; - - if (button & Game::GPAD_DIGITAL_MASK) - { - const auto buttonValue = button & Game::GPAD_VALUE_MASK; - - down = (gamePad.digitals & buttonValue) != 0; - lastDown = (gamePad.lastDigitals & buttonValue) != 0; - } - else if (button & Game::GPAD_ANALOG_MASK) - { - const auto analogIndex = button & Game::GPAD_VALUE_MASK; - assert(analogIndex < std::extent_v); - - if (analogIndex < std::extent_v) - { - down = gamePad.analogs[analogIndex] > 0.0f; - lastDown = gamePad.lastAnalogs[analogIndex] > 0.0f; - } - } - - return !down && lastDown; - } - - void Gamepad::GPad_UpdateSticksDown(const int gamePadIndex) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePad = gamePads[gamePadIndex]; - - for (auto stickIndex = 0u; stickIndex < std::extent_v; stickIndex++) - { - for (auto dir = 0; dir < Game::GPAD_STICK_DIR_COUNT; dir++) - { - gamePad.stickDownLast[stickIndex][dir] = gamePad.stickDown[stickIndex][dir]; - - auto threshold = gpad_stick_pressed.get(); - - if (gamePad.stickDownLast[stickIndex][dir]) - threshold -= gpad_stick_pressed_hysteresis.get(); - else - threshold += gpad_stick_pressed_hysteresis.get(); - - if (dir == Game::GPAD_STICK_POS) - { - gamePad.stickDown[stickIndex][dir] = gamePad.sticks[stickIndex] > threshold; - } - else - { - assert(dir == Game::GPAD_STICK_NEG); - gamePad.stickDown[stickIndex][dir] = gamePad.sticks[stickIndex] < -threshold; - } - } - } - } - - void Gamepad::GPad_UpdateSticks(const int gamePadIndex, const XINPUT_GAMEPAD& state) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - - auto& gamePad = gamePads[gamePadIndex]; - - Game::vec2_t lVec, rVec; - GPad_ConvertStickToFloat(state.sThumbLX, state.sThumbLY, lVec[0], lVec[1]); - GPad_ConvertStickToFloat(state.sThumbRX, state.sThumbRY, rVec[0], rVec[1]); - - gamePad.lastSticks[0] = gamePad.sticks[0]; - gamePad.sticks[0] = lVec[0]; - gamePad.lastSticks[1] = gamePad.sticks[1]; - gamePad.sticks[1] = lVec[1]; - gamePad.lastSticks[2] = gamePad.sticks[2]; - gamePad.sticks[2] = rVec[0]; - gamePad.lastSticks[3] = gamePad.sticks[3]; - gamePad.sticks[3] = rVec[1]; - - GPad_UpdateSticksDown(gamePadIndex); + Game::ButtonToCodeMap_t Gamepad::buttonList[] + { + {Game::GPAD_X, Game::K_BUTTON_X}, + {Game::GPAD_A, Game::K_BUTTON_A}, + {Game::GPAD_B, Game::K_BUTTON_B}, + {Game::GPAD_Y, Game::K_BUTTON_Y}, + {Game::GPAD_L_TRIG, Game::K_BUTTON_LTRIG}, + {Game::GPAD_R_TRIG, Game::K_BUTTON_RTRIG}, + {Game::GPAD_L_SHLDR, Game::K_BUTTON_LSHLDR}, + {Game::GPAD_R_SHLDR, Game::K_BUTTON_RSHLDR}, + {Game::GPAD_START, Game::K_BUTTON_START}, + {Game::GPAD_BACK, Game::K_BUTTON_BACK}, + {Game::GPAD_L3, Game::K_BUTTON_LSTICK}, + {Game::GPAD_R3, Game::K_BUTTON_RSTICK}, + {Game::GPAD_UP, Game::K_DPAD_UP}, + {Game::GPAD_DOWN, Game::K_DPAD_DOWN}, + {Game::GPAD_LEFT, Game::K_DPAD_LEFT}, + {Game::GPAD_RIGHT, Game::K_DPAD_RIGHT} + }; + + Game::StickToCodeMap_t Gamepad::analogStickList[4] + { + {Game::GPAD_LX, Game::K_APAD_RIGHT, Game::K_APAD_LEFT}, + {Game::GPAD_LY, Game::K_APAD_UP, Game::K_APAD_DOWN}, + {Game::GPAD_RX, Game::K_APAD_RIGHT, Game::K_APAD_LEFT}, + {Game::GPAD_RY, Game::K_APAD_UP, Game::K_APAD_DOWN}, + }; + + Game::GamePadStick Gamepad::stickForAxis[Game::GPAD_PHYSAXIS_COUNT] + { + Game::GPAD_RX, + Game::GPAD_RY, + Game::GPAD_LX, + Game::GPAD_LY, + Game::GPAD_INVALID, + Game::GPAD_INVALID + }; + + Game::GamepadPhysicalAxis Gamepad::axisSameStick[Game::GPAD_PHYSAXIS_COUNT] + { + Game::GPAD_PHYSAXIS_RSTICK_Y, + Game::GPAD_PHYSAXIS_RSTICK_X, + Game::GPAD_PHYSAXIS_LSTICK_Y, + Game::GPAD_PHYSAXIS_LSTICK_X, + Game::GPAD_PHYSAXIS_NONE, + Game::GPAD_PHYSAXIS_NONE + }; + + const char* Gamepad::physicalAxisNames[Game::GPAD_PHYSAXIS_COUNT] + { + "A_RSTICK_X", + "A_RSTICK_Y", + "A_LSTICK_X", + "A_LSTICK_Y", + "A_RTRIGGER", + "A_LTRIGGER" + }; + + const char* Gamepad::virtualAxisNames[Game::GPAD_VIRTAXIS_COUNT] + { + "VA_SIDE", + "VA_FORWARD", + "VA_UP", + "VA_YAW", + "VA_PITCH", + "VA_ATTACK" + }; + + const char* Gamepad::gamePadMappingTypeNames[Game::GPAD_MAP_COUNT] + { + "MAP_LINEAR", + "MAP_SQUARED" + }; + + Game::keyNum_t Gamepad::menuScrollButtonList[] + { + Game::K_APAD_UP, + Game::K_APAD_DOWN, + Game::K_APAD_LEFT, + Game::K_APAD_RIGHT, + Game::K_DPAD_UP, + Game::K_DPAD_DOWN, + Game::K_DPAD_LEFT, + Game::K_DPAD_RIGHT + }; + + Game::keyname_t Gamepad::extendedKeyNames[] + { + {"BUTTON_A", Game::K_BUTTON_A}, + {"BUTTON_B", Game::K_BUTTON_B}, + {"BUTTON_X", Game::K_BUTTON_X}, + {"BUTTON_Y", Game::K_BUTTON_Y}, + {"BUTTON_LSHLDR", Game::K_BUTTON_LSHLDR}, + {"BUTTON_RSHLDR", Game::K_BUTTON_RSHLDR}, + {"BUTTON_START", Game::K_BUTTON_START}, + {"BUTTON_BACK", Game::K_BUTTON_BACK}, + {"BUTTON_LSTICK", Game::K_BUTTON_LSTICK}, + {"BUTTON_RSTICK", Game::K_BUTTON_RSTICK}, + {"BUTTON_LTRIG", Game::K_BUTTON_LTRIG}, + {"BUTTON_RTRIG", Game::K_BUTTON_RTRIG}, + {"DPAD_UP", Game::K_DPAD_UP}, + {"DPAD_DOWN", Game::K_DPAD_DOWN}, + {"DPAD_LEFT", Game::K_DPAD_LEFT}, + {"DPAD_RIGHT", Game::K_DPAD_RIGHT}, + }; + + Game::keyname_t Gamepad::extendedLocalizedKeyNamesXenon[] + { + // Material text icons pattern: 0x01 width height material_name_len + {"^\x01\x32\x32\x08""button_a", Game::K_BUTTON_A}, + {"^\x01\x32\x32\x08""button_b", Game::K_BUTTON_B}, + {"^\x01\x32\x32\x08""button_x", Game::K_BUTTON_X}, + {"^\x01\x32\x32\x08""button_y", Game::K_BUTTON_Y}, + {"^\x01\x32\x32\x0D""button_lshldr", Game::K_BUTTON_LSHLDR}, + {"^\x01\x32\x32\x0D""button_rshldr", Game::K_BUTTON_RSHLDR}, + {"^\x01\x32\x32\x0C""button_start", Game::K_BUTTON_START}, + {"^\x01\x32\x32\x0B""button_back", Game::K_BUTTON_BACK}, + {"^\x01\x48\x32\x0D""button_lstick", Game::K_BUTTON_LSTICK}, + {"^\x01\x48\x32\x0D""button_rstick", Game::K_BUTTON_RSTICK}, + {"^\x01\x32\x32\x0C""button_ltrig", Game::K_BUTTON_LTRIG}, + {"^\x01\x32\x32\x0C""button_rtrig", Game::K_BUTTON_RTRIG}, + {"^\x01\x32\x32\x07""dpad_up", Game::K_DPAD_UP}, + {"^\x01\x32\x32\x09""dpad_down", Game::K_DPAD_DOWN}, + {"^\x01\x32\x32\x09""dpad_left", Game::K_DPAD_LEFT}, + {"^\x01\x32\x32\x0A""dpad_right", Game::K_DPAD_RIGHT}, + }; + + Game::keyname_t Gamepad::extendedLocalizedKeyNamesPs3[] + { + // Material text icons pattern: 0x01 width height material_name_len + {"^\x01\x32\x32\x10""button_ps3_cross", Game::K_BUTTON_A}, + {"^\x01\x32\x32\x11""button_ps3_circle", Game::K_BUTTON_B}, + {"^\x01\x32\x32\x11""button_ps3_square", Game::K_BUTTON_X}, + {"^\x01\x32\x32\x13""button_ps3_triangle", Game::K_BUTTON_Y}, + {"^\x01\x32\x32\x0D""button_ps3_l1", Game::K_BUTTON_LSHLDR}, + {"^\x01\x32\x32\x0D""button_ps3_r1", Game::K_BUTTON_RSHLDR}, + {"^\x01\x32\x32\x10""button_ps3_start", Game::K_BUTTON_START}, + {"^\x01\x32\x32\x0F""button_ps3_back", Game::K_BUTTON_BACK}, + {"^\x01\x48\x32\x0D""button_ps3_l3", Game::K_BUTTON_LSTICK}, + {"^\x01\x48\x32\x0D""button_ps3_r3", Game::K_BUTTON_RSTICK}, + {"^\x01\x32\x32\x0D""button_ps3_l2", Game::K_BUTTON_LTRIG}, + {"^\x01\x32\x32\x0D""button_ps3_r2", Game::K_BUTTON_RTRIG}, + {"^\x01\x32\x32\x0B""dpad_ps3_up", Game::K_DPAD_UP}, + {"^\x01\x32\x32\x0D""dpad_ps3_down", Game::K_DPAD_DOWN}, + {"^\x01\x32\x32\x0D""dpad_ps3_left", Game::K_DPAD_LEFT}, + {"^\x01\x32\x32\x0E""dpad_ps3_right", Game::K_DPAD_RIGHT}, + }; + Game::keyname_t Gamepad::combinedKeyNames[Game::KEY_NAME_COUNT + std::extent_v + 1]; + Game::keyname_t Gamepad::combinedLocalizedKeyNamesXenon[Game::KEY_NAME_COUNT + std::extent_v + 1]; + Game::keyname_t Gamepad::combinedLocalizedKeyNamesPs3[Game::KEY_NAME_COUNT + std::extent_v + 1]; + + Gamepad::ControllerMenuKeyMapping Gamepad::controllerMenuKeyMappings[] + { + {Game::K_BUTTON_A, Game::K_ENTER}, + {Game::K_BUTTON_START, Game::K_ENTER}, + {Game::K_BUTTON_B, Game::K_ESCAPE}, + {Game::K_BUTTON_BACK, Game::K_ESCAPE}, + {Game::K_DPAD_UP, Game::K_UPARROW}, + {Game::K_APAD_UP, Game::K_UPARROW}, + {Game::K_DPAD_DOWN, Game::K_DOWNARROW}, + {Game::K_APAD_DOWN, Game::K_DOWNARROW}, + {Game::K_DPAD_LEFT, Game::K_LEFTARROW}, + {Game::K_APAD_LEFT, Game::K_LEFTARROW}, + {Game::K_DPAD_RIGHT, Game::K_RIGHTARROW}, + {Game::K_APAD_RIGHT, Game::K_RIGHTARROW}, + }; + + Gamepad::GamePad Gamepad::gamePads[Game::MAX_GAMEPADS]{}; + Gamepad::GamePadGlobals Gamepad::gamePadGlobals[Game::MAX_GAMEPADS]{{}}; + int Gamepad::gamePadBindingsModifiedFlags = 0; + + Dvar::Var Gamepad::gpad_enabled; + Dvar::Var Gamepad::gpad_debug; + Dvar::Var Gamepad::gpad_present; + Dvar::Var Gamepad::gpad_in_use; + Dvar::Var Gamepad::gpad_style; + Dvar::Var Gamepad::gpad_sticksConfig; + Dvar::Var Gamepad::gpad_buttonConfig; + Dvar::Var Gamepad::gpad_menu_scroll_delay_first; + Dvar::Var Gamepad::gpad_menu_scroll_delay_rest; + Dvar::Var Gamepad::gpad_rumble; + Dvar::Var Gamepad::gpad_stick_pressed_hysteresis; + Dvar::Var Gamepad::gpad_stick_pressed; + Dvar::Var Gamepad::gpad_stick_deadzone_max; + Dvar::Var Gamepad::gpad_stick_deadzone_min; + Dvar::Var Gamepad::gpad_button_deadzone; + Dvar::Var Gamepad::gpad_button_rstick_deflect_max; + Dvar::Var Gamepad::gpad_button_lstick_deflect_max; + Dvar::Var Gamepad::gpad_use_hold_time; + Dvar::Var Gamepad::gpad_lockon_enabled; + Dvar::Var Gamepad::gpad_slowdown_enabled; + Dvar::Var Gamepad::input_viewSensitivity; + Dvar::Var Gamepad::input_invertPitch; + Dvar::Var Gamepad::sv_allowAimAssist; + Dvar::Var Gamepad::aim_turnrate_pitch; + Dvar::Var Gamepad::aim_turnrate_pitch_ads; + Dvar::Var Gamepad::aim_turnrate_yaw; + Dvar::Var Gamepad::aim_turnrate_yaw_ads; + Dvar::Var Gamepad::aim_accel_turnrate_enabled; + Dvar::Var Gamepad::aim_accel_turnrate_lerp; + Dvar::Var Gamepad::aim_input_graph_enabled; + Dvar::Var Gamepad::aim_input_graph_index; + Dvar::Var Gamepad::aim_scale_view_axis; + Dvar::Var Gamepad::cl_bypassMouseInput; + Dvar::Var Gamepad::cg_mapLocationSelectionCursorSpeed; + Dvar::Var Gamepad::aim_aimAssistRangeScale; + Dvar::Var Gamepad::aim_slowdown_enabled; + Dvar::Var Gamepad::aim_slowdown_debug; + Dvar::Var Gamepad::aim_slowdown_pitch_scale; + Dvar::Var Gamepad::aim_slowdown_pitch_scale_ads; + Dvar::Var Gamepad::aim_slowdown_yaw_scale; + Dvar::Var Gamepad::aim_slowdown_yaw_scale_ads; + Dvar::Var Gamepad::aim_lockon_enabled; + Dvar::Var Gamepad::aim_lockon_deflection; + Dvar::Var Gamepad::aim_lockon_pitch_strength; + Dvar::Var Gamepad::aim_lockon_strength; + + Gamepad::GamePadGlobals::GamePadGlobals() + : axes{}, + nextScrollTime(0) + { + for (auto& virtualAxis : axes.virtualAxes) + { + virtualAxis.physicalAxis = Game::GPAD_PHYSAXIS_NONE; + virtualAxis.mapType = Game::GPAD_MAP_NONE; + } + } + + __declspec(naked) void Gamepad::MSG_WriteDeltaUsercmdKeyStub() + { + __asm + { + // fix stack pointer + add esp, 0Ch + + // put both forward move and rightmove values in the movement button + mov dl, byte ptr[edi + 1Ah] // to_forwardMove + mov dh, byte ptr[edi + 1Bh] // to_rightMove + + mov[esp + 30h], dx // to_buttons + + mov dl, byte ptr[ebp + 1Ah] // from_forwardMove + mov dh, byte ptr[ebp + 1Bh] // from_rightMove + + mov[esp + 2Ch], dx // from_buttons + + // return back + push 0x60E40E + retn + } + } + + void Gamepad::ApplyMovement(Game::msg_t* msg, int key, Game::usercmd_s* from, Game::usercmd_s* to) + { + char forward; + char right; + + if (Game::MSG_ReadBit(msg)) + { + short movementBits = static_cast(key ^ Game::MSG_ReadBits(msg, 16)); + + forward = static_cast(movementBits); + right = static_cast(movementBits >> 8); + } + else + { + forward = from->forwardmove; + right = from->rightmove; + } + + to->forwardmove = forward; + to->rightmove = right; + } + + __declspec(naked) void Gamepad::MSG_ReadDeltaUsercmdKeyStub() + { + __asm + { + push ebx // to + push ebp // from + push edi // key + push esi // msg + call ApplyMovement + add esp, 10h + + // return back + push 0x4921BF + ret + } + } + + __declspec(naked) void Gamepad::MSG_ReadDeltaUsercmdKeyStub2() + { + __asm + { + push ebx // to + push ebp // from + push edi // key + push esi // msg + call ApplyMovement + add esp, 10h + + // return back + push 3 + push esi + push 0x492085 + ret + } + } + + bool Gamepad::GPad_Check(const int gamePadIndex, const int portIndex) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + if (XInputGetCapabilities(portIndex, XINPUT_FLAG_GAMEPAD, &gamePad.caps) == ERROR_SUCCESS) + { + gamePad.enabled = true; + gamePad.portIndex = portIndex; + return true; + } + + gamePad.enabled = false; + return false; + } + + void Gamepad::GPad_RefreshAll() + { + auto currentGamePadNum = 0; + + for (auto currentPort = 0; currentPort < XUSER_MAX_COUNT && currentGamePadNum < Game::MAX_GAMEPADS; currentPort++) + { + if (GPad_Check(currentGamePadNum, currentPort)) + currentGamePadNum++; + } + } + + float Gamepad::LinearTrack(const float target, const float current, const float rate, const float deltaTime) + { + const auto err = target - current; + float step; + if (err <= 0.0f) + step = -rate * deltaTime; + else + step = rate * deltaTime; + + if (std::fabs(err) <= 0.001f) + return target; + + if (std::fabs(step) <= std::fabs(err)) + return current + step; + + return target; + } + + bool Gamepad::AimAssist_DoBoundsIntersectCenterBox(const float* clipMins, const float* clipMaxs, const float clipHalfWidth, const float clipHalfHeight) + { + return clipHalfWidth >= clipMins[0] && clipMaxs[0] >= -clipHalfWidth + && clipHalfHeight >= clipMins[1] && clipMaxs[1] >= -clipHalfHeight; + } + + bool Gamepad::AimAssist_IsPlayerUsingOffhand(Game::AimAssistPlayerState* ps) + { + // Check offhand flag + if ((ps->weapFlags & 2) == 0) + return false; + + // If offhand weapon has no id we are not using one + if (!ps->weapIndex) + return false; + + const auto* weaponDef = Game::BG_GetWeaponDef(ps->weapIndex); + + return weaponDef->offhandClass != Game::OFFHAND_CLASS_NONE; + } + + const Game::AimScreenTarget* Gamepad::AimAssist_GetBestTarget(const Game::AimAssistGlobals* aaGlob, const float range, const float regionWidth, const float regionHeight) + { + const auto rangeSqr = range * range; + for (auto targetIndex = 0; targetIndex < aaGlob->screenTargetCount; targetIndex++) + { + const auto* currentTarget = &aaGlob->screenTargets[targetIndex]; + if (currentTarget->distSqr <= rangeSqr && AimAssist_DoBoundsIntersectCenterBox(currentTarget->clipMins, currentTarget->clipMaxs, regionWidth, regionHeight)) + { + return currentTarget; + } + } + + return nullptr; + } + + const Game::AimScreenTarget* Gamepad::AimAssist_GetTargetFromEntity(const Game::AimAssistGlobals* aaGlob, const int entIndex) + { + if (entIndex == Game::AIM_TARGET_INVALID) + return nullptr; + + for (auto targetIndex = 0; targetIndex < aaGlob->screenTargetCount; targetIndex++) + { + const auto* currentTarget = &aaGlob->screenTargets[targetIndex]; + if (currentTarget->entIndex == entIndex) + return currentTarget; + } + + return nullptr; + } + + const Game::AimScreenTarget* Gamepad::AimAssist_GetPrevOrBestTarget(const Game::AimAssistGlobals* aaGlob, const float range, const float regionWidth, const float regionHeight, + const int prevTargetEnt) + { + const auto screenTarget = AimAssist_GetTargetFromEntity(aaGlob, prevTargetEnt); + + if (screenTarget && (range * range) > screenTarget->distSqr && AimAssist_DoBoundsIntersectCenterBox(screenTarget->clipMins, screenTarget->clipMaxs, regionWidth, regionHeight)) + return screenTarget; + + return AimAssist_GetBestTarget(aaGlob, range, regionWidth, regionHeight); + } + + bool Gamepad::AimAssist_IsLockonActive(const int gamePadIndex) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& aaGlob = Game::aaGlobArray[gamePadIndex]; + + if (!aim_lockon_enabled.get() || !gpad_lockon_enabled.get()) + return false; + + if (AimAssist_IsPlayerUsingOffhand(&aaGlob.ps)) + return false; + + if (aaGlob.autoAimActive || aaGlob.autoMeleeState == Game::AIM_MELEE_STATE_UPDATING) + return false; + + return true; + } + + void Gamepad::AimAssist_ApplyLockOn(const Game::AimInput* input, Game::AimOutput* output) + { + assert(input); + assert(input->localClientNum < Game::MAX_GAMEPADS); + auto& aaGlob = Game::aaGlobArray[input->localClientNum]; + + const auto prevTargetEnt = aaGlob.lockOnTargetEnt; + aaGlob.lockOnTargetEnt = Game::AIM_TARGET_INVALID; + + if (!AimAssist_IsLockonActive(input->localClientNum)) + return; + + const auto* weaponDef = Game::BG_GetWeaponDef(aaGlob.ps.weapIndex); + if (weaponDef->requireLockonToFire) + return; + + const auto deflection = aim_lockon_deflection.get(); + if (deflection > std::fabs(input->pitchAxis) && deflection > std::fabs(input->yawAxis) && deflection > std::fabs(input->rightAxis)) + return; + + if (!aaGlob.ps.weapIndex) + return; + + const auto aimAssistRange = AimAssist_Lerp(weaponDef->aimAssistRange, weaponDef->aimAssistRangeAds, aaGlob.adsLerp) * aim_aimAssistRangeScale.get(); + const auto screenTarget = AimAssist_GetPrevOrBestTarget(&aaGlob, aimAssistRange, aaGlob.tweakables.lockOnRegionWidth, aaGlob.tweakables.lockOnRegionHeight, prevTargetEnt); + + if (screenTarget && screenTarget->distSqr > 0.0f) + { + aaGlob.lockOnTargetEnt = screenTarget->entIndex; + const auto arcLength = std::sqrt(screenTarget->distSqr) * static_cast(M_PI); + + const auto pitchTurnRate = + (screenTarget->velocity[0] * aaGlob.viewAxis[2][0] + screenTarget->velocity[1] * aaGlob.viewAxis[2][1] + screenTarget->velocity[2] * aaGlob.viewAxis[2][2] + - (aaGlob.ps.velocity[0] * aaGlob.viewAxis[2][0] + aaGlob.ps.velocity[1] * aaGlob.viewAxis[2][1] + aaGlob.ps.velocity[2] * aaGlob.viewAxis[2][2])) + / arcLength * 180.0f * aim_lockon_pitch_strength.get(); + + const auto yawTurnRate = + (screenTarget->velocity[0] * aaGlob.viewAxis[1][0] + screenTarget->velocity[1] * aaGlob.viewAxis[1][1] + screenTarget->velocity[2] * aaGlob.viewAxis[1][2] + - (aaGlob.ps.velocity[0] * aaGlob.viewAxis[1][0] + aaGlob.ps.velocity[1] * aaGlob.viewAxis[1][1] + aaGlob.ps.velocity[2] * aaGlob.viewAxis[1][2])) + / arcLength * 180.0f * aim_lockon_strength.get(); + + output->pitch -= pitchTurnRate * input->deltaTime; + output->yaw += yawTurnRate * input->deltaTime; + } + } + + void Gamepad::AimAssist_CalcAdjustedAxis(const Game::AimInput* input, float* pitchAxis, float* yawAxis) + { + assert(input); + assert(pitchAxis); + assert(yawAxis); + + const auto graphIndex = aim_input_graph_index.get(); + if (aim_input_graph_enabled.get() && graphIndex >= 0 && static_cast(graphIndex) < Game::AIM_ASSIST_GRAPH_COUNT) + { + const auto deflection = std::sqrt(input->pitchAxis * input->pitchAxis + input->yawAxis * input->yawAxis); + + float fraction; + if (deflection - 1.0f < 0.0f) + fraction = deflection; + else + fraction = 1.0f; + + if (0.0f - deflection >= 0.0f) + fraction = 0.0f; + + const auto graphScale = Game::GraphFloat_GetValue(&Game::aaInputGraph[graphIndex], fraction); + *pitchAxis = input->pitchAxis * graphScale; + *yawAxis = input->yawAxis * graphScale; + } + else + { + *pitchAxis = input->pitchAxis; + *yawAxis = input->yawAxis; + } + + if (aim_scale_view_axis.get()) + { + const auto absPitchAxis = std::fabs(*pitchAxis); + const auto absYawAxis = std::fabs(*yawAxis); + + if (absPitchAxis <= absYawAxis) + *pitchAxis = (1.0f - (absYawAxis - absPitchAxis)) * *pitchAxis; + else + *yawAxis = (1.0f - (absPitchAxis - absYawAxis)) * *yawAxis; + } + } + + bool Gamepad::AimAssist_IsSlowdownActive(const Game::AimAssistPlayerState* ps) + { + if (!aim_slowdown_enabled.get() || !gpad_slowdown_enabled.get()) + return false; + + if (!ps->weapIndex) + return false; + + const auto* weaponDef = Game::BG_GetWeaponDef(ps->weapIndex); + if (weaponDef->requireLockonToFire) + return false; + + if (ps->linkFlags & Game::PLF_WEAPONVIEW_ONLY) + return false; + + if (ps->weaponState >= Game::WEAPON_STUNNED_START && ps->weaponState <= Game::WEAPON_STUNNED_END) + return false; + + if (ps->eFlags & (Game::EF_VEHICLE_ACTIVE | Game::EF_TURRET_ACTIVE_DUCK | Game::EF_TURRET_ACTIVE_PRONE)) + return false; + + if (!ps->hasAmmo) + return false; + + return true; + } + + void Gamepad::AimAssist_CalcSlowdown(const Game::AimInput* input, float* pitchScale, float* yawScale) + { + assert(input); + assert(input->localClientNum < Game::MAX_GAMEPADS); + auto& aaGlob = Game::aaGlobArray[input->localClientNum]; + assert(pitchScale); + assert(yawScale); + + *pitchScale = 1.0f; + *yawScale = 1.0f; + + if (!AimAssist_IsSlowdownActive(&aaGlob.ps)) + return; + + const auto* weaponDef = Game::BG_GetWeaponDef(aaGlob.ps.weapIndex); + const auto aimAssistRange = AimAssist_Lerp(weaponDef->aimAssistRange, weaponDef->aimAssistRangeAds, aaGlob.adsLerp) * aim_aimAssistRangeScale.get(); + const auto screenTarget = AimAssist_GetBestTarget(&aaGlob, aimAssistRange, aaGlob.tweakables.slowdownRegionWidth, aaGlob.tweakables.slowdownRegionHeight); + + if (screenTarget) + { + *pitchScale = AimAssist_Lerp(aim_slowdown_pitch_scale.get(), aim_slowdown_pitch_scale_ads.get(), aaGlob.adsLerp); + *yawScale = AimAssist_Lerp(aim_slowdown_yaw_scale.get(), aim_slowdown_yaw_scale_ads.get(), aaGlob.adsLerp); + } + + if (AimAssist_IsPlayerUsingOffhand(&aaGlob.ps)) + *pitchScale = 1.0f; + } + + float Gamepad::AimAssist_Lerp(const float from, const float to, const float fraction) + { + return (to - from) * fraction + from; + } + + void Gamepad::AimAssist_ApplyTurnRates(const Game::AimInput* input, Game::AimOutput* output) + { + assert(input->localClientNum < Game::MAX_GAMEPADS); + auto& aaGlob = Game::aaGlobArray[input->localClientNum]; + + auto slowdownPitchScale = 0.0f; + auto slowdownYawScale = 0.0f; + float adjustedPitchAxis; + float adjustedYawAxis; + + if (aaGlob.autoMeleeState == Game::AIM_MELEE_STATE_UPDATING) + { + adjustedPitchAxis = 0.0f; + adjustedYawAxis = 0.0f; + slowdownPitchScale = 1.0f; + slowdownYawScale = 1.0f; + } + else + { + AimAssist_CalcAdjustedAxis(input, &adjustedPitchAxis, &adjustedYawAxis); + AimAssist_CalcSlowdown(input, &slowdownPitchScale, &slowdownYawScale); + } + + const auto sensitivity = input_viewSensitivity.get(); + auto pitchTurnRate = AimAssist_Lerp(aim_turnrate_pitch.get(), aim_turnrate_pitch_ads.get(), aaGlob.adsLerp); + pitchTurnRate = slowdownPitchScale * aaGlob.fovTurnRateScale * sensitivity * pitchTurnRate; + auto yawTurnRate = AimAssist_Lerp(aim_turnrate_yaw.get(), aim_turnrate_yaw_ads.get(), aaGlob.adsLerp); + yawTurnRate = slowdownYawScale * aaGlob.fovTurnRateScale * sensitivity * yawTurnRate; + + if (input->pitchMax > 0 && input->pitchMax < pitchTurnRate) + pitchTurnRate = input->pitchMax; + if (input->yawMax > 0 && input->yawMax < yawTurnRate) + yawTurnRate = input->yawMax; + + const auto pitchSign = adjustedPitchAxis >= 0.0f ? 1.0f : -1.0f; + const auto yawSign = adjustedYawAxis >= 0.0f ? 1.0f : -1.0f; + + const auto pitchDelta = std::fabs(adjustedPitchAxis) * pitchTurnRate; + const auto yawDelta = std::fabs(adjustedYawAxis) * yawTurnRate; + + if (!aim_accel_turnrate_enabled.get()) + { + aaGlob.pitchDelta = pitchDelta; + aaGlob.yawDelta = yawDelta; + } + else + { + const auto accel = aim_accel_turnrate_lerp.get() * sensitivity; + if (pitchDelta <= aaGlob.pitchDelta) + aaGlob.pitchDelta = pitchDelta; + else + aaGlob.pitchDelta = LinearTrack(pitchDelta, aaGlob.pitchDelta, accel, input->deltaTime); + + if (yawDelta <= aaGlob.yawDelta) + aaGlob.yawDelta = yawDelta; + else + aaGlob.yawDelta = LinearTrack(yawDelta, aaGlob.yawDelta, accel, input->deltaTime); + } + + output->pitch += aaGlob.pitchDelta * input->deltaTime * pitchSign; + output->yaw += aaGlob.yawDelta * input->deltaTime * yawSign; + } + + void Gamepad::AimAssist_UpdateGamePadInput(const Game::AimInput* input, Game::AimOutput* output) + { + assert(input->localClientNum < Game::MAX_GAMEPADS); + auto& aaGlob = Game::aaGlobArray[input->localClientNum]; + + output->pitch = input->pitch; + output->yaw = input->yaw; + + if (aaGlob.initialized) + { + Game::AimAssist_UpdateTweakables(input->localClientNum); + Game::AimAssist_UpdateAdsLerp(input); + AimAssist_ApplyTurnRates(input, output); + + Game::AimAssist_ApplyAutoMelee(input, output); + AimAssist_ApplyLockOn(input, output); + } + + aaGlob.prevButtons = input->buttons; + } + + void Gamepad::CL_RemoteControlMove_GamePad(const int localClientNum, Game::usercmd_s* cmd) + { + // Buttons are already handled by keyboard input handler + + const auto up = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_FORWARD); + const auto right = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_SIDE); + const auto yaw = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_YAW); + const auto pitch = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_PITCH); + const auto sensitivity = input_viewSensitivity.get(); + + constexpr auto scale = static_cast(std::numeric_limits::max()); + cmd->remoteControlAngles[0] = ClampChar(cmd->remoteControlAngles[0] + static_cast(std::floor(-up * scale * sensitivity)) + + static_cast(std::floor(-pitch * scale * sensitivity))); + cmd->remoteControlAngles[1] = ClampChar(cmd->remoteControlAngles[1] + static_cast(std::floor(-right * scale * sensitivity)) + + static_cast(std::floor(-yaw * scale * sensitivity))); + } + + constexpr auto CL_RemoteControlMove = 0x5A6BA0; + __declspec(naked) void Gamepad::CL_RemoteControlMove_Stub() + { + __asm + { + // Prepare args for our function call + push edi // usercmd + push eax // localClientNum + + call CL_RemoteControlMove + + // Call our function, the args were already prepared earlier + call CL_RemoteControlMove_GamePad + add esp, 0x8 + + ret + } + } + + bool Gamepad::CG_HandleLocationSelectionInput_GamePad(const int localClientNum, Game::usercmd_s* /*cmd*/) + { + // Buttons are already handled by keyboard input handler + + const auto frameTime = static_cast(Game::cgArray[0].frametime) * 0.001f; + const auto mapAspectRatio = Game::cgArray[0].compassMapWorldSize[0] / Game::cgArray[0].compassMapWorldSize[1]; + const auto selectionRequiresAngle = (Game::cgArray[0].predictedPlayerState.locationSelectionInfo & 0x80) != 0; + + auto up = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_FORWARD); + auto right = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_SIDE); + auto magnitude = up * up + right * right; + + if (magnitude > 1.0f) + { + magnitude = std::sqrt(magnitude); + up /= magnitude; + right /= magnitude; + } + + Game::cgArray[0].selectedLocation[0] += right * cg_mapLocationSelectionCursorSpeed.get() * frameTime; + Game::cgArray[0].selectedLocation[1] -= up * mapAspectRatio * cg_mapLocationSelectionCursorSpeed.get() * frameTime; + + if (selectionRequiresAngle) + { + const auto yawUp = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_PITCH); + const auto yawRight = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_YAW); + + if (std::fabs(yawUp) > 0.0f || std::fabs(yawRight) > 0.0f) + { + Game::vec2_t vec + { + yawUp, + -yawRight + }; + + Game::cgArray[0].selectedLocationAngle = Game::AngleNormalize360(Game::vectoyaw(&vec)); + Game::cgArray[0].selectedAngleLocation[0] = Game::cgArray[0].selectedLocation[0]; + Game::cgArray[0].selectedAngleLocation[1] = Game::cgArray[0].selectedLocation[1]; + } + } + else + { + Game::cgArray[0].selectedAngleLocation[0] = Game::cgArray[0].selectedLocation[0]; + Game::cgArray[0].selectedAngleLocation[1] = Game::cgArray[0].selectedLocation[1]; + } + + return true; + } + + constexpr auto CG_HandleLocationSelectionInput = 0x5A67A0; + __declspec(naked) void Gamepad::CG_HandleLocationSelectionInput_Stub() + { + __asm + { + // Prepare args for our function call + push esi // usercmd + push eax // localClientNum + + call CG_HandleLocationSelectionInput + + test al,al + jz exit_handling + + // Call our function, the args were already prepared earlier + call CG_HandleLocationSelectionInput_GamePad + + exit_handling: + add esp, 0x8 + ret + } + } + + bool Gamepad::CG_ShouldUpdateViewAngles(const int localClientNum) + { + return !Game::Key_IsKeyCatcherActive(localClientNum, Game::KEYCATCH_MASK_ANY) || Game::UI_GetActiveMenu(localClientNum) == Game::UIMENU_SCOREBOARD; + } + + float Gamepad::CL_GamepadAxisValue(const int gamePadIndex, const Game::GamepadVirtualAxis virtualAxis) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + assert(virtualAxis > Game::GPAD_VIRTAXIS_NONE && virtualAxis < Game::GPAD_VIRTAXIS_COUNT); + const auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + + const auto& [physicalAxis, mapType] = gamePadGlobal.axes.virtualAxes[virtualAxis]; + + if (physicalAxis <= Game::GPAD_PHYSAXIS_NONE || physicalAxis >= Game::GPAD_PHYSAXIS_COUNT) + return 0.0f; + + auto axisDeflection = gamePadGlobal.axes.axesValues[physicalAxis]; + + if (mapType == Game::GPAD_MAP_SQUARED) + { + const auto otherAxisSameStick = axisSameStick[physicalAxis]; + + float otherAxisDeflection; + if (otherAxisSameStick <= Game::GPAD_PHYSAXIS_NONE || otherAxisSameStick >= Game::GPAD_PHYSAXIS_COUNT) + otherAxisDeflection = 0.0f; + else + otherAxisDeflection = gamePadGlobal.axes.axesValues[otherAxisSameStick]; + + axisDeflection = std::sqrt(axisDeflection * axisDeflection + otherAxisDeflection * otherAxisDeflection) * axisDeflection; + } + + return axisDeflection; + } + + char Gamepad::ClampChar(const int value) + { + return static_cast(std::clamp(value, std::numeric_limits::min(), std::numeric_limits::max())); + } + + void Gamepad::CL_GamepadMove(const int gamePadIndex, Game::usercmd_s* cmd, const float frameTimeBase) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + auto& clientActive = Game::clients[gamePadIndex]; + + if (!gpad_enabled.get() || !gamePad.enabled) + return; + + auto pitch = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_PITCH); + if (!input_invertPitch.get()) + pitch *= -1; + + auto yaw = -CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_YAW); + auto forward = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_FORWARD); + auto side = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_SIDE); + + // The game implements an attack axis at this location. This axis is unused however so for this patch it was not implemented. + //auto attack = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_ATTACK); + + auto moveScale = static_cast(std::numeric_limits::max()); + + if (std::fabs(side) > 0.0f || std::fabs(forward) > 0.0f) + { + const auto length = std::fabs(side) <= std::fabs(forward) + ? side / forward + : forward / side; + moveScale = std::sqrt((length * length) + 1.0f) * moveScale; + } + + const auto forwardMove = static_cast(std::floor(forward * moveScale)); + const auto rightMove = static_cast(std::floor(side * moveScale)); + + cmd->rightmove = ClampChar(cmd->rightmove + rightMove); + cmd->forwardmove = ClampChar(cmd->forwardmove + forwardMove); + + // Swap attack and throw buttons when using controller and akimbo to match "left trigger"="left weapon" and "right trigger"="right weapon" + if(gamePad.inUse && clientActive.snap.ps.weapCommon.lastWeaponHand == Game::WEAPON_HAND_LEFT) + { + auto oldButtons = cmd->buttons; + if (oldButtons & Game::CMD_BUTTON_ATTACK) + cmd->buttons |= Game::CMD_BUTTON_THROW; + else + cmd->buttons &= ~Game::CMD_BUTTON_THROW; + + if (oldButtons & Game::CMD_BUTTON_THROW) + cmd->buttons |= Game::CMD_BUTTON_ATTACK; + else + cmd->buttons &= ~Game::CMD_BUTTON_ATTACK; + } + + // Check for frozen controls. Flag name should start with PMF_ + if (CG_ShouldUpdateViewAngles(gamePadIndex) && (clientActive.snap.ps.pm_flags & Game::PMF_FROZEN) == 0) + { + Game::AimInput aimInput{}; + Game::AimOutput aimOutput{}; + aimInput.deltaTime = frameTimeBase; + aimInput.buttons = cmd->buttons; + aimInput.localClientNum = gamePadIndex; + aimInput.deltaTimeScaled = static_cast(Game::cls->frametime) * 0.001f; + aimInput.pitch = clientActive.clViewangles[0]; + aimInput.pitchAxis = pitch; + aimInput.pitchMax = clientActive.cgameMaxPitchSpeed; + aimInput.yaw = clientActive.clViewangles[1]; + aimInput.yawAxis = yaw; + aimInput.yawMax = clientActive.cgameMaxYawSpeed; + aimInput.forwardAxis = forward; + aimInput.rightAxis = side; + AimAssist_UpdateGamePadInput(&aimInput, &aimOutput); + clientActive.clViewangles[0] = aimOutput.pitch; + clientActive.clViewangles[1] = aimOutput.yaw; + cmd->meleeChargeDist = aimOutput.meleeChargeDist; + cmd->meleeChargeYaw = aimOutput.meleeChargeYaw; + } + } + + constexpr auto CL_MouseMove = 0x5A6240; + __declspec(naked) void Gamepad::CL_MouseMove_Stub() + { + __asm + { + // Prepare args for our function call + push [esp+0x4] // frametime_base + push ebx // cmd + push eax // localClientNum + + push [esp+0x8] // restore frametime_base on the stack + call CL_MouseMove + add esp,4 + + // Call our function, the args were already prepared earlier + call CL_GamepadMove + add esp,0xC + + ret + } + } + + bool Gamepad::Gamepad_ShouldUse(const Game::gentity_s* playerEnt, const unsigned useTime) + { + // Only apply hold time to +usereload keybind + return !(playerEnt->client->buttons & Game::CMD_BUTTON_USE_RELOAD) || useTime >= static_cast(gpad_use_hold_time.get()); + } + + __declspec(naked) void Gamepad::Player_UseEntity_Stub() + { + __asm + { + // Execute overwritten instructions + cmp eax, [ecx + 0x10] + jl skipUse + + // Call our custom check + push eax + pushad + push eax + push edi + call Gamepad_ShouldUse + add esp, 8h + mov [esp + 0x20], eax + popad + pop eax + + // Skip use if custom check returns false + test al, al + jz skipUse + + // perform use + push 0x5FE39B + ret + + skipUse: + push 0x5FE3AF + ret + } + } + + bool Gamepad::Key_IsValidGamePadChar(const int key) + { + return key >= Game::K_FIRSTGAMEPADBUTTON_RANGE_1 && key <= Game::K_LASTGAMEPADBUTTON_RANGE_1 + || key >= Game::K_FIRSTGAMEPADBUTTON_RANGE_2 && key <= Game::K_LASTGAMEPADBUTTON_RANGE_2 + || key >= Game::K_FIRSTGAMEPADBUTTON_RANGE_3 && key <= Game::K_LASTGAMEPADBUTTON_RANGE_3; + } + + void Gamepad::CL_GamepadResetMenuScrollTime(const int gamePadIndex, const int key, const bool down, const unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + + if (!down) + return; + + const auto scrollDelayFirst = gpad_menu_scroll_delay_first.get(); + for (const auto scrollButton : menuScrollButtonList) + { + if (key == scrollButton) + { + gamePadGlobal.nextScrollTime = scrollDelayFirst + time; + return; + } + } + } + + void Gamepad::CL_GamepadGenerateAPad(const int gamePadIndex, const Game::GamepadPhysicalAxis physicalAxis, unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + assert(physicalAxis < Game::GPAD_PHYSAXIS_COUNT && physicalAxis >= 0); + + auto& gamePad = gamePads[gamePadIndex]; + + const auto stick = stickForAxis[physicalAxis]; + const auto stickIndex = stick & Game::GPAD_VALUE_MASK; + if (stick != Game::GPAD_INVALID) + { + assert(stickIndex < 4); + const auto& mapping = analogStickList[stickIndex]; + + if (gamePad.stickDown[stickIndex][Game::GPAD_STICK_POS]) + { + const Game::GamePadButtonEvent event = gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_POS] ? Game::GPAD_BUTTON_UPDATE : Game::GPAD_BUTTON_PRESSED; + CL_GamepadButtonEvent(gamePadIndex, mapping.posCode, event, time); + } + else if (gamePad.stickDown[stickIndex][Game::GPAD_STICK_NEG]) + { + const Game::GamePadButtonEvent event = gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_NEG] ? Game::GPAD_BUTTON_UPDATE : Game::GPAD_BUTTON_PRESSED; + CL_GamepadButtonEvent(gamePadIndex, mapping.negCode, event, time); + } + else if (gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_POS]) + { + CL_GamepadButtonEvent(gamePadIndex, mapping.posCode, Game::GPAD_BUTTON_RELEASED, time); + } + else if (gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_NEG]) + { + CL_GamepadButtonEvent(gamePadIndex, mapping.negCode, Game::GPAD_BUTTON_RELEASED, time); + } + } + } + + void Gamepad::CL_GamepadEvent(const int gamePadIndex, const Game::GamepadPhysicalAxis physicalAxis, const float value, const unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + assert(physicalAxis < Game::GPAD_PHYSAXIS_COUNT && physicalAxis >= 0); + + auto& gamePad = gamePads[gamePadIndex]; + auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + + gamePadGlobal.axes.axesValues[physicalAxis] = value; + CL_GamepadGenerateAPad(gamePadIndex, physicalAxis, time); + + if (std::fabs(value) > 0.0f) + { + gamePad.inUse = true; + gpad_in_use.setRaw(true); + } + } + + void Gamepad::UI_GamepadKeyEvent(const int gamePadIndex, const int key, const bool down) + { + for (const auto& mapping : controllerMenuKeyMappings) + { + if (mapping.controllerKey == key) + { + Game::UI_KeyEvent(gamePadIndex, mapping.pcKey, down); + return; + } + } + + // No point in sending unmapped controller keystrokes to the key event handler since it doesn't know how to use it anyway + // Game::UI_KeyEvent(gamePadIndex, key, down); + } + + bool Gamepad::Scoreboard_HandleInput(int gamePadIndex, int key) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& keyState = Game::playerKeys[gamePadIndex]; + + if (keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "togglescores") == 0) + { + Game::Cbuf_AddText(gamePadIndex, "togglescores\n"); + return true; + } + + switch (key) + { + case Game::K_DPAD_UP: + Game::CG_ScrollScoreboardUp(Game::cgArray); + return true; + + case Game::K_DPAD_DOWN: + Game::CG_ScrollScoreboardDown(Game::cgArray); + return true; + + default: + return false; + } + } + + bool Gamepad::CL_CheckForIgnoreDueToRepeat(const int gamePadIndex, const int key, const int repeatCount, const unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + + if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) + { + const int scrollDelayFirst = gpad_menu_scroll_delay_first.get(); + const int scrollDelayRest = gpad_menu_scroll_delay_rest.get(); + + for (const auto menuScrollButton : menuScrollButtonList) + { + if (key == menuScrollButton) + { + if (repeatCount == 1) + { + gamePadGlobal.nextScrollTime = time + scrollDelayFirst; + return false; + } + + if (time > gamePadGlobal.nextScrollTime) + { + gamePadGlobal.nextScrollTime = time + scrollDelayRest; + return false; + } + break; + } + } + } + + return repeatCount > 1; + } + + void Gamepad::CL_GamepadButtonEvent(const int gamePadIndex, const int key, const Game::GamePadButtonEvent buttonEvent, const unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + + const auto pressed = buttonEvent == Game::GPAD_BUTTON_PRESSED; + const auto pressedOrUpdated = pressed || buttonEvent == Game::GPAD_BUTTON_UPDATE; + + auto& keyState = Game::playerKeys[gamePadIndex]; + keyState.keys[key].down = pressedOrUpdated; + + if (pressedOrUpdated) + { + if (++keyState.keys[key].repeats == 1) + keyState.anyKeyDown++; + } + else if (buttonEvent == Game::GPAD_BUTTON_RELEASED && keyState.keys[key].repeats > 0) + { + keyState.keys[key].repeats = 0; + if (--keyState.anyKeyDown < 0) + keyState.anyKeyDown = 0; + } + + if (pressedOrUpdated && CL_CheckForIgnoreDueToRepeat(gamePadIndex, key, keyState.keys[key].repeats, time)) + return; + + if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_LOCATION_SELECTION) && pressedOrUpdated) + { + if (key == Game::K_BUTTON_B || keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "+actionslot 4") == 0) + { + keyState.locSelInputState = Game::LOC_SEL_INPUT_CANCEL; + } + else if (key == Game::K_BUTTON_A || keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "+attack") == 0) + { + keyState.locSelInputState = Game::LOC_SEL_INPUT_CONFIRM; + } + return; + } + + const auto activeMenu = Game::UI_GetActiveMenu(gamePadIndex); + if(activeMenu == Game::UIMENU_SCOREBOARD) + { + if (buttonEvent == Game::GPAD_BUTTON_PRESSED && Scoreboard_HandleInput(gamePadIndex, key)) + return; + } + + keyState.locSelInputState = Game::LOC_SEL_INPUT_NONE; + + const auto* keyBinding = keyState.keys[key].binding; + + char cmd[1024]; + if (pressedOrUpdated) + { + if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) + { + UI_GamepadKeyEvent(gamePadIndex, key, pressedOrUpdated); + return; + } + + if (keyBinding) + { + if (keyBinding[0] == '+') + { + sprintf_s(cmd, "%s %i %i\n", keyBinding, key, time); + Game::Cbuf_AddText(gamePadIndex, cmd); + } + else + { + Game::Cbuf_InsertText(gamePadIndex, keyBinding); + } + } + } + else + { + if (keyBinding && keyBinding[0] == '+') + { + sprintf_s(cmd, "-%s %i %i\n", &keyBinding[1], key, time); + Game::Cbuf_AddText(gamePadIndex, cmd); + } + + if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) + { + UI_GamepadKeyEvent(gamePadIndex, key, pressedOrUpdated); + } + } + } + + void Gamepad::CL_GamepadButtonEventForPort(const int gamePadIndex, const int key, const Game::GamePadButtonEvent buttonEvent, const unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + gamePad.inUse = true; + gpad_in_use.setRaw(true); + + if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) + CL_GamepadResetMenuScrollTime(gamePadIndex, key, buttonEvent == Game::GPAD_BUTTON_PRESSED, time); + + + CL_GamepadButtonEvent(gamePadIndex, key, buttonEvent, time); + } + + void Gamepad::GPad_ConvertStickToFloat(const short x, const short y, float& outX, float& outY) + { + if(x == 0 && y == 0) + { + outX = 0.0f; + outY = 0.0f; + return; + } + + Game::vec2_t stickVec; + stickVec[0] = static_cast(x) / static_cast(std::numeric_limits::max()); + stickVec[1] = static_cast(y) / static_cast(std::numeric_limits::max()); + + const auto deadZoneTotal = gpad_stick_deadzone_min.get() + gpad_stick_deadzone_max.get(); + auto len = Game::Vec2Normalize(stickVec); + + if (gpad_stick_deadzone_min.get() <= len) + { + if (1.0f - gpad_stick_deadzone_max.get() >= len) + len = (len - gpad_stick_deadzone_min.get()) / (1.0f - deadZoneTotal); + else + len = 1.0f; + } + else + len = 0.0f; + + outX = stickVec[0] * len; + outY = stickVec[1] * len; + } + + float Gamepad::GPad_GetStick(const int gamePadIndex, const Game::GamePadStick stick) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + return gamePad.sticks[stick]; + } + + float Gamepad::GPad_GetButton(const int gamePadIndex, Game::GamePadButton button) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + float value = 0.0f; + + if (button & Game::GPAD_DIGITAL_MASK) + { + const auto buttonValue = button & Game::GPAD_VALUE_MASK; + value = buttonValue & gamePad.digitals ? 1.0f : 0.0f; + } + else if (button & Game::GPAD_ANALOG_MASK) + { + const auto analogIndex = button & Game::GPAD_VALUE_MASK; + if (analogIndex < std::extent_v) + { + value = gamePad.analogs[analogIndex]; + } + } + + return value; + } + + bool Gamepad::GPad_IsButtonPressed(const int gamePadIndex, Game::GamePadButton button) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + bool down = false; + bool lastDown = false; + + if (button & Game::GPAD_DIGITAL_MASK) + { + const auto buttonValue = button & Game::GPAD_VALUE_MASK; + down = (buttonValue & gamePad.digitals) != 0; + lastDown = (buttonValue & gamePad.lastDigitals) != 0; + } + else if (button & Game::GPAD_ANALOG_MASK) + { + const auto analogIndex = button & Game::GPAD_VALUE_MASK; + assert(analogIndex < std::extent_v); + + if (analogIndex < std::extent_v) + { + down = gamePad.analogs[analogIndex] > 0.0f; + lastDown = gamePad.lastAnalogs[analogIndex] > 0.0f; + } + } + + return down && !lastDown; + } + + bool Gamepad::GPad_ButtonRequiresUpdates(const int gamePadIndex, Game::GamePadButton button) + { + return (button & Game::GPAD_ANALOG_MASK || button & Game::GPAD_DPAD_MASK) && GPad_GetButton(gamePadIndex, button) > 0.0f; + } + + bool Gamepad::GPad_IsButtonReleased(int gamePadIndex, Game::GamePadButton button) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + bool down = false; + bool lastDown = false; + + if (button & Game::GPAD_DIGITAL_MASK) + { + const auto buttonValue = button & Game::GPAD_VALUE_MASK; + + down = (gamePad.digitals & buttonValue) != 0; + lastDown = (gamePad.lastDigitals & buttonValue) != 0; + } + else if (button & Game::GPAD_ANALOG_MASK) + { + const auto analogIndex = button & Game::GPAD_VALUE_MASK; + assert(analogIndex < std::extent_v); + + if (analogIndex < std::extent_v) + { + down = gamePad.analogs[analogIndex] > 0.0f; + lastDown = gamePad.lastAnalogs[analogIndex] > 0.0f; + } + } + + return !down && lastDown; + } + + void Gamepad::GPad_UpdateSticksDown(const int gamePadIndex) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + for (auto stickIndex = 0u; stickIndex < std::extent_v; stickIndex++) + { + for (auto dir = 0; dir < Game::GPAD_STICK_DIR_COUNT; dir++) + { + gamePad.stickDownLast[stickIndex][dir] = gamePad.stickDown[stickIndex][dir]; + + auto threshold = gpad_stick_pressed.get(); + + if (gamePad.stickDownLast[stickIndex][dir]) + threshold -= gpad_stick_pressed_hysteresis.get(); + else + threshold += gpad_stick_pressed_hysteresis.get(); + + if (dir == Game::GPAD_STICK_POS) + { + gamePad.stickDown[stickIndex][dir] = gamePad.sticks[stickIndex] > threshold; + } + else + { + assert(dir == Game::GPAD_STICK_NEG); + gamePad.stickDown[stickIndex][dir] = gamePad.sticks[stickIndex] < -threshold; + } + } + } + } + + void Gamepad::GPad_UpdateSticks(const int gamePadIndex, const XINPUT_GAMEPAD& state) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + + auto& gamePad = gamePads[gamePadIndex]; + + Game::vec2_t lVec, rVec; + GPad_ConvertStickToFloat(state.sThumbLX, state.sThumbLY, lVec[0], lVec[1]); + GPad_ConvertStickToFloat(state.sThumbRX, state.sThumbRY, rVec[0], rVec[1]); + + gamePad.lastSticks[0] = gamePad.sticks[0]; + gamePad.sticks[0] = lVec[0]; + gamePad.lastSticks[1] = gamePad.sticks[1]; + gamePad.sticks[1] = lVec[1]; + gamePad.lastSticks[2] = gamePad.sticks[2]; + gamePad.sticks[2] = rVec[0]; + gamePad.lastSticks[3] = gamePad.sticks[3]; + gamePad.sticks[3] = rVec[1]; + + GPad_UpdateSticksDown(gamePadIndex); #ifdef DEBUG - if (gpad_debug.get()) - { - Logger::Print("Left: X: %f Y: %f\n", lVec[0], lVec[1]); - Logger::Print("Right: X: %f Y: %f\n", rVec[0], rVec[1]); - Logger::Print("Down: %i:%i %i:%i %i:%i %i:%i\n", gamePad.stickDown[0][Game::GPAD_STICK_POS], gamePad.stickDown[0][Game::GPAD_STICK_NEG], - gamePad.stickDown[1][Game::GPAD_STICK_POS], gamePad.stickDown[1][Game::GPAD_STICK_NEG], - gamePad.stickDown[2][Game::GPAD_STICK_POS], gamePad.stickDown[2][Game::GPAD_STICK_NEG], - gamePad.stickDown[3][Game::GPAD_STICK_POS], gamePad.stickDown[3][Game::GPAD_STICK_NEG]); - } + if (gpad_debug.get()) + { + Logger::Print("Left: X: %f Y: %f\n", lVec[0], lVec[1]); + Logger::Print("Right: X: %f Y: %f\n", rVec[0], rVec[1]); + Logger::Print("Down: %i:%i %i:%i %i:%i %i:%i\n", gamePad.stickDown[0][Game::GPAD_STICK_POS], gamePad.stickDown[0][Game::GPAD_STICK_NEG], + gamePad.stickDown[1][Game::GPAD_STICK_POS], gamePad.stickDown[1][Game::GPAD_STICK_NEG], + gamePad.stickDown[2][Game::GPAD_STICK_POS], gamePad.stickDown[2][Game::GPAD_STICK_NEG], + gamePad.stickDown[3][Game::GPAD_STICK_POS], gamePad.stickDown[3][Game::GPAD_STICK_NEG]); + } #endif - } + } - void Gamepad::GPad_UpdateDigitals(const int gamePadIndex, const XINPUT_GAMEPAD& state) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); + void Gamepad::GPad_UpdateDigitals(const int gamePadIndex, const XINPUT_GAMEPAD& state) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePad = gamePads[gamePadIndex]; + auto& gamePad = gamePads[gamePadIndex]; - gamePad.lastDigitals = gamePad.digitals; - gamePad.digitals = state.wButtons; + gamePad.lastDigitals = gamePad.digitals; + gamePad.digitals = state.wButtons; - const auto leftDeflect = gpad_button_lstick_deflect_max.get(); - if (std::fabs(gamePad.sticks[0]) > leftDeflect || std::fabs(gamePad.sticks[1]) > leftDeflect) - gamePad.digitals &= ~static_cast(XINPUT_GAMEPAD_LEFT_THUMB); - const auto rightDeflect = gpad_button_rstick_deflect_max.get(); - if (std::fabs(gamePad.sticks[2]) > leftDeflect || std::fabs(gamePad.sticks[3]) > rightDeflect) - gamePad.digitals &= ~static_cast(XINPUT_GAMEPAD_RIGHT_THUMB); + const auto leftDeflect = gpad_button_lstick_deflect_max.get(); + if (std::fabs(gamePad.sticks[0]) > leftDeflect || std::fabs(gamePad.sticks[1]) > leftDeflect) + gamePad.digitals &= ~static_cast(XINPUT_GAMEPAD_LEFT_THUMB); + const auto rightDeflect = gpad_button_rstick_deflect_max.get(); + if (std::fabs(gamePad.sticks[2]) > leftDeflect || std::fabs(gamePad.sticks[3]) > rightDeflect) + gamePad.digitals &= ~static_cast(XINPUT_GAMEPAD_RIGHT_THUMB); #ifdef DEBUG - if (gpad_debug.get()) - { - Logger::Print("Buttons: %x\n", gamePad.digitals); - } + if (gpad_debug.get()) + { + Logger::Print("Buttons: %x\n", gamePad.digitals); + } #endif - } + } - void Gamepad::GPad_UpdateAnalogs(const int gamePadIndex, const XINPUT_GAMEPAD& state) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); + void Gamepad::GPad_UpdateAnalogs(const int gamePadIndex, const XINPUT_GAMEPAD& state) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePad = gamePads[gamePadIndex]; + auto& gamePad = gamePads[gamePadIndex]; - const auto buttonDeadZone = gpad_button_deadzone.get(); + const auto buttonDeadZone = gpad_button_deadzone.get(); - gamePad.lastAnalogs[0] = gamePad.analogs[0]; - gamePad.analogs[0] = static_cast(state.bLeftTrigger) / static_cast(std::numeric_limits::max()); - if (gamePad.analogs[0] < buttonDeadZone) - gamePad.analogs[0] = 0.0f; + gamePad.lastAnalogs[0] = gamePad.analogs[0]; + gamePad.analogs[0] = static_cast(state.bLeftTrigger) / static_cast(std::numeric_limits::max()); + if (gamePad.analogs[0] < buttonDeadZone) + gamePad.analogs[0] = 0.0f; - gamePad.lastAnalogs[1] = gamePad.analogs[1]; - gamePad.analogs[1] = static_cast(state.bRightTrigger) / static_cast(std::numeric_limits::max()); - if (gamePad.analogs[1] < buttonDeadZone) - gamePad.analogs[1] = 0.0f; + gamePad.lastAnalogs[1] = gamePad.analogs[1]; + gamePad.analogs[1] = static_cast(state.bRightTrigger) / static_cast(std::numeric_limits::max()); + if (gamePad.analogs[1] < buttonDeadZone) + gamePad.analogs[1] = 0.0f; #ifdef DEBUG - if (gpad_debug.get()) - { - Logger::Print("Triggers: %f %f\n", gamePad.analogs[0], gamePad.analogs[1]); - } + if (gpad_debug.get()) + { + Logger::Print("Triggers: %f %f\n", gamePad.analogs[0], gamePad.analogs[1]); + } #endif - } - - void Gamepad::GPad_UpdateAll() - { - GPad_RefreshAll(); - - for (auto currentGamePadIndex = 0; currentGamePadIndex < Game::MAX_GAMEPADS; currentGamePadIndex++) - { - const auto& gamePad = gamePads[currentGamePadIndex]; - if (!gamePad.enabled) - continue; - - XINPUT_STATE inputState; - if (XInputGetState(gamePad.portIndex, &inputState) != ERROR_SUCCESS) - continue; - - GPad_UpdateSticks(currentGamePadIndex, inputState.Gamepad); - GPad_UpdateDigitals(currentGamePadIndex, inputState.Gamepad); - GPad_UpdateAnalogs(currentGamePadIndex, inputState.Gamepad); - } - } - - void Gamepad::IN_GamePadsMove() - { - if (!gpad_enabled.get()) - return; - - GPad_UpdateAll(); - const auto time = Game::Sys_Milliseconds(); - - bool gpadPresent = false; - for (auto gamePadIndex = 0; gamePadIndex < Game::MAX_GAMEPADS; gamePadIndex++) - { - const auto& gamePad = gamePads[gamePadIndex]; - - if (gamePad.enabled) - { - gpadPresent = true; - const auto lx = GPad_GetStick(gamePadIndex, Game::GPAD_LX); - const auto ly = GPad_GetStick(gamePadIndex, Game::GPAD_LY); - const auto rx = GPad_GetStick(gamePadIndex, Game::GPAD_RX); - const auto ry = GPad_GetStick(gamePadIndex, Game::GPAD_RY); - const auto leftTrig = GPad_GetButton(gamePadIndex, Game::GPAD_L_TRIG); - const auto rightTrig = GPad_GetButton(gamePadIndex, Game::GPAD_R_TRIG); - - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LSTICK_X, lx, time); - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LSTICK_Y, ly, time); - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RSTICK_X, rx, time); - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RSTICK_Y, ry, time); - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LTRIGGER, leftTrig, time); - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RTRIGGER, rightTrig, time); - - for (const auto& buttonMapping : buttonList) - { - if (GPad_IsButtonPressed(gamePadIndex, buttonMapping.padButton)) - { - CL_GamepadButtonEventForPort( - gamePadIndex, - buttonMapping.code, - Game::GPAD_BUTTON_PRESSED, - time); - } - else if (GPad_ButtonRequiresUpdates(gamePadIndex, buttonMapping.padButton)) - { - CL_GamepadButtonEventForPort( - gamePadIndex, - buttonMapping.code, - Game::GPAD_BUTTON_UPDATE, - time); - } - else if (GPad_IsButtonReleased(gamePadIndex, buttonMapping.padButton)) - { - CL_GamepadButtonEventForPort( - gamePadIndex, - buttonMapping.code, - Game::GPAD_BUTTON_RELEASED, - time); - } - } - } - } - - gpad_present.setRaw(gpadPresent); - } - - - void Gamepad::IN_Frame_Hk() - { - // Call original method - Utils::Hook::Call(0x64C490)(); - - IN_GamePadsMove(); - } - - void Gamepad::Gamepad_WriteBindings(const int gamePadIndex, const int handle) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; - - Game::FS_Printf(handle, "unbindallaxis\n"); - - for (auto virtualAxisIndex = 0u; virtualAxisIndex < Game::GPAD_VIRTAXIS_COUNT; virtualAxisIndex++) - { - const auto& axisMapping = gamePadGlobal.axes.virtualAxes[virtualAxisIndex]; - if (axisMapping.physicalAxis <= Game::GPAD_PHYSAXIS_NONE || axisMapping.physicalAxis >= Game::GPAD_PHYSAXIS_COUNT - || axisMapping.mapType <= Game::GPAD_MAP_NONE || axisMapping.mapType >= Game::GPAD_MAP_COUNT) - { - continue; - } - - const auto* physicalAxisName = physicalAxisNames[axisMapping.physicalAxis]; - const auto* virtualAxisName = virtualAxisNames[virtualAxisIndex]; - const auto* mappingName = gamePadMappingTypeNames[axisMapping.mapType]; - - Game::FS_Printf(handle, "bindaxis %s %s %s\n", physicalAxisName, virtualAxisName, mappingName); - } - } - - void Gamepad::Key_WriteBindings_Hk(const int localClientNum, const int handle) - { - // Call original function - Utils::Hook::Call(0x4A5A20)(localClientNum, handle); - - Gamepad_WriteBindings(0, handle); - } - - void __declspec(naked) Gamepad::Com_WriteConfiguration_Modified_Stub() - { - __asm - { - mov eax, [ecx + 0x18] - or eax, gamePadBindingsModifiedFlags // Also check for gamePadBindingsModifiedFlags - test al, 1 - jz endMethod - mov gamePadBindingsModifiedFlags, 0 // Reset gamePadBindingsModifiedFlags - mov eax, [ecx + 0x18] // Restore eax to dvar_modified_flags - - push 0x60B26E - retn - - endMethod: - push 0x60B298 - retn - } - } - - void Gamepad::Gamepad_BindAxis(const int gamePadIndex, const Game::GamepadPhysicalAxis realIndex, const Game::GamepadVirtualAxis axisIndex, const Game::GamepadMapping mapType) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - assert(realIndex > Game::GPAD_PHYSAXIS_NONE && realIndex < Game::GPAD_PHYSAXIS_COUNT); - assert(axisIndex > Game::GPAD_VIRTAXIS_NONE && axisIndex < Game::GPAD_VIRTAXIS_COUNT); - assert(mapType > Game::GPAD_MAP_NONE && mapType < Game::GPAD_MAP_COUNT); - - auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; - gamePadGlobal.axes.virtualAxes[axisIndex].physicalAxis = realIndex; - gamePadGlobal.axes.virtualAxes[axisIndex].mapType = mapType; - - gamePadBindingsModifiedFlags |= 1; - } - - Game::GamepadPhysicalAxis Gamepad::StringToPhysicalAxis(const char* str) - { - for (auto i = 0u; i < std::extent_v; i++) - { - if (strcmp(str, physicalAxisNames[i]) == 0) - return static_cast(i); - } - - return Game::GPAD_PHYSAXIS_NONE; - } - - Game::GamepadVirtualAxis Gamepad::StringToVirtualAxis(const char* str) - { - for (auto i = 0u; i < std::extent_v; i++) - { - if (strcmp(str, virtualAxisNames[i]) == 0) - return static_cast(i); - } - - return Game::GPAD_VIRTAXIS_NONE; - } - - Game::GamepadMapping Gamepad::StringToGamePadMapping(const char* str) - { - for (auto i = 0u; i < std::extent_v; i++) - { - if (strcmp(str, gamePadMappingTypeNames[i]) == 0) - return static_cast(i); - } - - return Game::GPAD_MAP_NONE; - } - - void Gamepad::Axis_Bind_f(Command::Params* params) - { - if (params->size() < 4) - { - Logger::Print("bindaxis \n"); - return; - } - - const auto* physicalAxisText = params->get(1); - const auto* virtualAxisText = params->get(2); - const auto* mappingText = params->get(3); - - const Game::GamepadPhysicalAxis physicalAxis = StringToPhysicalAxis(physicalAxisText); - if (physicalAxis == Game::GPAD_PHYSAXIS_NONE) - { - Logger::Print("\"%s\" isn't a valid physical axis\n", physicalAxisText); - return; - } - - const Game::GamepadVirtualAxis virtualAxis = StringToVirtualAxis(virtualAxisText); - if (virtualAxis == Game::GPAD_VIRTAXIS_NONE) - { - Logger::Print("\"%s\" isn't a valid virtual axis\n", virtualAxisText); - return; - } - - const Game::GamepadMapping mapping = StringToGamePadMapping(mappingText); - if (mapping == Game::GPAD_MAP_NONE) - { - Logger::Print("\"%s\" isn't a valid input type\n", mappingText); - return; - } - - Gamepad_BindAxis(0, physicalAxis, virtualAxis, mapping); - } - - void Gamepad::Axis_Unbindall_f(Command::Params*) - { - auto& gamePadGlobal = gamePadGlobals[0]; - - for (auto& virtualAxis : gamePadGlobal.axes.virtualAxes) - { - virtualAxis.physicalAxis = Game::GPAD_PHYSAXIS_NONE; - virtualAxis.mapType = Game::GPAD_MAP_NONE; - } - } - - void Gamepad::Bind_GP_SticksConfigs_f(Command::Params*) - { - const auto* stickConfigName = gpad_sticksConfig.get(); - Game::Cbuf_AddText(0, Utils::String::VA("exec %s\n", stickConfigName)); - } - - void Gamepad::Bind_GP_ButtonsConfigs_f(Command::Params*) - { - const auto* buttonConfigName = gpad_buttonConfig.get(); - Game::Cbuf_AddText(0, Utils::String::VA("exec %s\n", buttonConfigName)); - } - - void Gamepad::Scores_Toggle_f(Command::Params*) - { - if(Game::cgArray[0].nextSnap) - { - if (Game::UI_GetActiveMenu(0) != Game::UIMENU_SCOREBOARD) - Game::CG_ScoresDown_f(); - else - Game::CG_ScoresUp_f(); - } - } - - void Gamepad::InitDvars() - { - gpad_enabled = Dvar::Register("gpad_enabled", false, Game::DVAR_ARCHIVE, "Game pad enabled"); - gpad_debug = Dvar::Register("gpad_debug", false, Game::DVAR_NONE, "Game pad debugging"); - gpad_present = Dvar::Register("gpad_present", false, Game::DVAR_NONE, "Game pad present"); - gpad_in_use = Dvar::Register("gpad_in_use", false, Game::DVAR_NONE, "A game pad is in use"); - gpad_style = Dvar::Register("gpad_style", false, Game::DVAR_ARCHIVE, "Switch between Xbox and PS HUD"); - gpad_sticksConfig = Dvar::Register("gpad_sticksConfig", "", Game::DVAR_ARCHIVE, "Game pad stick configuration"); - gpad_buttonConfig = Dvar::Register("gpad_buttonConfig", "", Game::DVAR_ARCHIVE, "Game pad button configuration"); - gpad_menu_scroll_delay_first = Dvar::Register("gpad_menu_scroll_delay_first", 420, 0, 1000, Game::DVAR_ARCHIVE, "Menu scroll key-repeat delay, for the first repeat, in milliseconds"); - gpad_menu_scroll_delay_rest = Dvar::Register("gpad_menu_scroll_delay_rest", 210, 0, 1000, Game::DVAR_ARCHIVE, - "Menu scroll key-repeat delay, for repeats after the first, in milliseconds"); - gpad_rumble = Dvar::Register("gpad_rumble", true, Game::DVAR_ARCHIVE, "Enable game pad rumble"); - gpad_stick_pressed_hysteresis = Dvar::Register("gpad_stick_pressed_hysteresis", 0.1f, 0.0f, 1.0f, Game::DVAR_NONE, - "Game pad stick pressed no-change-zone around gpad_stick_pressed to prevent bouncing"); - gpad_stick_pressed = Dvar::Register("gpad_stick_pressed", 0.4f, 0.0, 1.0, Game::DVAR_NONE, "Game pad stick pressed threshhold"); - gpad_stick_deadzone_max = Dvar::Register("gpad_stick_deadzone_max", 0.01f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad maximum stick deadzone"); - gpad_stick_deadzone_min = Dvar::Register("gpad_stick_deadzone_min", 0.2f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad minimum stick deadzone"); - gpad_button_deadzone = Dvar::Register("gpad_button_deadzone", 0.13f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad button deadzone threshhold"); - gpad_button_lstick_deflect_max = Dvar::Register("gpad_button_lstick_deflect_max", 1.0f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad maximum pad stick pressed value"); - gpad_button_rstick_deflect_max = Dvar::Register("gpad_button_rstick_deflect_max", 1.0f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad maximum pad stick pressed value"); - gpad_use_hold_time = Dvar::Register("gpad_use_hold_time", 250, 0, std::numeric_limits::max(), Game::DVAR_NONE, "Time to hold the 'use' button on gamepads to activate use"); - gpad_lockon_enabled = Dvar::Register("gpad_lockon_enabled", true, Game::DVAR_ARCHIVE, "Game pad lockon aim assist enabled"); - gpad_slowdown_enabled = Dvar::Register("gpad_slowdown_enabled", true, Game::DVAR_ARCHIVE, "Game pad slowdown aim assist enabled"); - - input_viewSensitivity = Dvar::Register("input_viewSensitivity", 1.0f, 0.0001f, 5.0f, Game::DVAR_ARCHIVE, "View Sensitivity"); - input_invertPitch = Dvar::Register("input_invertPitch", false, Game::DVAR_ARCHIVE, "Invert gamepad pitch"); - sv_allowAimAssist = Dvar::Register("sv_allowAimAssist", true, Game::DVAR_NONE, "Controls whether aim assist features on clients are enabled"); - aim_turnrate_pitch = Dvar::Var("aim_turnrate_pitch"); - aim_turnrate_pitch_ads = Dvar::Var("aim_turnrate_pitch_ads"); - aim_turnrate_yaw = Dvar::Var("aim_turnrate_yaw"); - aim_turnrate_yaw_ads = Dvar::Var("aim_turnrate_yaw_ads"); - aim_accel_turnrate_enabled = Dvar::Var("aim_accel_turnrate_enabled"); - aim_accel_turnrate_lerp = Dvar::Var("aim_accel_turnrate_lerp"); - aim_input_graph_enabled = Dvar::Var("aim_input_graph_enabled"); - aim_input_graph_index = Dvar::Var("aim_input_graph_index"); - aim_scale_view_axis = Dvar::Var("aim_scale_view_axis"); - cl_bypassMouseInput = Dvar::Var("cl_bypassMouseInput"); - cg_mapLocationSelectionCursorSpeed = Dvar::Var("cg_mapLocationSelectionCursorSpeed"); - aim_aimAssistRangeScale = Dvar::Var("aim_aimAssistRangeScale"); - aim_slowdown_enabled = Dvar::Var("aim_slowdown_enabled"); - aim_slowdown_debug = Dvar::Var("aim_slowdown_debug"); - aim_slowdown_pitch_scale = Dvar::Var("aim_slowdown_pitch_scale"); - aim_slowdown_pitch_scale_ads = Dvar::Var("aim_slowdown_pitch_scale_ads"); - aim_slowdown_yaw_scale = Dvar::Var("aim_slowdown_yaw_scale"); - aim_slowdown_yaw_scale_ads = Dvar::Var("aim_slowdown_yaw_scale_ads"); - aim_lockon_enabled = Dvar::Var("aim_lockon_enabled"); - aim_lockon_deflection = Dvar::Var("aim_lockon_deflection"); - aim_lockon_pitch_strength = Dvar::Var("aim_lockon_pitch_strength"); - aim_lockon_strength = Dvar::Var("aim_lockon_strength"); - } - - void Gamepad::CG_RegisterDvars_Hk() - { - // Call original method - Utils::Hook::Call(0x4F8DC0)(); - - InitDvars(); - } - - const char* Gamepad::GetGamePadCommand(const char* command) - { - if (strcmp(command, "+activate") == 0 || strcmp(command, "+reload") == 0) - return "+usereload"; - if (strcmp(command, "+melee_breath") == 0) - return "+holdbreath"; - - return command; - } - - int Gamepad::Key_GetCommandAssignmentInternal_Hk(const char* cmd, int (*keys)[2]) - { - auto keyCount = 0; - - if (gamePads[0].inUse) - { - cmd = GetGamePadCommand(cmd); - for (auto keyNum = 0; keyNum < Game::K_LAST_KEY; keyNum++) - { - if (!Key_IsValidGamePadChar(keyNum)) - continue; - - if (Game::playerKeys[0].keys[keyNum].binding && strcmp(Game::playerKeys[0].keys[keyNum].binding, cmd) == 0) - { - (*keys)[keyCount++] = keyNum; - - if (keyCount >= 2) - return keyCount; - } - } - } - else - { - for (auto keyNum = 0; keyNum < Game::K_LAST_KEY; keyNum++) - { - if (Key_IsValidGamePadChar(keyNum)) - continue; - - if (Game::playerKeys[0].keys[keyNum].binding && strcmp(Game::playerKeys[0].keys[keyNum].binding, cmd) == 0) - { - (*keys)[keyCount++] = keyNum; - - if (keyCount >= 2) - return keyCount; - } - } - } - - return keyCount; - } - - void Gamepad::CL_KeyEvent_Hk(const int localClientNum, const int key, const int down, const unsigned time) - { - // A keyboard key has been pressed. Mark controller as unused. - gamePads[0].inUse = false; - gpad_in_use.setRaw(false); - - // Call original function - Utils::Hook::Call(0x4F6480)(localClientNum, key, down, time); - } - - bool Gamepad::IsGamePadInUse() - { - return gamePads[0].inUse; - } - - int Gamepad::CL_MouseEvent_Hk(const int x, const int y, const int dx, const int dy) - { - if (dx != 0 || dy != 0) - { - gamePads[0].inUse = false; - gpad_in_use.setRaw(false); - } - - // Call original function - return Utils::Hook::Call(0x4D7C50)(x, y, dx, dy); - } - - bool Gamepad::UI_RefreshViewport_Hk() - { - return cl_bypassMouseInput.get() || IsGamePadInUse(); - } - - Game::keyname_t* Gamepad::GetLocalizedKeyNameMap() - { - if(gpad_style.get()) - return combinedLocalizedKeyNamesPs3; - - return combinedLocalizedKeyNamesXenon; - } - - void __declspec(naked) Gamepad::GetLocalizedKeyName_Stub() - { - __asm - { - push eax - pushad - - call GetLocalizedKeyNameMap - mov [esp + 0x20], eax - - popad - pop eax - - // Re-execute last instruction from game to set flags again for upcoming jump - test edi, edi - ret - } - } - - void Gamepad::CreateKeyNameMap() - { - memcpy(combinedKeyNames, Game::keyNames, sizeof(Game::keyname_t) * Game::KEY_NAME_COUNT); - memcpy(&combinedKeyNames[Game::KEY_NAME_COUNT], extendedKeyNames, sizeof(Game::keyname_t) * std::extent_v); - combinedKeyNames[std::extent_v - 1] = {nullptr, 0}; - - memcpy(combinedLocalizedKeyNamesXenon, Game::localizedKeyNames, sizeof(Game::keyname_t) * Game::LOCALIZED_KEY_NAME_COUNT); - memcpy(&combinedLocalizedKeyNamesXenon[Game::LOCALIZED_KEY_NAME_COUNT], extendedLocalizedKeyNamesXenon, - sizeof(Game::keyname_t) * std::extent_v); - combinedLocalizedKeyNamesXenon[std::extent_v - 1] = {nullptr, 0}; - - memcpy(combinedLocalizedKeyNamesPs3, Game::localizedKeyNames, sizeof(Game::keyname_t) * Game::LOCALIZED_KEY_NAME_COUNT); - memcpy(&combinedLocalizedKeyNamesPs3[Game::LOCALIZED_KEY_NAME_COUNT], extendedLocalizedKeyNamesPs3, - sizeof(Game::keyname_t) * std::extent_v); - combinedLocalizedKeyNamesPs3[std::extent_v - 1] = {nullptr, 0}; - - Utils::Hook::Set(0x4A780A, combinedKeyNames); - Utils::Hook::Set(0x4A7810, combinedKeyNames); - Utils::Hook::Set(0x435C9F, combinedKeyNames); - Utils::Hook(0x435C97, GetLocalizedKeyName_Stub, HOOK_CALL).install()->quick(); - } - - Gamepad::Gamepad() - { - if (ZoneBuilder::IsEnabled()) - return; - - // Initialize gamepad environment - Utils::Hook(0x4059FE, CG_RegisterDvars_Hk, HOOK_CALL).install()->quick(); - - // package the forward and right move components in the move buttons - Utils::Hook(0x60E38D, MSG_WriteDeltaUsercmdKeyStub, HOOK_JUMP).install()->quick(); - - // send two bytes for sending movement data - Utils::Hook::Set(0x60E501, 16); - Utils::Hook::Set(0x60E5CD, 16); - - // make sure to parse the movement data properly and apply it - Utils::Hook(0x492127, MSG_ReadDeltaUsercmdKeyStub, HOOK_JUMP).install()->quick(); - Utils::Hook(0x492009, MSG_ReadDeltaUsercmdKeyStub2, HOOK_JUMP).install()->quick(); - - // Also rewrite configuration when gamepad config is dirty - Utils::Hook(0x60B264, Com_WriteConfiguration_Modified_Stub, HOOK_JUMP).install()->quick(); - Utils::Hook(0x60B223, Key_WriteBindings_Hk, HOOK_CALL).install()->quick(); - - // Add hold time to gamepad usereload on hold prompts - Utils::Hook(0x5FE396, Player_UseEntity_Stub, HOOK_JUMP).install()->quick(); - - CreateKeyNameMap(); - - Command::Add("bindaxis", Axis_Bind_f); - Command::Add("unbindallaxis", Axis_Unbindall_f); - Command::Add("bindgpsticksconfigs", Bind_GP_SticksConfigs_f); - Command::Add("bindgpbuttonsconfigs", Bind_GP_ButtonsConfigs_f); - Command::Add("togglescores", Scores_Toggle_f); - - if (Dedicated::IsEnabled()) - return; - - // Gamepad on frame hook - Utils::Hook(0x475E9E, IN_Frame_Hk, HOOK_CALL).install()->quick(); + } + + void Gamepad::GPad_UpdateAll() + { + GPad_RefreshAll(); + + for (auto currentGamePadIndex = 0; currentGamePadIndex < Game::MAX_GAMEPADS; currentGamePadIndex++) + { + const auto& gamePad = gamePads[currentGamePadIndex]; + if (!gamePad.enabled) + continue; + + XINPUT_STATE inputState; + if (XInputGetState(gamePad.portIndex, &inputState) != ERROR_SUCCESS) + continue; + + GPad_UpdateSticks(currentGamePadIndex, inputState.Gamepad); + GPad_UpdateDigitals(currentGamePadIndex, inputState.Gamepad); + GPad_UpdateAnalogs(currentGamePadIndex, inputState.Gamepad); + } + } + + void Gamepad::IN_GamePadsMove() + { + if (!gpad_enabled.get()) + return; + + GPad_UpdateAll(); + const auto time = Game::Sys_Milliseconds(); + + bool gpadPresent = false; + for (auto gamePadIndex = 0; gamePadIndex < Game::MAX_GAMEPADS; gamePadIndex++) + { + const auto& gamePad = gamePads[gamePadIndex]; + + if (gamePad.enabled) + { + gpadPresent = true; + const auto lx = GPad_GetStick(gamePadIndex, Game::GPAD_LX); + const auto ly = GPad_GetStick(gamePadIndex, Game::GPAD_LY); + const auto rx = GPad_GetStick(gamePadIndex, Game::GPAD_RX); + const auto ry = GPad_GetStick(gamePadIndex, Game::GPAD_RY); + const auto leftTrig = GPad_GetButton(gamePadIndex, Game::GPAD_L_TRIG); + const auto rightTrig = GPad_GetButton(gamePadIndex, Game::GPAD_R_TRIG); + + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LSTICK_X, lx, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LSTICK_Y, ly, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RSTICK_X, rx, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RSTICK_Y, ry, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LTRIGGER, leftTrig, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RTRIGGER, rightTrig, time); + + for (const auto& buttonMapping : buttonList) + { + if (GPad_IsButtonPressed(gamePadIndex, buttonMapping.padButton)) + { + CL_GamepadButtonEventForPort( + gamePadIndex, + buttonMapping.code, + Game::GPAD_BUTTON_PRESSED, + time); + } + else if (GPad_ButtonRequiresUpdates(gamePadIndex, buttonMapping.padButton)) + { + CL_GamepadButtonEventForPort( + gamePadIndex, + buttonMapping.code, + Game::GPAD_BUTTON_UPDATE, + time); + } + else if (GPad_IsButtonReleased(gamePadIndex, buttonMapping.padButton)) + { + CL_GamepadButtonEventForPort( + gamePadIndex, + buttonMapping.code, + Game::GPAD_BUTTON_RELEASED, + time); + } + } + } + } + + gpad_present.setRaw(gpadPresent); + } + + void Gamepad::IN_Frame_Hk() + { + RawMouse::IN_MouseMove(); + + IN_GamePadsMove(); + } + + void Gamepad::Gamepad_WriteBindings(const int gamePadIndex, const int handle) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + + Game::FS_Printf(handle, "unbindallaxis\n"); + + for (auto virtualAxisIndex = 0u; virtualAxisIndex < Game::GPAD_VIRTAXIS_COUNT; virtualAxisIndex++) + { + const auto& axisMapping = gamePadGlobal.axes.virtualAxes[virtualAxisIndex]; + if (axisMapping.physicalAxis <= Game::GPAD_PHYSAXIS_NONE || axisMapping.physicalAxis >= Game::GPAD_PHYSAXIS_COUNT + || axisMapping.mapType <= Game::GPAD_MAP_NONE || axisMapping.mapType >= Game::GPAD_MAP_COUNT) + { + continue; + } + + const auto* physicalAxisName = physicalAxisNames[axisMapping.physicalAxis]; + const auto* virtualAxisName = virtualAxisNames[virtualAxisIndex]; + const auto* mappingName = gamePadMappingTypeNames[axisMapping.mapType]; + + Game::FS_Printf(handle, "bindaxis %s %s %s\n", physicalAxisName, virtualAxisName, mappingName); + } + } + + void Gamepad::Key_WriteBindings_Hk(const int localClientNum, const int handle) + { + // Call original function + Utils::Hook::Call(0x4A5A20)(localClientNum, handle); + + Gamepad_WriteBindings(0, handle); + } + + void __declspec(naked) Gamepad::Com_WriteConfiguration_Modified_Stub() + { + __asm + { + mov eax, [ecx + 0x18] + or eax, gamePadBindingsModifiedFlags // Also check for gamePadBindingsModifiedFlags + test al, 1 + jz endMethod + mov gamePadBindingsModifiedFlags, 0 // Reset gamePadBindingsModifiedFlags + mov eax, [ecx + 0x18] // Restore eax to dvar_modified_flags + + push 0x60B26E + retn + + endMethod: + push 0x60B298 + retn + } + } + + void Gamepad::Gamepad_BindAxis(const int gamePadIndex, const Game::GamepadPhysicalAxis realIndex, const Game::GamepadVirtualAxis axisIndex, const Game::GamepadMapping mapType) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + assert(realIndex > Game::GPAD_PHYSAXIS_NONE && realIndex < Game::GPAD_PHYSAXIS_COUNT); + assert(axisIndex > Game::GPAD_VIRTAXIS_NONE && axisIndex < Game::GPAD_VIRTAXIS_COUNT); + assert(mapType > Game::GPAD_MAP_NONE && mapType < Game::GPAD_MAP_COUNT); + + auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + gamePadGlobal.axes.virtualAxes[axisIndex].physicalAxis = realIndex; + gamePadGlobal.axes.virtualAxes[axisIndex].mapType = mapType; + + gamePadBindingsModifiedFlags |= 1; + } + + Game::GamepadPhysicalAxis Gamepad::StringToPhysicalAxis(const char* str) + { + for (auto i = 0u; i < std::extent_v; i++) + { + if (strcmp(str, physicalAxisNames[i]) == 0) + return static_cast(i); + } + + return Game::GPAD_PHYSAXIS_NONE; + } + + Game::GamepadVirtualAxis Gamepad::StringToVirtualAxis(const char* str) + { + for (auto i = 0u; i < std::extent_v; i++) + { + if (strcmp(str, virtualAxisNames[i]) == 0) + return static_cast(i); + } + + return Game::GPAD_VIRTAXIS_NONE; + } + + Game::GamepadMapping Gamepad::StringToGamePadMapping(const char* str) + { + for (auto i = 0u; i < std::extent_v; i++) + { + if (strcmp(str, gamePadMappingTypeNames[i]) == 0) + return static_cast(i); + } + + return Game::GPAD_MAP_NONE; + } + + void Gamepad::Axis_Bind_f(Command::Params* params) + { + if (params->size() < 4) + { + Logger::Print("bindaxis \n"); + return; + } + + const auto* physicalAxisText = params->get(1); + const auto* virtualAxisText = params->get(2); + const auto* mappingText = params->get(3); + + const Game::GamepadPhysicalAxis physicalAxis = StringToPhysicalAxis(physicalAxisText); + if (physicalAxis == Game::GPAD_PHYSAXIS_NONE) + { + Logger::Print("\"%s\" isn't a valid physical axis\n", physicalAxisText); + return; + } + + const Game::GamepadVirtualAxis virtualAxis = StringToVirtualAxis(virtualAxisText); + if (virtualAxis == Game::GPAD_VIRTAXIS_NONE) + { + Logger::Print("\"%s\" isn't a valid virtual axis\n", virtualAxisText); + return; + } + + const Game::GamepadMapping mapping = StringToGamePadMapping(mappingText); + if (mapping == Game::GPAD_MAP_NONE) + { + Logger::Print("\"%s\" isn't a valid input type\n", mappingText); + return; + } + + Gamepad_BindAxis(0, physicalAxis, virtualAxis, mapping); + } + + void Gamepad::Axis_Unbindall_f(Command::Params*) + { + auto& gamePadGlobal = gamePadGlobals[0]; + + for (auto& virtualAxis : gamePadGlobal.axes.virtualAxes) + { + virtualAxis.physicalAxis = Game::GPAD_PHYSAXIS_NONE; + virtualAxis.mapType = Game::GPAD_MAP_NONE; + } + } + + void Gamepad::Bind_GP_SticksConfigs_f(Command::Params*) + { + const auto* stickConfigName = gpad_sticksConfig.get(); + Game::Cbuf_AddText(0, Utils::String::VA("exec %s\n", stickConfigName)); + } + + void Gamepad::Bind_GP_ButtonsConfigs_f(Command::Params*) + { + const auto* buttonConfigName = gpad_buttonConfig.get(); + Game::Cbuf_AddText(0, Utils::String::VA("exec %s\n", buttonConfigName)); + } + + void Gamepad::Scores_Toggle_f(Command::Params*) + { + if(Game::cgArray[0].nextSnap) + { + if (Game::UI_GetActiveMenu(0) != Game::UIMENU_SCOREBOARD) + Game::CG_ScoresDown_f(); + else + Game::CG_ScoresUp_f(); + } + } + + void Gamepad::InitDvars() + { + gpad_enabled = Dvar::Register("gpad_enabled", false, Game::DVAR_ARCHIVE, "Game pad enabled"); + gpad_debug = Dvar::Register("gpad_debug", false, Game::DVAR_NONE, "Game pad debugging"); + gpad_present = Dvar::Register("gpad_present", false, Game::DVAR_NONE, "Game pad present"); + gpad_in_use = Dvar::Register("gpad_in_use", false, Game::DVAR_NONE, "A game pad is in use"); + gpad_style = Dvar::Register("gpad_style", false, Game::DVAR_ARCHIVE, "Switch between Xbox and PS HUD"); + gpad_sticksConfig = Dvar::Register("gpad_sticksConfig", "", Game::DVAR_ARCHIVE, "Game pad stick configuration"); + gpad_buttonConfig = Dvar::Register("gpad_buttonConfig", "", Game::DVAR_ARCHIVE, "Game pad button configuration"); + gpad_menu_scroll_delay_first = Dvar::Register("gpad_menu_scroll_delay_first", 420, 0, 1000, Game::DVAR_ARCHIVE, "Menu scroll key-repeat delay, for the first repeat, in milliseconds"); + gpad_menu_scroll_delay_rest = Dvar::Register("gpad_menu_scroll_delay_rest", 210, 0, 1000, Game::DVAR_ARCHIVE, + "Menu scroll key-repeat delay, for repeats after the first, in milliseconds"); + gpad_rumble = Dvar::Register("gpad_rumble", true, Game::DVAR_ARCHIVE, "Enable game pad rumble"); + gpad_stick_pressed_hysteresis = Dvar::Register("gpad_stick_pressed_hysteresis", 0.1f, 0.0f, 1.0f, Game::DVAR_NONE, + "Game pad stick pressed no-change-zone around gpad_stick_pressed to prevent bouncing"); + gpad_stick_pressed = Dvar::Register("gpad_stick_pressed", 0.4f, 0.0, 1.0, Game::DVAR_NONE, "Game pad stick pressed threshhold"); + gpad_stick_deadzone_max = Dvar::Register("gpad_stick_deadzone_max", 0.01f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad maximum stick deadzone"); + gpad_stick_deadzone_min = Dvar::Register("gpad_stick_deadzone_min", 0.2f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad minimum stick deadzone"); + gpad_button_deadzone = Dvar::Register("gpad_button_deadzone", 0.13f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad button deadzone threshhold"); + gpad_button_lstick_deflect_max = Dvar::Register("gpad_button_lstick_deflect_max", 1.0f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad maximum pad stick pressed value"); + gpad_button_rstick_deflect_max = Dvar::Register("gpad_button_rstick_deflect_max", 1.0f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad maximum pad stick pressed value"); + gpad_use_hold_time = Dvar::Register("gpad_use_hold_time", 250, 0, std::numeric_limits::max(), Game::DVAR_NONE, "Time to hold the 'use' button on gamepads to activate use"); + gpad_lockon_enabled = Dvar::Register("gpad_lockon_enabled", true, Game::DVAR_ARCHIVE, "Game pad lockon aim assist enabled"); + gpad_slowdown_enabled = Dvar::Register("gpad_slowdown_enabled", true, Game::DVAR_ARCHIVE, "Game pad slowdown aim assist enabled"); + + input_viewSensitivity = Dvar::Register("input_viewSensitivity", 1.0f, 0.0001f, 5.0f, Game::DVAR_ARCHIVE, "View Sensitivity"); + input_invertPitch = Dvar::Register("input_invertPitch", false, Game::DVAR_ARCHIVE, "Invert gamepad pitch"); + sv_allowAimAssist = Dvar::Register("sv_allowAimAssist", true, Game::DVAR_NONE, "Controls whether aim assist features on clients are enabled"); + aim_turnrate_pitch = Dvar::Var("aim_turnrate_pitch"); + aim_turnrate_pitch_ads = Dvar::Var("aim_turnrate_pitch_ads"); + aim_turnrate_yaw = Dvar::Var("aim_turnrate_yaw"); + aim_turnrate_yaw_ads = Dvar::Var("aim_turnrate_yaw_ads"); + aim_accel_turnrate_enabled = Dvar::Var("aim_accel_turnrate_enabled"); + aim_accel_turnrate_lerp = Dvar::Var("aim_accel_turnrate_lerp"); + aim_input_graph_enabled = Dvar::Var("aim_input_graph_enabled"); + aim_input_graph_index = Dvar::Var("aim_input_graph_index"); + aim_scale_view_axis = Dvar::Var("aim_scale_view_axis"); + cl_bypassMouseInput = Dvar::Var("cl_bypassMouseInput"); + cg_mapLocationSelectionCursorSpeed = Dvar::Var("cg_mapLocationSelectionCursorSpeed"); + aim_aimAssistRangeScale = Dvar::Var("aim_aimAssistRangeScale"); + aim_slowdown_enabled = Dvar::Var("aim_slowdown_enabled"); + aim_slowdown_debug = Dvar::Var("aim_slowdown_debug"); + aim_slowdown_pitch_scale = Dvar::Var("aim_slowdown_pitch_scale"); + aim_slowdown_pitch_scale_ads = Dvar::Var("aim_slowdown_pitch_scale_ads"); + aim_slowdown_yaw_scale = Dvar::Var("aim_slowdown_yaw_scale"); + aim_slowdown_yaw_scale_ads = Dvar::Var("aim_slowdown_yaw_scale_ads"); + aim_lockon_enabled = Dvar::Var("aim_lockon_enabled"); + aim_lockon_deflection = Dvar::Var("aim_lockon_deflection"); + aim_lockon_pitch_strength = Dvar::Var("aim_lockon_pitch_strength"); + aim_lockon_strength = Dvar::Var("aim_lockon_strength"); + } + + void Gamepad::CG_RegisterDvars_Hk() + { + // Call original function + Utils::Hook::Call(0x4F8DC0)(); + + InitDvars(); + } + + const char* Gamepad::GetGamePadCommand(const char* command) + { + if (strcmp(command, "+activate") == 0 || strcmp(command, "+reload") == 0) + return "+usereload"; + if (strcmp(command, "+melee_breath") == 0) + return "+holdbreath"; + + return command; + } + + int Gamepad::Key_GetCommandAssignmentInternal_Hk(const char* cmd, int (*keys)[2]) + { + auto keyCount = 0; + + if (gamePads[0].inUse) + { + cmd = GetGamePadCommand(cmd); + for (auto keyNum = 0; keyNum < Game::K_LAST_KEY; keyNum++) + { + if (!Key_IsValidGamePadChar(keyNum)) + continue; + + if (Game::playerKeys[0].keys[keyNum].binding && strcmp(Game::playerKeys[0].keys[keyNum].binding, cmd) == 0) + { + (*keys)[keyCount++] = keyNum; + + if (keyCount >= 2) + return keyCount; + } + } + } + else + { + for (auto keyNum = 0; keyNum < Game::K_LAST_KEY; keyNum++) + { + if (Key_IsValidGamePadChar(keyNum)) + continue; + + if (Game::playerKeys[0].keys[keyNum].binding && strcmp(Game::playerKeys[0].keys[keyNum].binding, cmd) == 0) + { + (*keys)[keyCount++] = keyNum; + + if (keyCount >= 2) + return keyCount; + } + } + } + + return keyCount; + } + + void Gamepad::CL_KeyEvent_Hk(const int localClientNum, const int key, const int down, const unsigned time) + { + // A keyboard key has been pressed. Mark controller as unused. + gamePads[0].inUse = false; + gpad_in_use.setRaw(false); + + // Call original function + Utils::Hook::Call(0x4F6480)(localClientNum, key, down, time); + } + + bool Gamepad::IsGamePadInUse() + { + return gamePads[0].inUse; + } + + int Gamepad::CL_MouseEvent_Hk(const int x, const int y, const int dx, const int dy) + { + if (dx != 0 || dy != 0) + { + gamePads[0].inUse = false; + gpad_in_use.setRaw(false); + } + + // Call original function + return Utils::Hook::Call(0x4D7C50)(x, y, dx, dy); + } + + bool Gamepad::UI_RefreshViewport_Hk() + { + return cl_bypassMouseInput.get() || IsGamePadInUse(); + } + + Game::keyname_t* Gamepad::GetLocalizedKeyNameMap() + { + if(gpad_style.get()) + return combinedLocalizedKeyNamesPs3; + + return combinedLocalizedKeyNamesXenon; + } + + void __declspec(naked) Gamepad::GetLocalizedKeyName_Stub() + { + __asm + { + push eax + pushad + + call GetLocalizedKeyNameMap + mov [esp + 0x20], eax + + popad + pop eax + + // Re-execute last instruction from game to set flags again for upcoming jump + test edi, edi + ret + } + } + + void Gamepad::CreateKeyNameMap() + { + memcpy(combinedKeyNames, Game::keyNames, sizeof(Game::keyname_t) * Game::KEY_NAME_COUNT); + memcpy(&combinedKeyNames[Game::KEY_NAME_COUNT], extendedKeyNames, sizeof(Game::keyname_t) * std::extent_v); + combinedKeyNames[std::extent_v - 1] = {nullptr, 0}; + + memcpy(combinedLocalizedKeyNamesXenon, Game::localizedKeyNames, sizeof(Game::keyname_t) * Game::LOCALIZED_KEY_NAME_COUNT); + memcpy(&combinedLocalizedKeyNamesXenon[Game::LOCALIZED_KEY_NAME_COUNT], extendedLocalizedKeyNamesXenon, + sizeof(Game::keyname_t) * std::extent_v); + combinedLocalizedKeyNamesXenon[std::extent_v - 1] = {nullptr, 0}; + + memcpy(combinedLocalizedKeyNamesPs3, Game::localizedKeyNames, sizeof(Game::keyname_t) * Game::LOCALIZED_KEY_NAME_COUNT); + memcpy(&combinedLocalizedKeyNamesPs3[Game::LOCALIZED_KEY_NAME_COUNT], extendedLocalizedKeyNamesPs3, + sizeof(Game::keyname_t) * std::extent_v); + combinedLocalizedKeyNamesPs3[std::extent_v - 1] = {nullptr, 0}; + + Utils::Hook::Set(0x4A780A, combinedKeyNames); + Utils::Hook::Set(0x4A7810, combinedKeyNames); + Utils::Hook::Set(0x435C9F, combinedKeyNames); + Utils::Hook(0x435C97, GetLocalizedKeyName_Stub, HOOK_CALL).install()->quick(); + } + + Gamepad::Gamepad() + { + if (ZoneBuilder::IsEnabled()) + return; + + // Initialize gamepad environment + Utils::Hook(0x4059FE, CG_RegisterDvars_Hk, HOOK_CALL).install()->quick(); + + // package the forward and right move components in the move buttons + Utils::Hook(0x60E38D, MSG_WriteDeltaUsercmdKeyStub, HOOK_JUMP).install()->quick(); + + // send two bytes for sending movement data + Utils::Hook::Set(0x60E501, 16); + Utils::Hook::Set(0x60E5CD, 16); + + // make sure to parse the movement data properly and apply it + Utils::Hook(0x492127, MSG_ReadDeltaUsercmdKeyStub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x492009, MSG_ReadDeltaUsercmdKeyStub2, HOOK_JUMP).install()->quick(); + + // Also rewrite configuration when gamepad config is dirty + Utils::Hook(0x60B264, Com_WriteConfiguration_Modified_Stub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x60B223, Key_WriteBindings_Hk, HOOK_CALL).install()->quick(); + + // Add hold time to gamepad usereload on hold prompts + Utils::Hook(0x5FE396, Player_UseEntity_Stub, HOOK_JUMP).install()->quick(); + + CreateKeyNameMap(); + + Command::Add("bindaxis", Axis_Bind_f); + Command::Add("unbindallaxis", Axis_Unbindall_f); + Command::Add("bindgpsticksconfigs", Bind_GP_SticksConfigs_f); + Command::Add("bindgpbuttonsconfigs", Bind_GP_ButtonsConfigs_f); + Command::Add("togglescores", Scores_Toggle_f); + + if (Dedicated::IsEnabled()) + return; + + // Gamepad on frame hook + Utils::Hook(0x475E9E, IN_Frame_Hk, HOOK_CALL).install()->quick(); - // Mark controller as unused when keyboard key is pressed - Utils::Hook(0x43D179, CL_KeyEvent_Hk, HOOK_CALL).install()->quick(); + // Mark controller as unused when keyboard key is pressed + Utils::Hook(0x43D179, CL_KeyEvent_Hk, HOOK_CALL).install()->quick(); - // Mark controller as unused when mouse is moved - Utils::Hook(0x64C507, CL_MouseEvent_Hk, HOOK_CALL).install()->quick(); + // Mark controller as unused when mouse is moved + Utils::Hook(0x64C507, CL_MouseEvent_Hk, HOOK_CALL).install()->quick(); - // Hide cursor when controller is active - Utils::Hook(0x48E527, UI_RefreshViewport_Hk, HOOK_CALL).install()->quick(); + // Hide cursor when controller is active + Utils::Hook(0x48E527, UI_RefreshViewport_Hk, HOOK_CALL).install()->quick(); - // Only return gamepad keys when gamepad enabled and only non gamepad keys when not - Utils::Hook(0x5A7A23, Key_GetCommandAssignmentInternal_Hk, HOOK_CALL).install()->quick(); + // Only return gamepad keys when gamepad enabled and only non gamepad keys when not + Utils::Hook(0x5A7A23, Key_GetCommandAssignmentInternal_Hk, HOOK_CALL).install()->quick(); - // Add gamepad inputs to remote control (eg predator) handling - Utils::Hook(0x5A6D4E, CL_RemoteControlMove_Stub, HOOK_CALL).install()->quick(); + // Add gamepad inputs to remote control (eg predator) handling + Utils::Hook(0x5A6D4E, CL_RemoteControlMove_Stub, HOOK_CALL).install()->quick(); - // Add gamepad inputs to location selection (eg airstrike location) handling - Utils::Hook(0x5A6D72, CG_HandleLocationSelectionInput_Stub, HOOK_CALL).install()->quick(); + // Add gamepad inputs to location selection (eg airstrike location) handling + Utils::Hook(0x5A6D72, CG_HandleLocationSelectionInput_Stub, HOOK_CALL).install()->quick(); - // Add gamepad inputs to usercmds - Utils::Hook(0x5A6DAE, CL_MouseMove_Stub, HOOK_CALL).install()->quick(); - } + // Add gamepad inputs to usercmds + Utils::Hook(0x5A6DAE, CL_MouseMove_Stub, HOOK_CALL).install()->quick(); + } } diff --git a/src/Components/Modules/Gamepad.hpp b/src/Components/Modules/Gamepad.hpp index de0c16e0..024d0c6a 100644 --- a/src/Components/Modules/Gamepad.hpp +++ b/src/Components/Modules/Gamepad.hpp @@ -2,202 +2,202 @@ namespace Components { - class Gamepad : public Component - { - struct ControllerMenuKeyMapping - { - Game::keyNum_t controllerKey; - Game::keyNum_t pcKey; - }; + class Gamepad : public Component + { + struct ControllerMenuKeyMapping + { + Game::keyNum_t controllerKey; + Game::keyNum_t pcKey; + }; - struct GamePad - { - bool enabled; - bool inUse; - int portIndex; - unsigned short digitals; - unsigned short lastDigitals; - float analogs[2]; - float lastAnalogs[2]; - float sticks[4]; - float lastSticks[4]; - bool stickDown[4][Game::GPAD_STICK_DIR_COUNT]; - bool stickDownLast[4][Game::GPAD_STICK_DIR_COUNT]; - float lowRumble; - float highRumble; + struct GamePad + { + bool enabled; + bool inUse; + int portIndex; + unsigned short digitals; + unsigned short lastDigitals; + float analogs[2]; + float lastAnalogs[2]; + float sticks[4]; + float lastSticks[4]; + bool stickDown[4][Game::GPAD_STICK_DIR_COUNT]; + bool stickDownLast[4][Game::GPAD_STICK_DIR_COUNT]; + float lowRumble; + float highRumble; - XINPUT_VIBRATION rumble; - XINPUT_CAPABILITIES caps; - }; + XINPUT_VIBRATION rumble; + XINPUT_CAPABILITIES caps; + }; - struct GamePadGlobals - { - Game::GpadAxesGlob axes; - unsigned nextScrollTime; + struct GamePadGlobals + { + Game::GpadAxesGlob axes; + unsigned nextScrollTime; - GamePadGlobals(); - }; + GamePadGlobals(); + }; - public: - Gamepad(); + public: + Gamepad(); - private: - static Game::ButtonToCodeMap_t buttonList[]; - static Game::StickToCodeMap_t analogStickList[4]; - static Game::GamePadStick stickForAxis[]; - static Game::GamepadPhysicalAxis axisSameStick[]; - static const char* physicalAxisNames[]; - static const char* virtualAxisNames[]; - static const char* gamePadMappingTypeNames[]; - static Game::keyNum_t menuScrollButtonList[]; - static Game::keyname_t extendedKeyNames[]; - static Game::keyname_t extendedLocalizedKeyNamesXenon[]; - static Game::keyname_t extendedLocalizedKeyNamesPs3[]; - static Game::keyname_t combinedKeyNames[]; - static Game::keyname_t combinedLocalizedKeyNamesXenon[]; - static Game::keyname_t combinedLocalizedKeyNamesPs3[]; - static ControllerMenuKeyMapping controllerMenuKeyMappings[]; + private: + static Game::ButtonToCodeMap_t buttonList[]; + static Game::StickToCodeMap_t analogStickList[4]; + static Game::GamePadStick stickForAxis[]; + static Game::GamepadPhysicalAxis axisSameStick[]; + static const char* physicalAxisNames[]; + static const char* virtualAxisNames[]; + static const char* gamePadMappingTypeNames[]; + static Game::keyNum_t menuScrollButtonList[]; + static Game::keyname_t extendedKeyNames[]; + static Game::keyname_t extendedLocalizedKeyNamesXenon[]; + static Game::keyname_t extendedLocalizedKeyNamesPs3[]; + static Game::keyname_t combinedKeyNames[]; + static Game::keyname_t combinedLocalizedKeyNamesXenon[]; + static Game::keyname_t combinedLocalizedKeyNamesPs3[]; + static ControllerMenuKeyMapping controllerMenuKeyMappings[]; - static GamePad gamePads[Game::MAX_GAMEPADS]; - static GamePadGlobals gamePadGlobals[Game::MAX_GAMEPADS]; + static GamePad gamePads[Game::MAX_GAMEPADS]; + static GamePadGlobals gamePadGlobals[Game::MAX_GAMEPADS]; - static int gamePadBindingsModifiedFlags; + static int gamePadBindingsModifiedFlags; - static Dvar::Var gpad_enabled; - static Dvar::Var gpad_debug; - static Dvar::Var gpad_present; - static Dvar::Var gpad_in_use; - static Dvar::Var gpad_style; - static Dvar::Var gpad_sticksConfig; - static Dvar::Var gpad_buttonConfig; - static Dvar::Var gpad_menu_scroll_delay_first; - static Dvar::Var gpad_menu_scroll_delay_rest; - static Dvar::Var gpad_rumble; - static Dvar::Var gpad_stick_pressed_hysteresis; - static Dvar::Var gpad_stick_pressed; - static Dvar::Var gpad_stick_deadzone_max; - static Dvar::Var gpad_stick_deadzone_min; - static Dvar::Var gpad_button_deadzone; - static Dvar::Var gpad_button_rstick_deflect_max; - static Dvar::Var gpad_button_lstick_deflect_max; - static Dvar::Var gpad_use_hold_time; - static Dvar::Var gpad_lockon_enabled; - static Dvar::Var gpad_slowdown_enabled; - static Dvar::Var input_viewSensitivity; - static Dvar::Var input_invertPitch; - static Dvar::Var sv_allowAimAssist; - static Dvar::Var aim_turnrate_pitch; - static Dvar::Var aim_turnrate_pitch_ads; - static Dvar::Var aim_turnrate_yaw; - static Dvar::Var aim_turnrate_yaw_ads; - static Dvar::Var aim_accel_turnrate_enabled; - static Dvar::Var aim_accel_turnrate_lerp; - static Dvar::Var aim_input_graph_enabled; - static Dvar::Var aim_input_graph_index; - static Dvar::Var aim_scale_view_axis; - static Dvar::Var cl_bypassMouseInput; - static Dvar::Var cg_mapLocationSelectionCursorSpeed; - static Dvar::Var aim_aimAssistRangeScale; - static Dvar::Var aim_slowdown_enabled; - static Dvar::Var aim_slowdown_debug; - static Dvar::Var aim_slowdown_pitch_scale; - static Dvar::Var aim_slowdown_pitch_scale_ads; - static Dvar::Var aim_slowdown_yaw_scale; - static Dvar::Var aim_slowdown_yaw_scale_ads; - static Dvar::Var aim_lockon_enabled; - static Dvar::Var aim_lockon_deflection; - static Dvar::Var aim_lockon_pitch_strength; - static Dvar::Var aim_lockon_strength; + static Dvar::Var gpad_enabled; + static Dvar::Var gpad_debug; + static Dvar::Var gpad_present; + static Dvar::Var gpad_in_use; + static Dvar::Var gpad_style; + static Dvar::Var gpad_sticksConfig; + static Dvar::Var gpad_buttonConfig; + static Dvar::Var gpad_menu_scroll_delay_first; + static Dvar::Var gpad_menu_scroll_delay_rest; + static Dvar::Var gpad_rumble; + static Dvar::Var gpad_stick_pressed_hysteresis; + static Dvar::Var gpad_stick_pressed; + static Dvar::Var gpad_stick_deadzone_max; + static Dvar::Var gpad_stick_deadzone_min; + static Dvar::Var gpad_button_deadzone; + static Dvar::Var gpad_button_rstick_deflect_max; + static Dvar::Var gpad_button_lstick_deflect_max; + static Dvar::Var gpad_use_hold_time; + static Dvar::Var gpad_lockon_enabled; + static Dvar::Var gpad_slowdown_enabled; + static Dvar::Var input_viewSensitivity; + static Dvar::Var input_invertPitch; + static Dvar::Var sv_allowAimAssist; + static Dvar::Var aim_turnrate_pitch; + static Dvar::Var aim_turnrate_pitch_ads; + static Dvar::Var aim_turnrate_yaw; + static Dvar::Var aim_turnrate_yaw_ads; + static Dvar::Var aim_accel_turnrate_enabled; + static Dvar::Var aim_accel_turnrate_lerp; + static Dvar::Var aim_input_graph_enabled; + static Dvar::Var aim_input_graph_index; + static Dvar::Var aim_scale_view_axis; + static Dvar::Var cl_bypassMouseInput; + static Dvar::Var cg_mapLocationSelectionCursorSpeed; + static Dvar::Var aim_aimAssistRangeScale; + static Dvar::Var aim_slowdown_enabled; + static Dvar::Var aim_slowdown_debug; + static Dvar::Var aim_slowdown_pitch_scale; + static Dvar::Var aim_slowdown_pitch_scale_ads; + static Dvar::Var aim_slowdown_yaw_scale; + static Dvar::Var aim_slowdown_yaw_scale_ads; + static Dvar::Var aim_lockon_enabled; + static Dvar::Var aim_lockon_deflection; + static Dvar::Var aim_lockon_pitch_strength; + static Dvar::Var aim_lockon_strength; - static void MSG_WriteDeltaUsercmdKeyStub(); + static void MSG_WriteDeltaUsercmdKeyStub(); - static void ApplyMovement(Game::msg_t* msg, int key, Game::usercmd_s* from, Game::usercmd_s* to); + static void ApplyMovement(Game::msg_t* msg, int key, Game::usercmd_s* from, Game::usercmd_s* to); - static void MSG_ReadDeltaUsercmdKeyStub(); - static void MSG_ReadDeltaUsercmdKeyStub2(); + static void MSG_ReadDeltaUsercmdKeyStub(); + static void MSG_ReadDeltaUsercmdKeyStub2(); - static float LinearTrack(float target, float current, float rate, float deltaTime); - static bool AimAssist_DoBoundsIntersectCenterBox(const float* clipMins, const float* clipMaxs, float clipHalfWidth, float clipHalfHeight); - static bool AimAssist_IsPlayerUsingOffhand(Game::AimAssistPlayerState* ps); - static const Game::AimScreenTarget* AimAssist_GetBestTarget(const Game::AimAssistGlobals* aaGlob, float range, float regionWidth, float regionHeight); - static const Game::AimScreenTarget* AimAssist_GetTargetFromEntity(const Game::AimAssistGlobals* aaGlob, int entIndex); - static const Game::AimScreenTarget* AimAssist_GetPrevOrBestTarget(const Game::AimAssistGlobals* aaGlob, float range, float regionWidth, float regionHeight, int prevTargetEnt); - static bool AimAssist_IsLockonActive(int gamePadIndex); - static void AimAssist_ApplyLockOn(const Game::AimInput* input, Game::AimOutput* output); - static void AimAssist_CalcAdjustedAxis(const Game::AimInput* input, float* pitchAxis, float* yawAxis); - static bool AimAssist_IsSlowdownActive(const Game::AimAssistPlayerState* ps); - static void AimAssist_CalcSlowdown(const Game::AimInput* input, float* pitchScale, float* yawScale); - static float AimAssist_Lerp(float from, float to, float fraction); - static void AimAssist_ApplyTurnRates(const Game::AimInput* input, Game::AimOutput* output); - static void AimAssist_UpdateGamePadInput(const Game::AimInput* input, Game::AimOutput* output); + static float LinearTrack(float target, float current, float rate, float deltaTime); + static bool AimAssist_DoBoundsIntersectCenterBox(const float* clipMins, const float* clipMaxs, float clipHalfWidth, float clipHalfHeight); + static bool AimAssist_IsPlayerUsingOffhand(Game::AimAssistPlayerState* ps); + static const Game::AimScreenTarget* AimAssist_GetBestTarget(const Game::AimAssistGlobals* aaGlob, float range, float regionWidth, float regionHeight); + static const Game::AimScreenTarget* AimAssist_GetTargetFromEntity(const Game::AimAssistGlobals* aaGlob, int entIndex); + static const Game::AimScreenTarget* AimAssist_GetPrevOrBestTarget(const Game::AimAssistGlobals* aaGlob, float range, float regionWidth, float regionHeight, int prevTargetEnt); + static bool AimAssist_IsLockonActive(int gamePadIndex); + static void AimAssist_ApplyLockOn(const Game::AimInput* input, Game::AimOutput* output); + static void AimAssist_CalcAdjustedAxis(const Game::AimInput* input, float* pitchAxis, float* yawAxis); + static bool AimAssist_IsSlowdownActive(const Game::AimAssistPlayerState* ps); + static void AimAssist_CalcSlowdown(const Game::AimInput* input, float* pitchScale, float* yawScale); + static float AimAssist_Lerp(float from, float to, float fraction); + static void AimAssist_ApplyTurnRates(const Game::AimInput* input, Game::AimOutput* output); + static void AimAssist_UpdateGamePadInput(const Game::AimInput* input, Game::AimOutput* output); - static void CL_RemoteControlMove_GamePad(int localClientNum, Game::usercmd_s* cmd); - static void CL_RemoteControlMove_Stub(); - static bool CG_HandleLocationSelectionInput_GamePad(int localClientNum, Game::usercmd_s* cmd); - static void CG_HandleLocationSelectionInput_Stub(); - static bool CG_ShouldUpdateViewAngles(int localClientNum); - static float CL_GamepadAxisValue(int gamePadIndex, Game::GamepadVirtualAxis virtualAxis); - static char ClampChar(int value); - static void CL_GamepadMove(int gamePadIndex, Game::usercmd_s* cmd, float frameTimeBase); - static void CL_MouseMove_Stub(); - - static bool Gamepad_ShouldUse(const Game::gentity_s* playerEnt, unsigned useTime); - static void Player_UseEntity_Stub(); + static void CL_RemoteControlMove_GamePad(int localClientNum, Game::usercmd_s* cmd); + static void CL_RemoteControlMove_Stub(); + static bool CG_HandleLocationSelectionInput_GamePad(int localClientNum, Game::usercmd_s* cmd); + static void CG_HandleLocationSelectionInput_Stub(); + static bool CG_ShouldUpdateViewAngles(int localClientNum); + static float CL_GamepadAxisValue(int gamePadIndex, Game::GamepadVirtualAxis virtualAxis); + static char ClampChar(int value); + static void CL_GamepadMove(int gamePadIndex, Game::usercmd_s* cmd, float frameTimeBase); + static void CL_MouseMove_Stub(); + + static bool Gamepad_ShouldUse(const Game::gentity_s* playerEnt, unsigned useTime); + static void Player_UseEntity_Stub(); - static bool Key_IsValidGamePadChar(int key); - static void CL_GamepadResetMenuScrollTime(int gamePadIndex, int key, bool down, unsigned int time); - static bool Scoreboard_HandleInput(int gamePadIndex, int key); - static bool CL_CheckForIgnoreDueToRepeat(int gamePadIndex, int key, int repeatCount, unsigned int time); - static void UI_GamepadKeyEvent(int gamePadIndex, int key, bool down); - static void CL_GamepadGenerateAPad(int gamePadIndex, Game::GamepadPhysicalAxis physicalAxis, unsigned time); - static void CL_GamepadEvent(int gamePadIndex, Game::GamepadPhysicalAxis physicalAxis, float value, unsigned time); - static void CL_GamepadButtonEvent(int gamePadIndex, int key, Game::GamePadButtonEvent buttonEvent, unsigned time); - static void CL_GamepadButtonEventForPort(int gamePadIndex, int key, Game::GamePadButtonEvent buttonEvent, unsigned int time); + static bool Key_IsValidGamePadChar(int key); + static void CL_GamepadResetMenuScrollTime(int gamePadIndex, int key, bool down, unsigned int time); + static bool Scoreboard_HandleInput(int gamePadIndex, int key); + static bool CL_CheckForIgnoreDueToRepeat(int gamePadIndex, int key, int repeatCount, unsigned int time); + static void UI_GamepadKeyEvent(int gamePadIndex, int key, bool down); + static void CL_GamepadGenerateAPad(int gamePadIndex, Game::GamepadPhysicalAxis physicalAxis, unsigned time); + static void CL_GamepadEvent(int gamePadIndex, Game::GamepadPhysicalAxis physicalAxis, float value, unsigned time); + static void CL_GamepadButtonEvent(int gamePadIndex, int key, Game::GamePadButtonEvent buttonEvent, unsigned time); + static void CL_GamepadButtonEventForPort(int gamePadIndex, int key, Game::GamePadButtonEvent buttonEvent, unsigned int time); - static void GPad_ConvertStickToFloat(short x, short y, float& outX, float& outY); - static float GPad_GetStick(int gamePadIndex, Game::GamePadStick stick); - static float GPad_GetButton(int gamePadIndex, Game::GamePadButton button); - static bool GPad_IsButtonPressed(int gamePadIndex, Game::GamePadButton button); - static bool GPad_ButtonRequiresUpdates(int gamePadIndex, Game::GamePadButton button); - static bool GPad_IsButtonReleased(int gamePadIndex, Game::GamePadButton button); + static void GPad_ConvertStickToFloat(short x, short y, float& outX, float& outY); + static float GPad_GetStick(int gamePadIndex, Game::GamePadStick stick); + static float GPad_GetButton(int gamePadIndex, Game::GamePadButton button); + static bool GPad_IsButtonPressed(int gamePadIndex, Game::GamePadButton button); + static bool GPad_ButtonRequiresUpdates(int gamePadIndex, Game::GamePadButton button); + static bool GPad_IsButtonReleased(int gamePadIndex, Game::GamePadButton button); - static void GPad_UpdateSticksDown(int gamePadIndex); - static void GPad_UpdateSticks(int gamePadIndex, const XINPUT_GAMEPAD& state); - static void GPad_UpdateDigitals(int gamePadIndex, const XINPUT_GAMEPAD& state); - static void GPad_UpdateAnalogs(int gamePadIndex, const XINPUT_GAMEPAD& state); + static void GPad_UpdateSticksDown(int gamePadIndex); + static void GPad_UpdateSticks(int gamePadIndex, const XINPUT_GAMEPAD& state); + static void GPad_UpdateDigitals(int gamePadIndex, const XINPUT_GAMEPAD& state); + static void GPad_UpdateAnalogs(int gamePadIndex, const XINPUT_GAMEPAD& state); - static bool GPad_Check(int gamePadIndex, int portIndex); - static void GPad_RefreshAll(); - static void GPad_UpdateAll(); - static void IN_GamePadsMove(); - static void IN_Frame_Hk(); + static bool GPad_Check(int gamePadIndex, int portIndex); + static void GPad_RefreshAll(); + static void GPad_UpdateAll(); + static void IN_GamePadsMove(); + static void IN_Frame_Hk(); - static void Gamepad_WriteBindings(int gamePadIndex, int handle); - static void Key_WriteBindings_Hk(int localClientNum, int handle); - static void Com_WriteConfiguration_Modified_Stub(); + static void Gamepad_WriteBindings(int gamePadIndex, int handle); + static void Key_WriteBindings_Hk(int localClientNum, int handle); + static void Com_WriteConfiguration_Modified_Stub(); - static void Gamepad_BindAxis(int gamePadIndex, Game::GamepadPhysicalAxis realIndex, Game::GamepadVirtualAxis axisIndex, Game::GamepadMapping mapType); - static Game::GamepadPhysicalAxis StringToPhysicalAxis(const char* str); - static Game::GamepadVirtualAxis StringToVirtualAxis(const char* str); - static Game::GamepadMapping StringToGamePadMapping(const char* str); - static void Axis_Bind_f(Command::Params* params); - static void Axis_Unbindall_f(Command::Params* params); - static void Bind_GP_SticksConfigs_f(Command::Params* params); - static void Bind_GP_ButtonsConfigs_f(Command::Params* params); - static void Scores_Toggle_f(Command::Params* params); + static void Gamepad_BindAxis(int gamePadIndex, Game::GamepadPhysicalAxis realIndex, Game::GamepadVirtualAxis axisIndex, Game::GamepadMapping mapType); + static Game::GamepadPhysicalAxis StringToPhysicalAxis(const char* str); + static Game::GamepadVirtualAxis StringToVirtualAxis(const char* str); + static Game::GamepadMapping StringToGamePadMapping(const char* str); + static void Axis_Bind_f(Command::Params* params); + static void Axis_Unbindall_f(Command::Params* params); + static void Bind_GP_SticksConfigs_f(Command::Params* params); + static void Bind_GP_ButtonsConfigs_f(Command::Params* params); + static void Scores_Toggle_f(Command::Params* params); - static void InitDvars(); - static void CG_RegisterDvars_Hk(); + static void InitDvars(); + static void CG_RegisterDvars_Hk(); - static const char* GetGamePadCommand(const char* command); - static int Key_GetCommandAssignmentInternal_Hk(const char* cmd, int(*keys)[2]); - static bool IsGamePadInUse(); - static void CL_KeyEvent_Hk(int localClientNum, int key, int down, unsigned int time); - static int CL_MouseEvent_Hk(int x, int y, int dx, int dy); - static bool UI_RefreshViewport_Hk(); + static const char* GetGamePadCommand(const char* command); + static int Key_GetCommandAssignmentInternal_Hk(const char* cmd, int(*keys)[2]); + static bool IsGamePadInUse(); + static void CL_KeyEvent_Hk(int localClientNum, int key, int down, unsigned int time); + static int CL_MouseEvent_Hk(int x, int y, int dx, int dy); + static bool UI_RefreshViewport_Hk(); - static Game::keyname_t* GetLocalizedKeyNameMap(); - static void GetLocalizedKeyName_Stub(); - static void CreateKeyNameMap(); - }; + static Game::keyname_t* GetLocalizedKeyNameMap(); + static void GetLocalizedKeyName_Stub(); + static void CreateKeyNameMap(); + }; } diff --git a/src/Components/Modules/Gametypes.cpp b/src/Components/Modules/Gametypes.cpp index c155cf99..d7f567f4 100644 --- a/src/Components/Modules/Gametypes.cpp +++ b/src/Components/Modules/Gametypes.cpp @@ -98,15 +98,5 @@ namespace Components // Dynamically grab gametypes Utils::Hook(0x5FA46C, Gametypes::BuildGametypeList, HOOK_CALL).install()->quick(); // Scr_UpdateGameTypeList Utils::Hook(0x632155, Gametypes::BuildGametypeList, HOOK_CALL).install()->quick(); // UI_UpdateGameTypesList - - // This is placed here in case the anticheat has been disabled! - // Make sure this is called after every onther anticheat check! -#ifndef DISABLE_ANTICHEAT - Utils::Hook(0x5ACBA3, []() // Somewhere in the renderer, past other renderer hooks! - { - AntiCheat::FlagIntegrityCheck(); - return Utils::Hook::Call(0x50AB20)(); - }, HOOK_CALL).install()->quick(); -#endif } } diff --git a/src/Components/Modules/Localization.cpp b/src/Components/Modules/Localization.cpp index 3b385506..641f1b0f 100644 --- a/src/Components/Modules/Localization.cpp +++ b/src/Components/Modules/Localization.cpp @@ -297,13 +297,6 @@ namespace Components } } }); - -// #ifndef DISABLE_ANTICHEAT -// if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled() && !Utils::IsWineEnvironment() && !Loader::IsPerformingUnitTests()) -// { -// AntiCheat::PatchVirtualProtect(VirtualProtect, VirtualProtectEx); -// } -// #endif } Localization::~Localization() diff --git a/src/Components/Modules/Logger.cpp b/src/Components/Modules/Logger.cpp index f43724f3..605a5510 100644 --- a/src/Components/Modules/Logger.cpp +++ b/src/Components/Modules/Logger.cpp @@ -77,16 +77,14 @@ namespace Components std::string Logger::Format(const char** message) { - const size_t bufferSize = 0x10000; - Utils::Memory::Allocator allocator; - char* buffer = allocator.allocateArray(bufferSize); + char buffer[4096] = {0}; va_list ap = reinterpret_cast(const_cast(&message[1])); - //va_start(ap, *message); - _vsnprintf_s(buffer, bufferSize, bufferSize, *message, ap); + + _vsnprintf_s(buffer, _TRUNCATE, *message, ap); va_end(ap); - return buffer; + return {buffer}; } void Logger::Flush() @@ -138,8 +136,8 @@ namespace Components { if (!data) return; - std::string buffer(data); - for (auto& addr : Logger::LoggingAddresses[gLog & 1]) + const std::string buffer(data); + for (const auto& addr : Logger::LoggingAddresses[gLog & 1]) { Network::SendCommand(addr, "print", buffer); } @@ -373,9 +371,9 @@ namespace Components Logger::MessageMutex.unlock(); // Flush the console log - if (int fh = *reinterpret_cast(0x1AD8F28)) + if (const auto logfile = *reinterpret_cast(0x1AD8F28)) { - Game::FS_FCloseFile(fh); + Game::FS_FCloseFile(logfile); } } } diff --git a/src/Components/Modules/Maps.cpp b/src/Components/Modules/Maps.cpp index f46fdd56..34a819b2 100644 --- a/src/Components/Modules/Maps.cpp +++ b/src/Components/Modules/Maps.cpp @@ -788,7 +788,7 @@ namespace Components { int dlc = token.get(); - for (auto pack : Maps::DlcPacks) + for (const auto& pack : Maps::DlcPacks) { if (pack.index == dlc) { diff --git a/src/Components/Modules/Menus.cpp b/src/Components/Modules/Menus.cpp index f29b4499..2d316acf 100644 --- a/src/Components/Modules/Menus.cpp +++ b/src/Components/Modules/Menus.cpp @@ -70,7 +70,7 @@ namespace Components script->next = nullptr; - Game::source_t* source = allocator->allocate(); + auto* source = allocator->allocate(); if (!source) { Game::FreeMemory(script); @@ -116,7 +116,7 @@ namespace Components { Utils::Memory::Allocator* allocator = Utils::Memory::GetAllocator(); - Game::menuDef_t* menu = allocator->allocate(); + auto* menu = allocator->allocate(); if (!menu) return nullptr; menu->items = allocator->allocateArray(512); @@ -284,17 +284,7 @@ namespace Components if (menus.empty()) { - // // Try loading the original menu, if we can't load our custom one - // Game::menuDef_t* originalMenu = AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_MENU, menudef->window.name).menu; - // - // if (originalMenu) - // { - // menus.push_back({ false, originalMenu }); - // } - // else - // { menus.push_back({ false, menudef }); // Native menu -// } } return menus; @@ -308,7 +298,7 @@ namespace Components if (menus.empty()) return nullptr; // Allocate new menu list - Game::MenuList* newList = allocator->allocate(); + auto* newList = allocator->allocate(); if (!newList) return nullptr; newList->menus = allocator->allocateArray(menus.size()); @@ -319,7 +309,7 @@ namespace Components } newList->name = allocator->duplicateString(menu); - newList->menuCount = menus.size(); + newList->menuCount = static_cast(menus.size()); // Copy new menus for (unsigned int i = 0; i < menus.size(); ++i) @@ -759,50 +749,14 @@ namespace Components void Menus::RegisterCustomMenusHook() { - Game::UiContext* uiInfoArray = (Game::UiContext*)0x62E2858; - // Game::MenuList list; - Utils::Hook::Call(0x401700)(); // call original load functions - //Game::XAssetHeader header = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MENULIST, "ui_mp/iw4x.txt"); - //if (header.data && !(header.menuList->menuCount == 1 && !_stricmp("default_menu", header.menuList->menus[0]->window.name))) - //{ - // // Utils::Hook::Call(0x401700)(uiInfoArray, header.menuList, 1); // add loaded menus - // std::memcpy(&list, header.data, sizeof(Game::MenuList)); - // for (int i = 0; i < uiInfoArray->menuCount; i++) - // { - // for (int j = 0; j < list.menuCount; j++) - // { - // if (!list.menus[j]) continue; // skip already used entries - // if (!stricmp(list.menus[j]->window.name, uiInfoArray->Menus[i]->window.name)) - // { - // uiInfoArray->Menus[i] = list.menus[j]; // overwrite UiContext pointer - // list.menus[j] = nullptr; // clear entries that already exist so we don't add them later - // } - // } - // } - - // for (int i = 0; i < list.menuCount; i++) - // { - // if (list.menus[i]) - // { - // uiInfoArray->Menus[uiInfoArray->menuCount++] = list.menus[i]; - // } - // } - //} - - for (int i = 0; i < uiInfoArray->menuCount; i++) +#ifdef _DEBUG + for (int i = 0; i < Game::uiContext->menuCount; i++) { - OutputDebugStringA(Utils::String::VA("%s\n", uiInfoArray->Menus[i]->window.name)); + OutputDebugStringA(Utils::String::VA("%s\n", Game::uiContext->Menus[i]->window.name)); } - - /* - header = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MENULIST, "ui_mp/mod.txt"); - if (header.data && !(header.menuList->menuCount == 1 && !_stricmp("default_menu", header.menuList->menus[0]->window.name))) - { - Utils::Hook::Call(0x401700)(uiInfoArray, header.menuList, 1); // add loaded menus - } - */ +#endif } Menus::Menus() @@ -824,12 +778,12 @@ namespace Components // Use the connect menu open call to update server motds Utils::Hook(0x428E48, []() + { + if (!Party::GetMotd().empty() && Party::Target() == *Game::connectedHost) { - if (!Party::GetMotd().empty() && Party::Target() == *Game::connectedHost) - { - Dvar::Var("didyouknow").set(Party::GetMotd()); - } - }, HOOK_CALL).install()->quick(); + Dvar::Var("didyouknow").set(Party::GetMotd()); + } + }, HOOK_CALL).install()->quick(); // Intercept menu painting Utils::Hook(0x4FFBDF, Menus::IsMenuVisible, HOOK_CALL).install()->quick(); @@ -844,53 +798,49 @@ namespace Components Utils::Hook::SetString(0x6FC790, "main_text"); Command::Add("openmenu", [](Command::Params* params) + { + if (params->size() != 2) { - if (params->size() != 2) - { - Logger::Print("USAGE: openmenu \n"); - return; - } + Logger::Print("USAGE: openmenu \n"); + return; + } - // Not quite sure if we want to do this if we're not ingame, but it's only needed for ingame menus. - if (Dvar::Var("cl_ingame").get()) - { - Game::Key_SetCatcher(0, 16); - } + // Not quite sure if we want to do this if we're not ingame, but it's only needed for ingame menus. + if (Dvar::Var("cl_ingame").get()) + { + Game::Key_SetCatcher(0, 16); + } - Game::Menus_OpenByName(Game::uiContext, params->get(1)); - }); + Game::Menus_OpenByName(Game::uiContext, params->get(1)); + }); Command::Add("reloadmenus", [](Command::Params*) + { + // Close all menus + Game::Menus_CloseAll(Game::uiContext); + + // Free custom menus + Menus::FreeEverything(); + + // Only disconnect if in-game, context is updated automatically! + if (Game::CL_IsCgameInitialized()) { - // Close all menus - Game::Menus_CloseAll(Game::uiContext); + Game::Cbuf_AddText(0, "disconnect\n"); + } + else + { + // Reinitialize ui context + Utils::Hook::Call(0x401700)(); - // Free custom menus - Menus::FreeEverything(); - - // Only disconnect if in-game, context is updated automatically! - if (Game::CL_IsCgameInitialized()) - { - Game::Cbuf_AddText(0, "disconnect\n"); - } - else - { - // Reinitialize ui context - Utils::Hook::Call(0x401700)(); - - // Reopen main menu - Game::Menus_OpenByName(Game::uiContext, "main_text"); - } - }); - -#ifndef DISABLE_ANTICHEAT - Scheduler::OnFrameAsync(AntiCheat::QuickCodeScanner2); -#endif + // Reopen main menu + Game::Menus_OpenByName(Game::uiContext, "main_text"); + } + }); Command::Add("mp_QuickMessage", [](Command::Params*) - { - Command::Execute("openmenu quickmessage"); - }); + { + Command::Execute("openmenu quickmessage"); + }); // Define custom menus here Menus::Add("ui_mp/changelog.menu"); diff --git a/src/Components/Modules/Movement.cpp b/src/Components/Modules/Movement.cpp index 57bfc72f..806899c1 100644 --- a/src/Components/Modules/Movement.cpp +++ b/src/Components/Modules/Movement.cpp @@ -2,355 +2,289 @@ namespace Components { - Dvar::Var Movement::PlayerDuckedSpeedScale; - Dvar::Var Movement::PlayerLastStandCrawlSpeedScale; - Dvar::Var Movement::PlayerProneSpeedScale; - Dvar::Var Movement::PlayerSpectateSpeedScale; - Dvar::Var Movement::CGUfoScaler; - Dvar::Var Movement::CGNoclipScaler; - Dvar::Var Movement::BGBouncesAllAngles; - Dvar::Var Movement::BGRocketJump; - Dvar::Var Movement::BGPlayerEjection; - Dvar::Var Movement::BGPlayerCollision; - Game::dvar_t* Movement::BGBounces; - - float Movement::PM_CmdScaleForStance(const Game::pmove_s* pm) - { - assert(pm->ps != nullptr); - - const auto* playerState = pm->ps; - float scale; - - if (playerState->viewHeightLerpTime != 0 && playerState->viewHeightLerpTarget == 0xB) - { - scale = pm->cmd.serverTime - playerState->viewHeightLerpTime / 400.0f; - - if (0.0f <= scale) - { - if (scale > 1.0f) - { - scale = 1.0f; - return scale * 0.15f + (1.0f - scale) * 0.65f; - } - - if (scale != 0.0f) - { - return scale * 0.15f + (1.0f - scale) * 0.65f; - } - } - } - - if ((playerState->viewHeightLerpTime != 0 && playerState->viewHeightLerpTarget == 0x28) && - playerState->viewHeightLerpDown == 0) - { - scale = 400.0f / pm->cmd.serverTime - playerState->viewHeightLerpTime; - - if (0.0f <= scale) - { - if (scale > 1.0f) - { - scale = 1.0f; - } - else if (scale != 0.0f) - { - return scale * 0.65f + (1.0f - scale) * 0.15f; - } - } - } - - scale = 1.0f; - const auto stance = Game::PM_GetEffectiveStance(playerState); - - if (stance == Game::PM_EFF_STANCE_PRONE) - { - scale = Movement::PlayerProneSpeedScale.get(); - } - - else if (stance == Game::PM_EFF_STANCE_DUCKED) - { - scale = Movement::PlayerDuckedSpeedScale.get(); - } - - else if (stance == Game::PM_EFF_STANCE_LASTSTANDCRAWL) - { - scale = Movement::PlayerLastStandCrawlSpeedScale.get(); - } - - return scale; - } - - __declspec(naked) void Movement::PM_CmdScaleForStanceStub() - { - __asm - { - pushad - - push edx - call Movement::PM_CmdScaleForStance // pm - add esp, 4 - - popad - ret - } - } - - float Movement::PM_MoveScale(Game::playerState_s* ps, float forwardmove, - float rightmove, float upmove) - { - assert(ps != nullptr); - - auto max = (std::fabsf(forwardmove) < std::fabsf(rightmove)) - ? std::fabsf(rightmove) - : std::fabsf(forwardmove); - - if (std::fabsf(upmove) > max) - { - max = std::fabsf(upmove); - } - - if (max == 0.0f) - { - return 0.0f; - } - - auto total = std::sqrtf(forwardmove * forwardmove - + rightmove * rightmove + upmove * upmove); - auto scale = (ps->speed * max) / (127.0f * total); - - if (ps->pm_flags & Game::PMF_WALKING || ps->leanf != 0.0f) - { - scale *= 0.4f; - } - - if (ps->pm_type == Game::PM_NOCLIP) - { - return scale * Movement::CGNoclipScaler.get(); - } - - if (ps->pm_type == Game::PM_UFO) - { - return scale * Movement::CGUfoScaler.get(); - } - - if (ps->pm_type == Game::PM_SPECTATOR) - { - return scale * Movement::PlayerSpectateSpeedScale.get(); - } - - return scale; - } - - __declspec(naked) void Movement::PM_MoveScaleStub() - { - __asm - { - pushad - - push [esp + 0xC + 0x20] // upmove - push [esp + 0xC + 0x20] // rightmove - push [esp + 0xC + 0x20] // forwardmove - push esi // ps - call Movement::PM_MoveScale - add esp, 0x10 - - popad - ret - } - } - - __declspec(naked) void Movement::PM_StepSlideMoveStub() - { - __asm - { - // Check the value of BGBounces - push ecx - push eax - - mov eax, Movement::BGBounces - mov ecx, dword ptr [eax + 0x10] - test ecx, ecx - - pop eax - pop ecx - - // Do not bounce if BGBounces is 0 - jle noBounce - - // Bounce - push 0x4B1B34 - retn - - noBounce: - // Original game code - cmp dword ptr [esp + 0x24], 0 - push 0x4B1B48 - retn - } - } - - void Movement::PM_ProjectVelocityStub(const float* velIn, const float* normal, float* velOut) - { - const auto lengthSquared2D = velIn[0] * velIn[0] + velIn[1] * velIn[1]; - - if (std::fabsf(normal[2]) < 0.001f || lengthSquared2D == 0.0) - { - velOut[0] = velIn[0]; - velOut[1] = velIn[1]; - velOut[2] = velIn[2]; - return; - } - - auto newZ = velIn[0] * normal[0] + velIn[1] * normal[1]; - newZ = -newZ / normal[2]; - const auto lengthScale = std::sqrtf((velIn[2] * velIn[2] + lengthSquared2D) - / (newZ * newZ + lengthSquared2D)); - - if (Movement::BGBouncesAllAngles.get() - || (lengthScale < 1.f || newZ < 0.f || velIn[2] > 0.f)) - { - velOut[0] = velIn[0] * lengthScale; - velOut[1] = velIn[1] * lengthScale; - velOut[2] = newZ * lengthScale; - } - } - - // Double bounces - void Movement::Jump_ClearState_Hk(Game::playerState_s* ps) - { - if (Movement::BGBounces->current.integer != Movement::DOUBLE) - { - Game::Jump_ClearState(ps); - } - } - - Game::gentity_s* Movement::Weapon_RocketLauncher_Fire_Hk(Game::gentity_s* ent, unsigned int weaponIndex, - float spread, Game::weaponParms* wp, const float* gunVel, Game::lockonFireParms* lockParms, bool a7) - { - auto* result = Game::Weapon_RocketLauncher_Fire(ent, weaponIndex, spread, wp, gunVel, lockParms, a7); - - if (ent->client != nullptr && BGRocketJump.get()) - { - ent->client->ps.velocity[0] += (0 - wp->forward[0]) * 64.0f; - ent->client->ps.velocity[1] += (0 - wp->forward[1]) * 64.0f; - ent->client->ps.velocity[2] += (0 - wp->forward[2]) * 64.0f; - } - - return result; - } - - int Movement::StuckInClient_Hk(Game::gentity_s* self) - { - if (Movement::BGPlayerEjection.get()) - { - return Utils::Hook::Call(0x402D30)(self); // StuckInClient - } - - return 0; - } - - void Movement::CM_TransformedCapsuleTrace_Hk(Game::trace_t* results, const float* start, const float* end, - const Game::Bounds* bounds, const Game::Bounds* capsule, int contents, const float* origin, const float* angles) - { - if (Movement::BGPlayerCollision.get()) - { - Utils::Hook::Call - (0x478300) - (results, start, end, bounds, capsule, contents, origin, angles); // CM_TransformedCapsuleTrace - } - } - - Game::dvar_t* Movement::Dvar_RegisterLastStandSpeedScale(const char* dvarName, float value, - float min, float max, unsigned __int16 /*flags*/, const char* description) - { - Movement::PlayerLastStandCrawlSpeedScale = Dvar::Register(dvarName, value, - min, max, Game::DVAR_CHEAT | Game::DVAR_CODINFO, description); - - return Movement::PlayerLastStandCrawlSpeedScale.get(); - } - - Game::dvar_t* Movement::Dvar_RegisterSpectateSpeedScale(const char* dvarName, float value, - float min, float max, unsigned __int16 /*flags*/, const char* description) - { - Movement::PlayerSpectateSpeedScale = Dvar::Register(dvarName, value, - min, max, Game::DVAR_CHEAT | Game::DVAR_CODINFO, description); - - return Movement::PlayerSpectateSpeedScale.get(); - } - - Movement::Movement() - { - Dvar::OnInit([] - { - static const char* bg_bouncesValues[] = - { - "disabled", - "enabled", - "double", - nullptr - }; - - Movement::PlayerDuckedSpeedScale = Dvar::Register("player_duckedSpeedScale", - 0.65f, 0.0f, 5.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, - "The scale applied to the player speed when ducking"); - - Movement::PlayerProneSpeedScale = Dvar::Register("player_proneSpeedScale", - 0.15f, 0.0f, 5.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, - "The scale applied to the player speed when crawling"); - - // 3arc naming convention - Movement::CGUfoScaler = Dvar::Register("cg_ufo_scaler", - 6.0f, 0.001f, 1000.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, - "The speed at which ufo camera moves"); - - Movement::CGNoclipScaler = Dvar::Register("cg_noclip_scaler", - 3.0f, 0.001f, 1000.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, - "The speed at which noclip camera moves"); - - Movement::BGBounces = Game::Dvar_RegisterEnum("bg_bounces", - bg_bouncesValues, Movement::DISABLED, Game::DVAR_CODINFO, "Bounce glitch settings"); - - Movement::BGBouncesAllAngles = Dvar::Register("bg_bouncesAllAngles", - false, Game::DVAR_CODINFO, "Force bounce from all angles"); - - Movement::BGRocketJump = Dvar::Register("bg_rocketJump", - false, Game::DVAR_CODINFO, "Enable CoD4 rocket jumps"); - - Movement::BGPlayerEjection = Dvar::Register("bg_playerEjection", - true, Game::DVAR_CODINFO, "Push intersecting players away from each other"); - - Movement::BGPlayerCollision = Dvar::Register("bg_playerCollision", - true, Game::DVAR_CODINFO, "Push intersecting players away from each other"); - }); - - // Hook PM_CmdScaleForStance in PM_CmdScale_Walk - Utils::Hook(0x572F34, Movement::PM_CmdScaleForStanceStub, HOOK_CALL).install()->quick(); - - //Hook PM_CmdScaleForStance in PM_GetMaxSpeed - Utils::Hook(0x57395F, Movement::PM_CmdScaleForStanceStub, HOOK_CALL).install()->quick(); - - // Hook Dvar_RegisterFloat. Only thing that's changed is that the 0x80 flag is not used. - Utils::Hook(0x448B66, Movement::Dvar_RegisterLastStandSpeedScale, HOOK_CALL).install()->quick(); - - // Hook Dvar_RegisterFloat. Only thing that's changed is that the 0x80 flag is not used. - Utils::Hook(0x448990, Movement::Dvar_RegisterSpectateSpeedScale, HOOK_CALL).install()->quick(); - - // Hook PM_MoveScale so we can add custom speed scale for Ufo and Noclip - Utils::Hook(0x56F845, Movement::PM_MoveScaleStub, HOOK_CALL).install()->quick(); - Utils::Hook(0x56FABD, Movement::PM_MoveScaleStub, HOOK_CALL).install()->quick(); - - // Bounce logic - Utils::Hook(0x4B1B2D, Movement::PM_StepSlideMoveStub, HOOK_JUMP).install()->quick(); - Utils::Hook(0x57383E, Movement::Jump_ClearState_Hk, HOOK_CALL).install()->quick(); - Utils::Hook(0x4B1B97, Movement::PM_ProjectVelocityStub, HOOK_CALL).install()->quick(); - - // Rocket jump - Utils::Hook(0x4A4F9B, Movement::Weapon_RocketLauncher_Fire_Hk, HOOK_CALL).install()->quick(); // FireWeapon - - // Hook StuckInClient & CM_TransformedCapsuleTrace - // so we can prevent intersecting players from being pushed away from each other - Utils::Hook(0x5D8153, Movement::StuckInClient_Hk, HOOK_CALL).install()->quick(); - Utils::Hook(0x45A5BF, Movement::CM_TransformedCapsuleTrace_Hk, HOOK_CALL).install()->quick(); // SV_ClipMoveToEntity - Utils::Hook(0x5A0CAD, Movement::CM_TransformedCapsuleTrace_Hk, HOOK_CALL).install()->quick(); // CG_ClipMoveToEntity - } + Dvar::Var Movement::PlayerSpectateSpeedScale; + Dvar::Var Movement::CGUfoScaler; + Dvar::Var Movement::CGNoclipScaler; + Dvar::Var Movement::BGBouncesAllAngles; + Dvar::Var Movement::BGRocketJump; + Dvar::Var Movement::BGPlayerEjection; + Dvar::Var Movement::BGPlayerCollision; + Game::dvar_t* Movement::BGBounces; + Game::dvar_t* Movement::PlayerDuckedSpeedScale; + Game::dvar_t* Movement::PlayerProneSpeedScale; + + __declspec(naked) void Movement::PM_PlayerDuckedSpeedScaleStub() + { + __asm + { + push eax + mov eax, Movement::PlayerDuckedSpeedScale + fld dword ptr [eax + 0x10] // dvar_t.current.value + pop eax + + // Game's code + pop ecx + ret + } + } + + __declspec(naked) void Movement::PM_PlayerProneSpeedScaleStub() + { + __asm + { + push eax + mov eax, Movement::PlayerProneSpeedScale + fld dword ptr [eax + 0x10] // dvar_t.current.value + pop eax + + // Game's code + pop ecx + ret + } + } + + float Movement::PM_MoveScale(Game::playerState_s* ps, float fmove, + float rmove, float umove) + { + assert(ps != nullptr); + + auto max = std::fabsf(fmove) < std::fabsf(rmove) + ? std::fabsf(rmove) : std::fabsf(fmove); + + if (std::fabsf(umove) > max) + { + max = std::fabsf(umove); + } + + if (max == 0.0f) + { + return 0.0f; + } + + auto total = std::sqrtf(fmove * fmove + + rmove * rmove + umove * umove); + auto scale = (static_cast(ps->speed) * max) / (127.0f * total); + + if (ps->pm_flags & Game::PMF_WALKING || ps->leanf != 0.0f) + { + scale *= 0.4f; + } + + switch (ps->pm_type) + { + case Game::pmtype_t::PM_NOCLIP: + scale *= Movement::CGNoclipScaler.get(); + break; + case Game::pmtype_t::PM_UFO: + scale *= Movement::CGUfoScaler.get(); + break; + case Game::pmtype_t::PM_SPECTATOR: + scale *= Movement::PlayerSpectateSpeedScale.get(); + break; + default: + break; + } + + return scale; + } + + __declspec(naked) void Movement::PM_MoveScaleStub() + { + __asm + { + pushad + + push [esp + 0xC + 0x20] // umove + push [esp + 0xC + 0x20] // rmove + push [esp + 0xC + 0x20] // fmove + push esi // ps + call Movement::PM_MoveScale + add esp, 0x10 + + popad + ret + } + } + + __declspec(naked) void Movement::PM_StepSlideMoveStub() + { + __asm + { + // Check the value of BGBounces + push ecx + push eax + + mov eax, Movement::BGBounces + mov ecx, dword ptr [eax + 0x10] + test ecx, ecx + + pop eax + pop ecx + + // Do not bounce if BGBounces is 0 + jle noBounce + + // Bounce + push 0x4B1B34 + retn + + noBounce: + // Original game code + cmp dword ptr [esp + 0x24], 0 + push 0x4B1B48 + retn + } + } + + void Movement::PM_ProjectVelocityStub(const float* velIn, const float* normal, float* velOut) + { + const auto lengthSquared2D = velIn[0] * velIn[0] + velIn[1] * velIn[1]; + + if (std::fabsf(normal[2]) < 0.001f || lengthSquared2D == 0.0) + { + velOut[0] = velIn[0]; + velOut[1] = velIn[1]; + velOut[2] = velIn[2]; + return; + } + + auto newZ = velIn[0] * normal[0] + velIn[1] * normal[1]; + newZ = -newZ / normal[2]; + const auto lengthScale = std::sqrtf((velIn[2] * velIn[2] + lengthSquared2D) + / (newZ * newZ + lengthSquared2D)); + + if (Movement::BGBouncesAllAngles.get() + || (lengthScale < 1.f || newZ < 0.f || velIn[2] > 0.f)) + { + velOut[0] = velIn[0] * lengthScale; + velOut[1] = velIn[1] * lengthScale; + velOut[2] = newZ * lengthScale; + } + } + + // Double bounces + void Movement::Jump_ClearState_Hk(Game::playerState_s* ps) + { + if (Movement::BGBounces->current.integer != Movement::DOUBLE) + { + Game::Jump_ClearState(ps); + } + } + + Game::gentity_s* Movement::Weapon_RocketLauncher_Fire_Hk(Game::gentity_s* ent, unsigned int weaponIndex, + float spread, Game::weaponParms* wp, const float* gunVel, Game::lockonFireParms* lockParms, bool a7) + { + auto* result = Game::Weapon_RocketLauncher_Fire(ent, weaponIndex, spread, wp, gunVel, lockParms, a7); + + if (ent->client != nullptr && BGRocketJump.get()) + { + ent->client->ps.velocity[0] += (0.0f - wp->forward[0]) * 64.0f; + ent->client->ps.velocity[1] += (0.0f - wp->forward[1]) * 64.0f; + ent->client->ps.velocity[2] += (0.0f - wp->forward[2]) * 64.0f; + } + + return result; + } + + int Movement::StuckInClient_Hk(Game::gentity_s* self) + { + if (Movement::BGPlayerEjection.get()) + { + return Utils::Hook::Call(0x402D30)(self); // StuckInClient + } + + return 0; + } + + void Movement::CM_TransformedCapsuleTrace_Hk(Game::trace_t* results, const float* start, const float* end, + const Game::Bounds* bounds, const Game::Bounds* capsule, int contents, const float* origin, const float* angles) + { + if (Movement::BGPlayerCollision.get()) + { + Utils::Hook::Call + (0x478300) + (results, start, end, bounds, capsule, contents, origin, angles); // CM_TransformedCapsuleTrace + } + } + + Game::dvar_t* Movement::Dvar_RegisterSpectateSpeedScale(const char* dvarName, float value, + float min, float max, unsigned __int16 /*flags*/, const char* description) + { + Movement::PlayerSpectateSpeedScale = Dvar::Register(dvarName, value, + min, max, Game::DVAR_CHEAT | Game::DVAR_CODINFO, description); + + return Movement::PlayerSpectateSpeedScale.get(); + } + + Movement::Movement() + { + Dvar::OnInit([] + { + static const char* bg_bouncesValues[] = + { + "disabled", + "enabled", + "double", + nullptr + }; + + Movement::PlayerDuckedSpeedScale = Game::Dvar_RegisterFloat("player_duckedSpeedScale", + 0.65f, 0.0f, 5.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, + "The scale applied to the player speed when ducking"); + + Movement::PlayerProneSpeedScale = Game::Dvar_RegisterFloat("player_proneSpeedScale", + 0.15f, 0.0f, 5.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, + "The scale applied to the player speed when crawling"); + + // 3arc naming convention + Movement::CGUfoScaler = Dvar::Register("cg_ufo_scaler", + 6.0f, 0.001f, 1000.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, + "The speed at which ufo camera moves"); + + Movement::CGNoclipScaler = Dvar::Register("cg_noclip_scaler", + 3.0f, 0.001f, 1000.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, + "The speed at which noclip camera moves"); + + Movement::BGBounces = Game::Dvar_RegisterEnum("bg_bounces", + bg_bouncesValues, Movement::DISABLED, Game::DVAR_CODINFO, "Bounce glitch settings"); + + Movement::BGBouncesAllAngles = Dvar::Register("bg_bouncesAllAngles", + false, Game::DVAR_CODINFO, "Force bounce from all angles"); + + Movement::BGRocketJump = Dvar::Register("bg_rocketJump", + false, Game::DVAR_CODINFO, "Enable CoD4 rocket jumps"); + + Movement::BGPlayerEjection = Dvar::Register("bg_playerEjection", + true, Game::DVAR_CODINFO, "Push intersecting players away from each other"); + + Movement::BGPlayerCollision = Dvar::Register("bg_playerCollision", + true, Game::DVAR_CODINFO, "Push intersecting players away from each other"); + }); + + // Hook Dvar_RegisterFloat. Only thing that's changed is that the 0x80 flag is not used. + Utils::Hook(0x448990, Movement::Dvar_RegisterSpectateSpeedScale, HOOK_CALL).install()->quick(); + + // PM_CmdScaleForStance + Utils::Hook(0x572D9B, Movement::PM_PlayerDuckedSpeedScaleStub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x572DA5, Movement::PM_PlayerProneSpeedScaleStub, HOOK_JUMP).install()->quick(); + + // Hook PM_MoveScale so we can add custom speed scale for Ufo and Noclip + Utils::Hook(0x56F845, Movement::PM_MoveScaleStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x56FABD, Movement::PM_MoveScaleStub, HOOK_CALL).install()->quick(); + + // Bounce logic + Utils::Hook(0x4B1B2D, Movement::PM_StepSlideMoveStub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x57383E, Movement::Jump_ClearState_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x4B1B97, Movement::PM_ProjectVelocityStub, HOOK_CALL).install()->quick(); + + // Rocket jump + Utils::Hook(0x4A4F9B, Movement::Weapon_RocketLauncher_Fire_Hk, HOOK_CALL).install()->quick(); // FireWeapon + + // Hook StuckInClient & CM_TransformedCapsuleTrace + // so we can prevent intersecting players from being pushed away from each other + Utils::Hook(0x5D8153, Movement::StuckInClient_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x45A5BF, Movement::CM_TransformedCapsuleTrace_Hk, HOOK_CALL).install()->quick(); // SV_ClipMoveToEntity + Utils::Hook(0x5A0CAD, Movement::CM_TransformedCapsuleTrace_Hk, HOOK_CALL).install()->quick(); // CG_ClipMoveToEntity + } } diff --git a/src/Components/Modules/Movement.hpp b/src/Components/Modules/Movement.hpp index 2b718258..72aa3347 100644 --- a/src/Components/Modules/Movement.hpp +++ b/src/Components/Modules/Movement.hpp @@ -2,45 +2,43 @@ namespace Components { - class Movement : public Component - { - public: - Movement(); + class Movement : public Component + { + public: + Movement(); - private: - enum BouncesSettings { DISABLED, ENABLED, DOUBLE }; + private: + enum BouncesSettings { DISABLED, ENABLED, DOUBLE }; - static Dvar::Var PlayerDuckedSpeedScale; - static Dvar::Var PlayerLastStandCrawlSpeedScale; - static Dvar::Var PlayerProneSpeedScale; - static Dvar::Var PlayerSpectateSpeedScale; - static Dvar::Var CGUfoScaler; - static Dvar::Var CGNoclipScaler; - static Dvar::Var BGBouncesAllAngles; - static Dvar::Var BGRocketJump; - static Dvar::Var BGPlayerEjection; - static Dvar::Var BGPlayerCollision; - // Can't use Var class inside assembly stubs - static Game::dvar_t* BGBounces; + static Dvar::Var PlayerSpectateSpeedScale; + static Dvar::Var CGUfoScaler; + static Dvar::Var CGNoclipScaler; + static Dvar::Var BGBouncesAllAngles; + static Dvar::Var BGRocketJump; + static Dvar::Var BGPlayerEjection; + static Dvar::Var BGPlayerCollision; + // Can't use Var class inside assembly stubs + static Game::dvar_t* BGBounces; + static Game::dvar_t* PlayerDuckedSpeedScale; + static Game::dvar_t* PlayerProneSpeedScale; - static float PM_CmdScaleForStance(const Game::pmove_s* move); - static void PM_CmdScaleForStanceStub(); + static void PM_PlayerDuckedSpeedScaleStub(); + static void PM_PlayerProneSpeedScaleStub(); - static float PM_MoveScale(Game::playerState_s* ps, float forwardmove, float rightmove, float upmove); - static void PM_MoveScaleStub(); + static float PM_MoveScale(Game::playerState_s* ps, float fmove, float rmove, float umove); + static void PM_MoveScaleStub(); - // Bounce logic - static void PM_StepSlideMoveStub(); - static void PM_ProjectVelocityStub(const float* velIn, const float* normal, float* velOut); - static void Jump_ClearState_Hk(Game::playerState_s* ps); + // Bounce logic + static void PM_StepSlideMoveStub(); + static void PM_ProjectVelocityStub(const float* velIn, const float* normal, float* velOut); + static void Jump_ClearState_Hk(Game::playerState_s* ps); - static Game::gentity_s* Weapon_RocketLauncher_Fire_Hk(Game::gentity_s* ent, unsigned int weaponIndex, float spread, Game::weaponParms* wp, const float* gunVel, Game::lockonFireParms* lockParms, bool a7); + static Game::gentity_s* Weapon_RocketLauncher_Fire_Hk(Game::gentity_s* ent, unsigned int weaponIndex, float spread, Game::weaponParms* wp, const float* gunVel, Game::lockonFireParms* lockParms, bool a7); - // Player collison - static int StuckInClient_Hk(Game::gentity_s* self); - static void CM_TransformedCapsuleTrace_Hk(Game::trace_t* results, const float* start, const float* end, const Game::Bounds* bounds, const Game::Bounds* capsule, int contents, const float* origin, const float* angles); + // Player collison + static int StuckInClient_Hk(Game::gentity_s* self); + static void CM_TransformedCapsuleTrace_Hk(Game::trace_t* results, const float* start, const float* end, const Game::Bounds* bounds, const Game::Bounds* capsule, int contents, const float* origin, const float* angles); - static Game::dvar_t* Dvar_RegisterLastStandSpeedScale(const char* dvarName, float value, float min, float max, unsigned __int16 flags, const char* description); - static Game::dvar_t* Dvar_RegisterSpectateSpeedScale(const char* dvarName, float value, float min, float max, unsigned __int16 flags, const char* description); - }; + static Game::dvar_t* Dvar_RegisterSpectateSpeedScale(const char* dvarName, float value, float min, float max, unsigned __int16 flags, const char* description); + }; } diff --git a/src/Components/Modules/Network.cpp b/src/Components/Modules/Network.cpp index cd573569..ee44d739 100644 --- a/src/Components/Modules/Network.cpp +++ b/src/Components/Modules/Network.cpp @@ -10,48 +10,59 @@ namespace Components { Game::NET_StringToAdr(addrString.data(), &this->address); } + Network::Address::Address(sockaddr* addr) { Game::SockadrToNetadr(addr, &this->address); } + bool Network::Address::operator==(const Network::Address& obj) const { return Game::NET_CompareAdr(this->address, obj.address); } + void Network::Address::setPort(unsigned short port) { this->address.port = htons(port); } + unsigned short Network::Address::getPort() { return ntohs(this->address.port); } + void Network::Address::setIP(DWORD ip) { this->address.ip.full = ip; } + void Network::Address::setIP(Game::netIP_t ip) { this->address.ip = ip; } + Game::netIP_t Network::Address::getIP() { return this->address.ip; } + void Network::Address::setType(Game::netadrtype_t type) { this->address.type = type; } + Game::netadrtype_t Network::Address::getType() { return this->address.type; } + sockaddr Network::Address::getSockAddr() { sockaddr addr; this->toSockAddr(&addr); return addr; } + void Network::Address::toSockAddr(sockaddr* addr) { if (addr) @@ -59,22 +70,27 @@ namespace Components Game::NetadrToSockadr(&this->address, addr); } } + void Network::Address::toSockAddr(sockaddr_in* addr) { this->toSockAddr(reinterpret_cast(addr)); } + Game::netadr_t* Network::Address::get() { return &this->address; } + const char* Network::Address::getCString() const { return Game::NET_AdrToString(this->address); } + std::string Network::Address::getString() const { return this->getCString(); } + bool Network::Address::isLocal() { // According to: https://en.wikipedia.org/wiki/Private_network @@ -95,6 +111,7 @@ namespace Components return false; } + bool Network::Address::isSelf() { if (Game::NET_IsLocalAddress(this->address)) return true; // Loopback @@ -110,6 +127,7 @@ namespace Components return false; } + bool Network::Address::isLoopback() { if (this->getIP().full == 0x100007f) // 127.0.0.1 @@ -119,10 +137,12 @@ namespace Components return Game::NET_IsLocalAddress(this->address); } + bool Network::Address::isValid() { return (this->getType() != Game::netadrtype_t::NA_BAD && this->getType() >= Game::netadrtype_t::NA_BOT && this->getType() <= Game::netadrtype_t::NA_IP); } + void Network::Handle(const std::string& packet, Utils::Slot callback) { Network::PacketHandlers[Utils::String::ToLower(packet)] = callback; diff --git a/src/Components/Modules/Node.cpp b/src/Components/Modules/Node.cpp index 50797f93..60d1d18b 100644 --- a/src/Components/Modules/Node.cpp +++ b/src/Components/Modules/Node.cpp @@ -167,6 +167,7 @@ namespace Components void Node::RunFrame() { + if (ServerList::useMasterServer) return; if (Dedicated::IsEnabled() && Dvar::Var("sv_lanOnly").get()) return; if (!Dedicated::IsEnabled() && *Game::clcState > 0) @@ -246,7 +247,7 @@ namespace Components if (list.isnode() && (!list.port() || list.port() == address.getPort())) { - if (!Dedicated::IsEnabled() && ServerList::IsOnlineList() && list.protocol() == PROTOCOL) + if (!Dedicated::IsEnabled() && ServerList::IsOnlineList() && !ServerList::useMasterServer && list.protocol() == PROTOCOL) { NODE_LOG("Inserting %s into the serverlist\n", address.getCString()); ServerList::InsertRequest(address); diff --git a/src/Components/Modules/Party.cpp b/src/Components/Modules/Party.cpp index 564ec01b..6be17612 100644 --- a/src/Components/Modules/Party.cpp +++ b/src/Components/Modules/Party.cpp @@ -307,7 +307,7 @@ namespace Components { int botCount = 0; int clientCount = 0; - int maxclientCount = *Game::svs_numclients; + int maxclientCount = *Game::svs_clientCount; if (maxclientCount) { @@ -315,7 +315,7 @@ namespace Components { if (Game::svs_clients[i].state >= 3) { - if (Game::svs_clients[i].isBot) ++botCount; + if (Game::svs_clients[i].bIsTestClient) ++botCount; else ++clientCount; } } diff --git a/src/Components/Modules/PlayerName.cpp b/src/Components/Modules/PlayerName.cpp index d90cadf7..5f29acc0 100644 --- a/src/Components/Modules/PlayerName.cpp +++ b/src/Components/Modules/PlayerName.cpp @@ -8,7 +8,7 @@ namespace Components { if (!sv_allowColoredNames.get()) { - char nameBuffer[64] = { 0 }; + char nameBuffer[64] = {0}; TextRenderer::StripColors(name, nameBuffer, sizeof(nameBuffer)); TextRenderer::StripAllTextIcons(nameBuffer, buffer, size); } @@ -26,12 +26,12 @@ namespace Components } } - __declspec(naked) void PlayerName::ClientUserinfoChanged() + __declspec(naked) void PlayerName::ClientCleanName() { __asm { mov eax, [esp + 4h] // length - //sub eax, 1 + push eax push ecx // name @@ -53,12 +53,57 @@ namespace Components return buf; } + char* PlayerName::CleanStrStub(char* string) { TextRenderer::StripColors(string, string, strlen(string) + 1); return string; } + bool PlayerName::CopyClientNameCheck(char* dest, const char* source, int size) + { + Utils::Hook::Call(0x4D6F80)(dest, source, size); // I_strncpyz + + auto i = 0; + while (i < size - 1 && dest[i] != '\0') + { + if (dest[i] > 125 || dest[i] < 32 || dest[i] == '%') + { + return false; // Illegal string + } + + ++i; + } + + return true; + } + + __declspec(naked) void PlayerName::SV_UserinfoChangedStub() + { + __asm + { + call CopyClientNameCheck + test al, al + + jnz returnSafe + + pushad + + push 1 // tellThem + push INVALID_NAME_MSG // reason + push edi // drop + mov eax, 0x4D1600 // SV_DropClient + call eax + add esp, 0xC + + popad + + returnSafe: + push 0x401988 + retn + } + } + PlayerName::PlayerName() { sv_allowColoredNames = Dvar::Register("sv_allowColoredNames", true, Game::dvar_flag::DVAR_NONE, "Allow colored names on the server"); @@ -66,13 +111,17 @@ namespace Components // Disable SV_UpdateUserinfo_f, to block changing the name ingame Utils::Hook::Set(0x6258D0, 0xC3); - // Allow colored names ingame - Utils::Hook(0x5D8B40, ClientUserinfoChanged, HOOK_JUMP).install()->quick(); + // Allow colored names ingame. Hook placed in ClientUserinfoChanged + Utils::Hook(0x5D8B40, ClientCleanName, HOOK_JUMP).install()->quick(); // Though, don't apply that to overhead names. Utils::Hook(0x581932, GetClientName, HOOK_CALL).install()->quick(); // Patch I_CleanStr Utils::Hook(0x4AD470, CleanStrStub, HOOK_JUMP).install()->quick(); + + // Detect invalid characters including '%' to prevent format string vulnerabilities. + // Kicks the player as soon as possible + Utils::Hook(0x401983, SV_UserinfoChangedStub, HOOK_JUMP).install()->quick(); } } diff --git a/src/Components/Modules/PlayerName.hpp b/src/Components/Modules/PlayerName.hpp index 8335afda..b1beaaa7 100644 --- a/src/Components/Modules/PlayerName.hpp +++ b/src/Components/Modules/PlayerName.hpp @@ -11,9 +11,14 @@ namespace Components private: static Dvar::Var sv_allowColoredNames; + // Message used when kicking players + static constexpr auto INVALID_NAME_MSG = "Invalid name detected"; static char* CleanStrStub(char* string); - static void ClientUserinfoChanged(); + static void ClientCleanName(); static char* GetClientName(int localClientNum, int index, char* buf, size_t size); + + static bool CopyClientNameCheck(char* dest, const char* source, int size); + static void SV_UserinfoChangedStub(); }; } diff --git a/src/Components/Modules/QuickPatch.cpp b/src/Components/Modules/QuickPatch.cpp index de055020..92e09546 100644 --- a/src/Components/Modules/QuickPatch.cpp +++ b/src/Components/Modules/QuickPatch.cpp @@ -47,62 +47,6 @@ namespace Components } } - int QuickPatch::MsgReadBitsCompressCheckSV(const char *from, char *to, int size) - { - static char buffer[0x8000]; - - if (size > 0x800) return 0; - size = Game::MSG_ReadBitsCompress(from, buffer, size); - - if (size > 0x800) return 0; - std::memcpy(to, buffer, size); - - return size; - } - - int QuickPatch::MsgReadBitsCompressCheckCL(const char *from, char *to, int size) - { - static char buffer[0x100000]; - - if (size > 0x20000) return 0; - size = Game::MSG_ReadBitsCompress(from, buffer, size); - - if (size > 0x20000) return 0; - std::memcpy(to, buffer, size); - - return size; - } - - int QuickPatch::SVCanReplaceServerCommand(Game::client_t* /*client*/, const char* /*cmd*/) - { - // This is a fix copied from V2. As I don't have time to investigate, let's simply trust them - return -1; - } - - long QuickPatch::AtolAdjustPlayerLimit(const char* string) - { - return std::min(atol(string), 18l); - } - - void QuickPatch::SelectStringTableEntryInDvarStub() - { - Command::ClientParams params; - - if (params.size() >= 4) - { - const auto* dvarName = params[3]; - const auto* dvar = Game::Dvar_FindVar(dvarName); - - if (Command::Find(dvarName) || - (dvar != nullptr && dvar->flags & (Game::DVAR_WRITEPROTECTED | Game::DVAR_CHEAT | Game::DVAR_READONLY))) - { - return; - } - } - - Game::CL_SelectStringTableEntryInDvar_f(); - } - __declspec(naked) void QuickPatch::JavelinResetHookStub() { __asm @@ -117,69 +61,6 @@ namespace Components } } - __declspec(naked) int QuickPatch::G_GetClientScore() - { - __asm - { - mov eax, [esp + 4] // index - mov ecx, ds : 1A831A8h // level: &g_clients - - test ecx, ecx; - jz invalid_ptr; - - imul eax, 366Ch - mov eax, [eax + ecx + 3134h] - ret - - invalid_ptr: - xor eax, eax - ret - } - } - - bool QuickPatch::InvalidNameCheck(char* dest, const char* source, int size) - { - Utils::Hook::Call(0x4D6F80)(dest, source, size); // I_strncpyz - - for (int i = 0; i < size - 1; i++) - { - if (!dest[i]) break; - - if (dest[i] > 125 || dest[i] < 32 || dest[i] == '%') - { - return false; - } - } - - return true; - } - - __declspec(naked) void QuickPatch::InvalidNameStub() - { - static const char* kick_reason = "Invalid name detected."; - - __asm - { - call InvalidNameCheck; - test al, al - - jnz returnSafe; - - pushad; - push 1; - push kick_reason; - push edi; - mov eax, 0x004D1600; // SV_DropClientInternal - call eax; - add esp, 12; - popad; - - returnSafe: - push 0x00401988; - retn; - } - } - Game::dvar_t* QuickPatch::g_antilag; __declspec(naked) void QuickPatch::ClientEventsFireWeaponStub() { @@ -378,9 +259,6 @@ namespace Components Utils::Hook(0x5D6D56, QuickPatch::ClientEventsFireWeaponStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x5D6D6A, QuickPatch::ClientEventsFireWeaponMeleeStub, HOOK_JUMP).install()->quick(); - // Disallow invalid player names - Utils::Hook(0x401983, QuickPatch::InvalidNameStub, HOOK_JUMP).install()->quick(); - // Javelin fix Utils::Hook(0x578F52, QuickPatch::JavelinResetHookStub, HOOK_JUMP).install()->quick(); @@ -644,21 +522,6 @@ namespace Components } }); - // Exploit fixes - Utils::Hook::Set(0x412370, 0xC3); // SV_SteamAuthClient - Utils::Hook::Set(0x5A8C70, 0xC3); // CL_HandleRelayPacket - Utils::Hook(0x414D92, QuickPatch::MsgReadBitsCompressCheckSV, HOOK_CALL).install()->quick(); // SV_ExecuteClientCommands - Utils::Hook(0x4A9F56, QuickPatch::MsgReadBitsCompressCheckCL, HOOK_CALL).install()->quick(); // CL_ParseServerMessage - Utils::Hook(0x407376, QuickPatch::SVCanReplaceServerCommand , HOOK_CALL).install()->quick(); // SV_CanReplaceServerCommand - Utils::Hook(0x5B67ED, QuickPatch::AtolAdjustPlayerLimit , HOOK_CALL).install()->quick(); // PartyHost_HandleJoinPartyRequest - Utils::Hook::Nop(0x41698E, 5); // Disable Svcmd_EntityList_f - - // Patch selectStringTableEntryInDvar - Utils::Hook::Set(0x405959, QuickPatch::SelectStringTableEntryInDvarStub); - - // Patch G_GetClientScore for uninitialised game - Utils::Hook(0x469AC0, QuickPatch::G_GetClientScore, HOOK_JUMP).install()->quick(); - // Ignore call to print 'Offhand class mismatch when giving weapon...' Utils::Hook(0x5D9047, 0x4BB9B0, HOOK_CALL).install()->quick(); @@ -858,9 +721,6 @@ namespace Components }); #endif - // Dvars - Dvar::Register("ui_streamFriendly", false, Game::DVAR_ARCHIVE, "Stream friendly UI"); - // Debug patches #ifdef DEBUG // ui_debugMode 1 diff --git a/src/Components/Modules/QuickPatch.hpp b/src/Components/Modules/QuickPatch.hpp index 6eac85a2..62377cfd 100644 --- a/src/Components/Modules/QuickPatch.hpp +++ b/src/Components/Modules/QuickPatch.hpp @@ -12,21 +12,8 @@ namespace Components static void UnlockStats(); private: - static void SelectStringTableEntryInDvarStub(); - - static int SVCanReplaceServerCommand(Game::client_t *client, const char *cmd); - static int G_GetClientScore(); - - static int MsgReadBitsCompressCheckSV(const char *from, char *to, int size); - static int MsgReadBitsCompressCheckCL(const char *from, char *to, int size); - - static long AtolAdjustPlayerLimit(const char* string); - static void JavelinResetHookStub(); - static bool InvalidNameCheck(char* dest, const char* source, int size); - static void InvalidNameStub(); - static Dvar::Var r_customAspectRatio; static Game::dvar_t* Dvar_RegisterAspectRatioDvar(const char* dvarName, const char** valueList, int defaultIndex, unsigned __int16 flags, const char* description); static void SetAspectRatioStub(); diff --git a/src/Components/Modules/RawMouse.cpp b/src/Components/Modules/RawMouse.cpp new file mode 100644 index 00000000..3f95a874 --- /dev/null +++ b/src/Components/Modules/RawMouse.cpp @@ -0,0 +1,161 @@ +#include + +namespace Components +{ + Dvar::Var RawMouse::M_RawInput; + int RawMouse::MouseRawX = 0; + int RawMouse::MouseRawY = 0; + + void RawMouse::IN_ClampMouseMove() + { + tagRECT rc; + tagPOINT curPos; + + GetCursorPos(&curPos); + GetWindowRect(Window::GetWindow(), &rc); + auto isClamped = false; + if (curPos.x >= rc.left) + { + if (curPos.x >= rc.right) + { + curPos.x = rc.right - 1; + isClamped = true; + } + } + else + { + curPos.x = rc.left; + isClamped = true; + } + if (curPos.y >= rc.top) + { + if (curPos.y >= rc.bottom) + { + curPos.y = rc.bottom - 1; + isClamped = true; + } + } + else + { + curPos.y = rc.top; + isClamped = true; + } + + if (isClamped) + { + SetCursorPos(curPos.x, curPos.y); + } + } + + BOOL RawMouse::OnRawInput(LPARAM lParam, WPARAM) + { + auto dwSize = sizeof(RAWINPUT); + static BYTE lpb[sizeof(RAWINPUT)]; + + GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)); + + auto* raw = reinterpret_cast(lpb); + if (raw->header.dwType == RIM_TYPEMOUSE) + { + // Is there's really absolute mouse on earth? + if (raw->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) + { + MouseRawX = raw->data.mouse.lLastX; + MouseRawY = raw->data.mouse.lLastY; + } + else + { + MouseRawX += raw->data.mouse.lLastX; + MouseRawY += raw->data.mouse.lLastY; + } + } + + return TRUE; + } + + void RawMouse::IN_RawMouseMove() + { + static auto r_fullscreen = Dvar::Var("r_fullscreen"); + + if (GetForegroundWindow() == Window::GetWindow()) + { + if (r_fullscreen.get()) + IN_ClampMouseMove(); + + static auto oldX = 0, oldY = 0; + + auto dx = MouseRawX - oldX; + auto dy = MouseRawY - oldY; + + oldX = MouseRawX; + oldY = MouseRawY; + + // Don't use raw input for menu? + // Because it needs to call the ScreenToClient + tagPOINT curPos; + GetCursorPos(&curPos); + Game::s_wmv->oldPos = curPos; + ScreenToClient(Window::GetWindow(), &curPos); + + auto recenterMouse = Game::CL_MouseEvent(curPos.x, curPos.y, dx, dy); + + if (recenterMouse) + { + Game::IN_RecenterMouse(); + } + } + } + + void RawMouse::IN_RawMouse_Init() + { + if (Window::GetWindow() && RawMouse::M_RawInput.get()) + { +#ifdef DEBUG + Logger::Print("Raw Mouse Init.\n"); +#endif + + RAWINPUTDEVICE Rid[1]; + Rid[0].usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC + Rid[0].usUsage = 0x02; // HID_USAGE_GENERIC_MOUSE + Rid[0].dwFlags = RIDEV_INPUTSINK; + Rid[0].hwndTarget = Window::GetWindow(); + + RegisterRawInputDevices(Rid, ARRAYSIZE(Rid), sizeof(Rid[0])); + } + } + + void RawMouse::IN_Init() + { + Game::IN_Init(); + IN_RawMouse_Init(); + } + + void RawMouse::IN_MouseMove() + { + if (RawMouse::M_RawInput.get()) + { + IN_RawMouseMove(); + } + else + { + Game::IN_MouseMove(); + } + } + + RawMouse::RawMouse() + { + Utils::Hook(0x475E65, RawMouse::IN_MouseMove, HOOK_JUMP).install()->quick(); + Utils::Hook(0x475E8D, RawMouse::IN_MouseMove, HOOK_JUMP).install()->quick(); + + Utils::Hook(0x467C03, RawMouse::IN_Init, HOOK_CALL).install()->quick(); + Utils::Hook(0x64D095, RawMouse::IN_Init, HOOK_JUMP).install()->quick(); + + Dvar::OnInit([]() + { + RawMouse::M_RawInput = Dvar::Register("m_rawinput", true, Game::dvar_flag::DVAR_ARCHIVE, "Use raw mouse input, Improves accuracy & has better support for higher polling rates. Use in_restart to take effect if not enabled."); + }); + + Window::OnWndMessage(WM_INPUT, RawMouse::OnRawInput); + Window::OnCreate(RawMouse::IN_RawMouse_Init); + } +} diff --git a/src/Components/Modules/RawMouse.hpp b/src/Components/Modules/RawMouse.hpp new file mode 100644 index 00000000..28ed04da --- /dev/null +++ b/src/Components/Modules/RawMouse.hpp @@ -0,0 +1,22 @@ +#pragma once + +namespace Components +{ + class RawMouse : public Component + { + public: + RawMouse(); + + static void IN_MouseMove(); + + private: + static Dvar::Var M_RawInput; + static int MouseRawX, MouseRawY; + + static void IN_ClampMouseMove(); + static BOOL OnRawInput(LPARAM lParam, WPARAM); + static void IN_RawMouseMove(); + static void IN_RawMouse_Init(); + static void IN_Init(); + }; +} diff --git a/src/Components/Modules/Renderer.cpp b/src/Components/Modules/Renderer.cpp index efa91e94..7eaaabd7 100644 --- a/src/Components/Modules/Renderer.cpp +++ b/src/Components/Modules/Renderer.cpp @@ -502,7 +502,7 @@ namespace Components // } // }); - // Log broken materials + // Log broken materials Utils::Hook(0x0054CAAA, Renderer::StoreGfxBufContextPtrStub1, HOOK_JUMP).install()->quick(); Utils::Hook(0x0054CF8D, Renderer::StoreGfxBufContextPtrStub2, HOOK_JUMP).install()->quick(); diff --git a/src/Components/Modules/Renderer.hpp b/src/Components/Modules/Renderer.hpp index 553dd336..9e43a154 100644 --- a/src/Components/Modules/Renderer.hpp +++ b/src/Components/Modules/Renderer.hpp @@ -18,7 +18,6 @@ namespace Components static void OnDeviceRecoveryEnd(Utils::Slot callback); static void OnDeviceRecoveryBegin(Utils::Slot callback); - private: static void FrameStub(); diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 5dc331a3..798a2541 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -4,29 +4,30 @@ namespace Components { std::string Script::ScriptName; std::vector Script::ScriptHandles; - std::vector Script::ScriptFunctions; + std::unordered_map Script::CustomScrFunctions; + std::unordered_map Script::CustomScrMethods; std::vector Script::ScriptNameStack; unsigned short Script::FunctionName; std::unordered_map Script::ScriptStorage; std::unordered_map Script::ScriptBaseProgramNum; std::unordered_map Script::ReplacedFunctions; - const char* Script::ReplacedPos = 0; + const char* Script::ReplacedPos = nullptr; int Script::LastFrameTime = -1; Utils::Signal Script::VMShutdownSignal; void Script::FunctionError() { - std::string funcName = Game::SL_ConvertToString(Script::FunctionName); + const auto* funcName = Game::SL_ConvertToString(Script::FunctionName); Game::Scr_ShutdownAllocNode(); Logger::Print(23, "\n"); Logger::Print(23, "******* script compile error *******\n"); - Logger::Print(23, "Error: unknown function %s in %s\n", funcName.data(), Script::ScriptName.data()); + Logger::Print(23, "Error: unknown function %s in %s\n", funcName, Script::ScriptName.data()); Logger::Print(23, "************************************\n"); - Logger::Error(Game::ERR_SCRIPT_DROP, "script compile error\nunknown function %s\n%s\n\n", funcName.data(), Script::ScriptName.data()); + Logger::Error(Game::ERR_SCRIPT_DROP, "script compile error\nunknown function %s\n%s\n\n", funcName, Script::ScriptName.data()); } __declspec(naked) void Script::StoreFunctionNameStub() @@ -45,6 +46,37 @@ namespace Components } } + void Script::RuntimeError(const char* codePos, unsigned int index, const char* msg, const char* dialogMessage) + { + const auto developer = Dvar::Var("developer").get(); + + // Allow error messages to be printed if developer mode is on + // Should check scrVarPub.developer but it's absent + // in this version of the game so let's check the dvar + if (!Game::scrVmPub->terminal_error && !developer) + return; + + // If were are developing let's call RuntimeErrorInternal + // scrVmPub.debugCode seems to be always false + if (Game::scrVmPub->debugCode || Game::scrVarPub->developer_script) + { + Game::RuntimeErrorInternal(23, codePos, index, msg); + } + else + { + Logger::Print(23, "%s\n", msg); + } + + // Let's not throw error unless we have to + if (Game::scrVmPub->terminal_error) + { + if (dialogMessage == nullptr) + dialogMessage = ""; + + Logger::Error(Game::ERR_SCRIPT_DROP, "\x15script runtime error\n(see console for details)\n%s\n%s", msg, dialogMessage); + } + } + void Script::StoreScriptName(const char* name) { Script::ScriptNameStack.push_back(Script::ScriptName); @@ -108,11 +140,9 @@ namespace Components std::string buffer = script.getBuffer(); Utils::String::Replace(buffer, "\t", " "); - int line = 1; - int lineOffset = 0; - int inlineOffset = 0; + auto line = 1, lineOffset = 0, inlineOffset = 0; - for (unsigned int i = 0; i < buffer.size(); ++i) + for (size_t i = 0; i < buffer.size(); ++i) { // Terminate line if (i == offset) @@ -129,7 +159,7 @@ namespace Components if (buffer[i] == '\n') { ++line; - lineOffset = i; // Includes the line break! + lineOffset = static_cast(i); // Includes the line break! inlineOffset = 0; } else @@ -141,7 +171,7 @@ namespace Components Logger::Print(23, "in file %s, line %d:", filename, line); Logger::Print(23, "%s\n", buffer.data() + lineOffset); - for (int i = 0; i < (inlineOffset - 1); ++i) + for (auto i = 0; i < (inlineOffset - 1); ++i) { Logger::Print(23, " "); } @@ -156,11 +186,11 @@ namespace Components void Script::CompileError(unsigned int offset, const char* message, ...) { - char msgbuf[1024] = { 0 }; - va_list v; - va_start(v, message); - _vsnprintf_s(msgbuf, sizeof(msgbuf), message, v); - va_end(v); + char msgbuf[1024] = {0}; + va_list va; + va_start(va, message); + _vsnprintf_s(msgbuf, _TRUNCATE, message, va); + va_end(va); Game::Scr_ShutdownAllocNode(); @@ -180,7 +210,7 @@ namespace Components if (!Game::Scr_LoadScript(script.data())) { Logger::Print("Script %s encountered an error while loading. (doesn't exist?)", script.data()); - Logger::Error(Game::ERR_DROP, reinterpret_cast(0x70B810), script.data()); + Logger::Error(Game::ERR_DROP, reinterpret_cast(0x70B810), script.data()); } else { @@ -188,7 +218,7 @@ namespace Components } Logger::Print("Finding script handle %s::%s...\n", script.data(), label.data()); - int handle = Game::Scr_GetFunctionHandle(script.data(), label.data()); + const auto handle = Game::Scr_GetFunctionHandle(script.data(), label.data()); if (handle) { Logger::Print("Script handle %s::%s loaded successfully.\n", script.data(), label.data()); @@ -201,7 +231,7 @@ namespace Components void Script::LoadGameType() { - for (auto handle : Script::ScriptHandles) + for (const auto& handle : Script::ScriptHandles) { Game::Scr_FreeThread(Game::Scr_ExecThread(handle, 0)); } @@ -213,19 +243,23 @@ namespace Components { Script::ScriptHandles.clear(); - auto list = FileSystem::GetFileList("scripts/", "gsc"); + const auto list = FileSystem::GetFileList("scripts/", "gsc"); for (auto file : list) { - file = "scripts/" + file; + file.insert(0, "scripts/"); if (Utils::String::EndsWith(file, ".gsc")) { file = file.substr(0, file.size() - 4); } - int handle = Script::LoadScriptAndLabel(file, "init"); - if (handle) Script::ScriptHandles.push_back(handle); + auto handle = Script::LoadScriptAndLabel(file, "init"); + + if (handle) + { + Script::ScriptHandles.push_back(handle); + } else { handle = Script::LoadScriptAndLabel(file, "main"); @@ -236,61 +270,72 @@ namespace Components Game::GScr_LoadGameTypeScript(); } - void Script::AddFunction(const std::string& name, Game::scr_function_t function, bool isDev) + void Script::AddFunction(const char* name, Game::BuiltinFunction func, int type) { - for (auto i = Script::ScriptFunctions.begin(); i != Script::ScriptFunctions.end();) - { - if (i->getName() == name) - { - i = Script::ScriptFunctions.erase(i); - continue; - } + Game::BuiltinFunctionDef toAdd; + toAdd.actionString = name; + toAdd.actionFunc = func; + toAdd.type = type; - ++i; - } - - Script::ScriptFunctions.push_back({ name, function, isDev }); + CustomScrFunctions.insert_or_assign(Utils::String::ToLower(name), toAdd); } - Game::scr_function_t Script::GetFunction(void* caller, const char** name, int* isDev) + void Script::AddMethod(const char* name, Game::BuiltinMethod func, int type) { - for (auto& function : Script::ScriptFunctions) - { - if (name && *name) - { - if (Utils::String::ToLower(*name) == Utils::String::ToLower(function.getName())) - { - *name = function.getName(); - *isDev = function.isDev(); - return function.getFunction(); - } - } - else if (caller == reinterpret_cast(0x465781)) - { - Game::Scr_RegisterFunction(function.getFunction()); - } - } + Game::BuiltinMethodDef toAdd; + toAdd.actionString = name; + toAdd.actionFunc = func; + toAdd.type = type; - return nullptr; + CustomScrMethods.insert_or_assign(Utils::String::ToLower(name), toAdd); } - __declspec(naked) void Script::GetFunctionStub() + Game::BuiltinFunction Script::BuiltIn_GetFunctionStub(const char** pName, int* type) { - __asm + if (pName != nullptr) { - test eax, eax - jnz returnSafe + const auto got = Script::CustomScrFunctions.find(Utils::String::ToLower(*pName)); - sub esp, 8h - push [esp + 10h] - call Script::GetFunction - add esp, 0Ch - - returnSafe: - pop edi - pop esi - retn + // If no function was found let's call game's function + if (got != Script::CustomScrFunctions.end()) + { + *type = got->second.type; + return got->second.actionFunc; + } } + else + { + for (const auto& [name, builtin] : Script::CustomScrFunctions) + { + Game::Scr_RegisterFunction(reinterpret_cast(builtin.actionFunc), name.data()); + } + } + + return Utils::Hook::Call(0x5FA2B0)(pName, type); // BuiltIn_GetFunction + } + + Game::BuiltinMethod Script::BuiltIn_GetMethod(const char** pName, int* type) + { + if (pName != nullptr) + { + const auto got = Script::CustomScrMethods.find(Utils::String::ToLower(*pName)); + + // If no method was found let's call game's function + if (got != Script::CustomScrMethods.end()) + { + *type = got->second.type; + return got->second.actionFunc; + } + } + else + { + for (const auto& [name, builtin] : Script::CustomScrMethods) + { + Game::Scr_RegisterFunction(reinterpret_cast(builtin.actionFunc), name.data()); + } + } + + return Utils::Hook::Call(0x5FA360)(pName, type); // Player_GetMethod } void Script::StoreScriptBaseProgramNum() @@ -300,14 +345,12 @@ namespace Components void Script::Scr_PrintPrevCodePos(int scriptPos) { - int bestCodePos = -1; - int nextCodePos = -1; - int offset = -1; + auto bestCodePos = -1, nextCodePos = -1, offset = -1; std::string file; - for (auto kv : Script::ScriptBaseProgramNum) + for (const auto& [key, value] : Script::ScriptBaseProgramNum) { - int codePos = kv.first; + const auto codePos = key; if (codePos > scriptPos) { @@ -322,17 +365,15 @@ namespace Components bestCodePos = codePos; - file = kv.second; + file = value; offset = scriptPos - bestCodePos; } if (bestCodePos == -1) return; - float onehundred = 100.0; - Logger::Print(23, "\n@ %d (%d - %d)\n", scriptPos, bestCodePos, nextCodePos); - Logger::Print(23, "in %s (%.1f%% through the source)\n\n", file.c_str(), ((offset * onehundred) / (nextCodePos - bestCodePos))); + Logger::Print(23, "in %s (%.1f%% through the source)\n\n", file.data(), ((offset * 100.0f) / (nextCodePos - bestCodePos))); } __declspec(naked) void Script::Scr_PrintPrevCodePosStub() @@ -373,15 +414,15 @@ namespace Components void Script::OnVMShutdown(Utils::Slot callback) { Script::ScriptBaseProgramNum.clear(); - Script::VMShutdownSignal.connect(callback); + Script::VMShutdownSignal.connect(std::move(callback)); } - void Script::ScrShutdownSystemStub(int num) + void Script::ScrShutdownSystemStub(unsigned char sys) { Script::VMShutdownSignal(); // Scr_ShutdownSystem - Utils::Hook::Call(0x421EE0)(num); + Utils::Hook::Call(0x421EE0)(sys); } unsigned int Script::SetExpFogStub() @@ -403,7 +444,7 @@ namespace Components { if (static_cast(index) >= Game::scrVmPub->outparamcount) { - Game::Scr_Error("^1GetCodePosForParam: Index is out of range!\n"); + Game::Scr_ParamError(static_cast(index), "^1GetCodePosForParam: Index is out of range!\n"); return ""; } @@ -411,7 +452,7 @@ namespace Components if (value->type != Game::VAR_FUNCTION) { - Game::Scr_Error("^1GetCodePosForParam: Expects a function as parameter!\n"); + Game::Scr_ParamError(static_cast(index), "^1GetCodePosForParam: Expects a function as parameter!\n"); return ""; } @@ -430,7 +471,7 @@ namespace Components { if (what[0] == '\0' || with[0] == '\0') { - Logger::Print("Warning: Invalid paramters passed to ReplacedFunctions\n"); + Logger::Print("Warning: Invalid parameters passed to ReplacedFunctions\n"); return; } @@ -487,24 +528,28 @@ namespace Components } } - Game::gentity_t* Script::getEntFromEntRef(Game::scr_entref_t entref) + Game::client_t* Script::GetClient(const Game::gentity_t* ent) { - Game::gentity_t* gentity = &Game::g_entities[entref]; - return gentity; - } + assert(ent != nullptr); - Game::client_t* Script::getClientFromEnt(Game::gentity_t* gentity) - { - if (!gentity->client) + if (ent->client == nullptr) { - Logger::Error(Game::ERR_SCRIPT_DROP, "Entity: %i is not a client", gentity); + Game::Scr_ObjectError(Utils::String::VA("Entity %i is not a player", ent->s.number)); + return nullptr; } - return &Game::svs_clients[gentity->s.number]; + + if (ent->s.number >= *Game::svs_clientCount) + { + Game::Scr_ObjectError(Utils::String::VA("Entity %i is out of bounds", ent->s.number)); + return nullptr; + } + + return &Game::svs_clients[ent->s.number]; } void Script::AddFunctions() { - Script::AddFunction("ReplaceFunc", [](Game::scr_entref_t) // gsc: ReplaceFunc(, ) + Script::AddFunction("ReplaceFunc", []() // gsc: ReplaceFunc(, ) { if (Game::Scr_GetNumParam() != 2u) { @@ -519,7 +564,7 @@ namespace Components }); // System time - Script::AddFunction("GetSystemTime", [](Game::scr_entref_t) // gsc: GetSystemTime() + Script::AddFunction("GetSystemTime", []() // gsc: GetSystemTime() { SYSTEMTIME time; GetSystemTime(&time); @@ -527,7 +572,7 @@ namespace Components Game::Scr_AddInt(time.wSecond); }); - Script::AddFunction("GetSystemMilliseconds", [](Game::scr_entref_t) // gsc: GetSystemMilliseconds() + Script::AddFunction("GetSystemMilliseconds", []() // gsc: GetSystemMilliseconds() { SYSTEMTIME time; GetSystemTime(&time); @@ -535,106 +580,116 @@ namespace Components Game::Scr_AddInt(time.wMilliseconds); }); - // Print to console, even without being in 'developer 1'. - Script::AddFunction("PrintConsole", [](Game::scr_entref_t) // gsc: PrintConsole() - { - if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) - { - Game::Scr_Error("^1PrintConsole: Needs one string parameter!\n"); - return; - } - - auto str = Game::Scr_GetString(0); - - Game::Com_Printf(0, str); - }); - // Executes command to the console - Script::AddFunction("Exec", [](Game::scr_entref_t) // gsc: Exec() + Script::AddFunction("Exec", []() // gsc: Exec() { - if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) + const auto str = Game::Scr_GetString(0); + + if (str == nullptr) { - Game::Scr_Error("^1Exec: Needs one string parameter!\n"); + Game::Scr_ParamError(0, "^1Exec: Illegal parameter!\n"); return; } - auto str = Game::Scr_GetString(0); - Command::Execute(str, false); }); - - // Script Storage Funcs - Script::AddFunction("StorageSet", [](Game::scr_entref_t) // gsc: StorageSet(, ); + // Allow printing to the console even when developer is 0 + Script::AddFunction("PrintConsole", []() // gsc: PrintConsole() { - if (Game::Scr_GetNumParam() != 2u || Game::Scr_GetType(0) != Game::VAR_STRING || Game::Scr_GetType(1) != Game::VAR_STRING) + for (auto i = 0u; i < Game::Scr_GetNumParam(); i++) { - Game::Scr_Error("^1StorageSet: Needs two string parameters!\n"); - return; + const auto str = Game::Scr_GetString(i); + + if (str == nullptr) + { + Game::Scr_ParamError(i, "^1PrintConsole: Illegal parameter!\n"); + return; + } + + Logger::Print(Game::level->scriptPrintChannel, "%s", str); } - - std::string key = Game::Scr_GetString(0); - std::string data = Game::Scr_GetString(1); - - Script::ScriptStorage.insert_or_assign(key, data); }); - Script::AddFunction("StorageRemove", [](Game::scr_entref_t) // gsc: StorageRemove(); + // Script Storage Functions + Script::AddFunction("StorageSet", []() // gsc: StorageSet(, ); { - if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) + const auto* key = Game::Scr_GetString(0); + const auto* value = Game::Scr_GetString(1); + + if (key == nullptr || value == nullptr) { - Game::Scr_Error("^1StorageRemove: Needs one string parameter!\n"); + Game::Scr_Error("^1StorageSet: Illegal parameters!\n"); return; } - std::string key = Game::Scr_GetString(0); + Script::ScriptStorage.insert_or_assign(key, value); + }); + + Script::AddFunction("StorageRemove", []() // gsc: StorageRemove(); + { + const auto* key = Game::Scr_GetString(0); + + if (key == nullptr) + { + Game::Scr_Error("^1StorageRemove: Illegal parameter!\n"); + return; + } if (!Script::ScriptStorage.count(key)) { - Game::Scr_Error(Utils::String::VA("^1StorageRemove: Store does not have key '%s'!\n", key.c_str())); + Game::Scr_Error(Utils::String::VA("^1StorageRemove: Store does not have key '%s'!\n", key)); return; } Script::ScriptStorage.erase(key); }); - Script::AddFunction("StorageGet", [](Game::scr_entref_t) // gsc: StorageGet(); + Script::AddFunction("StorageGet", []() // gsc: StorageGet(); { - if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) + const auto* key = Game::Scr_GetString(0); + + if (key == nullptr) { - Game::Scr_Error("^1StorageGet: Needs one string parameter!\n"); + Game::Scr_Error("^1StorageGet: Illegal parameter!\n"); return; } - std::string key = Game::Scr_GetString(0); - if (!Script::ScriptStorage.count(key)) { - Game::Scr_Error(Utils::String::VA("^1StorageGet: Store does not have key '%s'!\n", key.c_str())); + Game::Scr_Error(Utils::String::VA("^1StorageGet: Store does not have key '%s'!\n", key)); return; } - auto data = Script::ScriptStorage.at(key); - Game::Scr_AddString(data.c_str()); + const auto& data = Script::ScriptStorage.at(key); + Game::Scr_AddString(data.data()); }); - Script::AddFunction("StorageHas", [](Game::scr_entref_t) // gsc: StorageHas(); + Script::AddFunction("StorageHas", []() // gsc: StorageHas(); { - if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) + const auto* key = Game::Scr_GetString(0); + + if (key == nullptr) { - Game::Scr_Error("^1StorageHas: Needs one string parameter!\n"); + Game::Scr_Error("^1StorageHas: Illegal parameter!\n"); return; } - std::string key = Game::Scr_GetString(0); - - Game::Scr_AddInt(Script::ScriptStorage.count(key)); + Game::Scr_AddBool(static_cast(Script::ScriptStorage.count(key))); // Until C++17 }); - Script::AddFunction("StorageClear", [](Game::scr_entref_t) // gsc: StorageClear(); + Script::AddFunction("StorageClear", []() // gsc: StorageClear(); { Script::ScriptStorage.clear(); }); + + // PlayerCmd_AreControlsFrozen GSC function from Black Ops 2 + Script::AddMethod("AreControlsFrozen", [](Game::scr_entref_t entref) // Usage: self AreControlsFrozen(); + { + const auto* ent = Game::GetPlayerEntity(entref); + + Game::Scr_AddBool((ent->client->flags & Game::PLAYER_FLAG_FROZEN) != 0); + }); } Script::Script() @@ -645,14 +700,12 @@ namespace Components Utils::Hook(0x426C2D, Script::StoreScriptBaseProgramNumStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x42281B, Script::Scr_PrintPrevCodePosStub, HOOK_JUMP).install()->quick(); - // enable scr_error printing if in developer - Dvar::OnInit([]() - { - int developer = Dvar::Var("developer").get(); - - if (developer > 0 && Dedicated::IsEnabled()) - Utils::Hook::Set(0x48D8C7, 0x75); - }); + Utils::Hook(0x61E3AD, Script::RuntimeError, HOOK_CALL).install()->quick(); + Utils::Hook(0x621976, Script::RuntimeError, HOOK_CALL).install()->quick(); + Utils::Hook(0x62246E, Script::RuntimeError, HOOK_CALL).install()->quick(); + // Skip check in GScr_CheckAllowedToSetPersistentData to prevent log spam in RuntimeError. + // On IW5 the function is entirely nullsubbed + Utils::Hook::Set(0x5F8DBF, 0xEB); Utils::Hook(0x612E8D, Script::FunctionError, HOOK_CALL).install()->quick(); Utils::Hook(0x612EA2, Script::FunctionError, HOOK_CALL).install()->quick(); @@ -661,27 +714,29 @@ namespace Components Utils::Hook(0x48EFFE, Script::LoadGameType, HOOK_CALL).install()->quick(); Utils::Hook(0x45D44A, Script::LoadGameTypeScript, HOOK_CALL).install()->quick(); - Utils::Hook(0x44E736, Script::GetFunctionStub, HOOK_JUMP).install()->quick(); // Scr_GetFunction - Utils::Hook(0x4EC8E5, Script::GetFunctionStub, HOOK_JUMP).install()->quick(); // Scr_GetMethod + // Fetch custom functions + Utils::Hook(0x44E72E, Script::BuiltIn_GetFunctionStub, HOOK_CALL).install()->quick(); // Scr_GetFunction + Utils::Hook(0x4EC8DD, Script::BuiltIn_GetMethod, HOOK_CALL).install()->quick(); // Scr_GetMethod Utils::Hook(0x5F41A3, Script::SetExpFogStub, HOOK_CALL).install()->quick(); Utils::Hook(0x61E92E, Script::VMExecuteInternalStub, HOOK_JUMP).install()->quick(); Utils::Hook::Nop(0x61E933, 1); - Utils::Hook(0x47548B, Script::ScrShutdownSystemStub, HOOK_CALL).install()->quick(); - Utils::Hook(0x4D06BA, Script::ScrShutdownSystemStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x47548B, Script::ScrShutdownSystemStub, HOOK_CALL).install()->quick(); // G_LoadGame + Utils::Hook(0x4D06BA, Script::ScrShutdownSystemStub, HOOK_CALL).install()->quick(); // G_ShutdownGame Scheduler::OnFrame([]() { if (!Game::SV_Loaded()) return; - int nowMs = Game::Sys_Milliseconds(); + const auto nowMs = Game::Sys_Milliseconds(); if (Script::LastFrameTime != -1) { - int timeTaken = static_cast((nowMs - Script::LastFrameTime) * Dvar::Var("timescale").get()); + const auto timeScale = Dvar::Var("timescale").get(); + const auto timeTaken = static_cast((nowMs - Script::LastFrameTime) * timeScale); if (timeTaken >= 500) Logger::Print(23, "Hitch warning: %i msec frame time\n", timeTaken); @@ -690,10 +745,19 @@ namespace Components Script::LastFrameTime = nowMs; }); - Script::AddFunction("debugBox", [](Game::scr_entref_t) +#ifdef _DEBUG + Script::AddFunction("DebugBox", []() { - MessageBoxA(nullptr, Game::Scr_GetString(0), "DEBUG", 0); - }, true); + const auto* message = Game::Scr_GetString(0); + + if (message == nullptr) + { + Game::Scr_Error("^1DebugBox: Illegal parameter!\n"); + } + + MessageBoxA(nullptr, message, "DEBUG", MB_OK); + }, 1); +#endif Script::AddFunctions(); diff --git a/src/Components/Modules/Script.hpp b/src/Components/Modules/Script.hpp index 123c4d6b..2885e53e 100644 --- a/src/Components/Modules/Script.hpp +++ b/src/Components/Modules/Script.hpp @@ -6,36 +6,23 @@ namespace Components class Script : public Component { public: - class Function - { - public: - Function(const std::string& _name, Game::scr_function_t _callback, bool _dev) : name(_name), callback(_callback), dev(_dev) {} - - const char* getName() const { return this->name.data(); } - bool isDev() const { return this->dev; } - Game::scr_function_t getFunction() const { return this->callback; } - - private: - std::string name; - Game::scr_function_t callback; - bool dev; - }; - Script(); ~Script(); static int LoadScriptAndLabel(const std::string& script, const std::string& label); - static void AddFunction(const std::string& name, Game::scr_function_t function, bool isDev = false); + + static void AddFunction(const char* name, Game::BuiltinFunction func, int type = 0); + static void AddMethod(const char* name, Game::BuiltinMethod func, int type = 0); static void OnVMShutdown(Utils::Slot callback); - static Game::gentity_t* getEntFromEntRef(Game::scr_entref_t entref); - static Game::client_t* getClientFromEnt(Game::gentity_t* gentity); + static Game::client_t* GetClient(const Game::gentity_t* gentity); private: static std::string ScriptName; static std::vector ScriptHandles; - static std::vector ScriptFunctions; + static std::unordered_map CustomScrFunctions; + static std::unordered_map CustomScrMethods; static std::vector ScriptNameStack; static unsigned short FunctionName; static std::unordered_map ScriptStorage; @@ -51,6 +38,7 @@ namespace Components static void FunctionError(); static void StoreFunctionNameStub(); + static void RuntimeError(const char* codePos, unsigned int index, const char* msg, const char* dialogMessage); static void StoreScriptName(const char* name); static void StoreScriptNameStub(); @@ -61,10 +49,10 @@ namespace Components static void LoadGameType(); static void LoadGameTypeScript(); - static Game::scr_function_t GetFunction(void* caller, const char** name, int* isDev); - static void GetFunctionStub(); + static Game::BuiltinFunction BuiltIn_GetFunctionStub(const char** pName, int* type); + static Game::BuiltinMethod BuiltIn_GetMethod(const char** pName, int* type); - static void ScrShutdownSystemStub(int); + static void ScrShutdownSystemStub(unsigned char sys); static void StoreScriptBaseProgramNumStub(); static void StoreScriptBaseProgramNum(); static void Scr_PrintPrevCodePosStub(); diff --git a/src/Components/Modules/ScriptExtension.cpp b/src/Components/Modules/ScriptExtension.cpp new file mode 100644 index 00000000..68e80a7a --- /dev/null +++ b/src/Components/Modules/ScriptExtension.cpp @@ -0,0 +1,401 @@ +#include + +namespace Components +{ + const char* ScriptExtension::QueryStrings[] = { R"(..)", R"(../)", R"(..\)" }; + + std::unordered_map ScriptExtension::CustomEntityFields; + std::unordered_map ScriptExtension::CustomClientFields; + + void ScriptExtension::AddEntityField(const char* name, Game::fieldtype_t type, + const Game::ScriptCallbackEnt& setter, const Game::ScriptCallbackEnt& getter) + { + static std::uint16_t fieldOffsetStart = 15; // fields count + assert((fieldOffsetStart & Game::ENTFIELD_MASK) == Game::ENTFIELD_ENTITY); + + ScriptExtension::CustomEntityFields[fieldOffsetStart] = {name, fieldOffsetStart, type, setter, getter}; + ++fieldOffsetStart; + } + + void ScriptExtension::AddClientField(const char* name, Game::fieldtype_t type, + const Game::ScriptCallbackClient& setter, const Game::ScriptCallbackClient& getter) + { + static std::uint16_t fieldOffsetStart = 21; // fields count + assert((fieldOffsetStart & Game::ENTFIELD_MASK) == Game::ENTFIELD_ENTITY); + + const auto offset = fieldOffsetStart | Game::ENTFIELD_CLIENT; // This is how client field's offset is calculated + + // Use 'index' in 'array' as map key. It will be used later in Scr_SetObjectFieldStub + ScriptExtension::CustomClientFields[fieldOffsetStart] = {name, offset, type, setter, getter}; + ++fieldOffsetStart; + } + + void ScriptExtension::GScr_AddFieldsForEntityStub() + { + for (const auto& [offset, field] : ScriptExtension::CustomEntityFields) + { + Game::Scr_AddClassField(Game::ClassNum::CLASS_NUM_ENTITY, field.name, field.ofs); + } + + Utils::Hook::Call(0x4A7CF0)(); // GScr_AddFieldsForClient + + for (const auto& [offset, field] : ScriptExtension::CustomClientFields) + { + Game::Scr_AddClassField(Game::ClassNum::CLASS_NUM_ENTITY, field.name, field.ofs); + } + } + + // Because some functions are inlined we have to hook this function instead of Scr_SetEntityField + int ScriptExtension::Scr_SetObjectFieldStub(unsigned int classnum, int entnum, int offset) + { + if (classnum == Game::ClassNum::CLASS_NUM_ENTITY) + { + const auto entity_offset = static_cast(offset); + + const auto got = ScriptExtension::CustomEntityFields.find(entity_offset); + if (got != ScriptExtension::CustomEntityFields.end()) + { + got->second.setter(&Game::g_entities[entnum], offset); + return 1; + } + } + + // No custom generic field was found, let the game handle it + return Game::Scr_SetObjectField(classnum, entnum, offset); + } + + // Offset was already converted to array 'index' following binop offset & ~Game::ENTFIELD_MASK + void ScriptExtension::Scr_SetClientFieldStub(Game::gclient_s* client, int offset) + { + const auto client_offset = static_cast(offset); + + const auto got = ScriptExtension::CustomClientFields.find(client_offset); + if (got != ScriptExtension::CustomClientFields.end()) + { + got->second.setter(client, &got->second); + return; + } + + // No custom field client was found, let the game handle it + Game::Scr_SetClientField(client, offset); + } + + void ScriptExtension::Scr_GetEntityFieldStub(int entnum, int offset) + { + if ((offset & Game::ENTFIELD_MASK) == Game::ENTFIELD_CLIENT) + { + // If we have a ENTFIELD_CLIENT offset we need to check g_entity is actually a fully connected client + if (Game::g_entities[entnum].client != nullptr) + { + const auto client_offset = static_cast(offset & ~Game::ENTFIELD_MASK); + + const auto got = ScriptExtension::CustomClientFields.find(client_offset); + if (got != ScriptExtension::CustomClientFields.end()) + { + // Game functions probably don't ever need to use the reference to client_fields_s... + got->second.getter(Game::g_entities[entnum].client, &got->second); + return; + } + } + } + + // Regular entity offsets can be searched directly in our custom handler + const auto entity_offset = static_cast(offset); + + const auto got = ScriptExtension::CustomEntityFields.find(entity_offset); + if (got != ScriptExtension::CustomEntityFields.end()) + { + got->second.getter(&Game::g_entities[entnum], offset); + return; + } + + // No custom generic field was found, let the game handle it + Game::Scr_GetEntityField(entnum, offset); + } + + void ScriptExtension::AddFunctions() + { + // File functions + Script::AddFunction("FileWrite", []() // gsc: FileWrite(, , ) + { + const auto* path = Game::Scr_GetString(0); + auto* text = Game::Scr_GetString(1); + auto* mode = Game::Scr_GetString(2); + + if (path == nullptr) + { + Game::Scr_ParamError(0, "^1FileWrite: filepath is not defined!\n"); + return; + } + + if (text == nullptr || mode == nullptr) + { + Game::Scr_Error("^1FileWrite: Illegal parameters!\n"); + return; + } + + for (auto i = 0u; i < ARRAYSIZE(ScriptExtension::QueryStrings); ++i) + { + if (std::strstr(path, ScriptExtension::QueryStrings[i]) != nullptr) + { + Logger::Print("^1FileWrite: directory traversal is not allowed!\n"); + return; + } + } + + if (mode != "append"s && mode != "write"s) + { + Logger::Print("^3FileWrite: mode not defined or was wrong, defaulting to 'write'\n"); + mode = "write"; + } + + if (mode == "write"s) + { + FileSystem::FileWriter(path).write(text); + } + else if (mode == "append"s) + { + FileSystem::FileWriter(path, true).write(text); + } + }); + + Script::AddFunction("FileRead", []() // gsc: FileRead() + { + const auto* path = Game::Scr_GetString(0); + + if (path == nullptr) + { + Game::Scr_ParamError(0, "^1FileRead: filepath is not defined!\n"); + return; + } + + for (auto i = 0u; i < ARRAYSIZE(ScriptExtension::QueryStrings); ++i) + { + if (std::strstr(path, ScriptExtension::QueryStrings[i]) != nullptr) + { + Logger::Print("^1FileRead: directory traversal is not allowed!\n"); + return; + } + } + + if (!FileSystem::FileReader(path).exists()) + { + Logger::Print("^1FileRead: file not found!\n"); + return; + } + + Game::Scr_AddString(FileSystem::FileReader(path).getBuffer().data()); + }); + + Script::AddFunction("FileExists", []() // gsc: FileExists() + { + const auto* path = Game::Scr_GetString(0); + + if (path == nullptr) + { + Game::Scr_ParamError(0, "^1FileExists: filepath is not defined!\n"); + return; + } + + for (auto i = 0u; i < ARRAYSIZE(ScriptExtension::QueryStrings); ++i) + { + if (std::strstr(path, ScriptExtension::QueryStrings[i]) != nullptr) + { + Logger::Print("^1FileExists: directory traversal is not allowed!\n"); + return; + } + } + + Game::Scr_AddInt(FileSystem::FileReader(path).exists()); + }); + + Script::AddFunction("FileRemove", []() // gsc: FileRemove() + { + const auto* path = Game::Scr_GetString(0); + + if (path == nullptr) + { + Game::Scr_ParamError(0, "^1FileRemove: filepath is not defined!\n"); + return; + } + + for (auto i = 0u; i < ARRAYSIZE(ScriptExtension::QueryStrings); ++i) + { + if (std::strstr(path, ScriptExtension::QueryStrings[i]) != nullptr) + { + Logger::Print("^1FileRemove: directory traversal is not allowed!\n"); + return; + } + } + + const auto p = std::filesystem::path(path); + const auto& folder = p.parent_path().string(); + const auto& file = p.filename().string(); + Game::Scr_AddInt(FileSystem::DeleteFile(folder, file)); + }); + + // Misc functions + Script::AddFunction("ToUpper", []() // gsc: ToUpper() + { + const auto scriptValue = Game::Scr_GetConstString(0); + const auto* string = Game::SL_ConvertToString(scriptValue); + + char out[1024] = {0}; // 1024 is the max for a string in this SL system + bool changed = false; + + size_t i = 0; + while (i < sizeof(out)) + { + const auto value = *string; + const auto result = static_cast(std::toupper(static_cast(value))); + out[i] = result; + + if (value != result) + changed = true; + + if (result == '\0') // Finished converting string + break; + + ++string; + ++i; + } + + // Null terminating character was overwritten + if (i >= sizeof(out)) + { + Game::Scr_Error("string too long"); + return; + } + + if (changed) + { + Game::Scr_AddString(out); + } + else + { + Game::SL_AddRefToString(scriptValue); + Game::Scr_AddConstString(scriptValue); + Game::SL_RemoveRefToString(scriptValue); + } + }); + + // Func present on IW5 + Script::AddFunction("StrICmp", []() // gsc: StrICmp(, ) + { + const auto value1 = Game::Scr_GetConstString(0); + const auto value2 = Game::Scr_GetConstString(1); + + const auto result = _stricmp(Game::SL_ConvertToString(value1), + Game::SL_ConvertToString(value2)); + + Game::Scr_AddInt(result); + }); + + // Func present on IW5 + Script::AddFunction("IsEndStr", []() // gsc: IsEndStr(, ) + { + const auto* s1 = Game::Scr_GetString(0); + const auto* s2 = Game::Scr_GetString(1); + + if (s1 == nullptr || s2 == nullptr) + { + Game::Scr_Error("^1IsEndStr: Illegal parameters!\n"); + return; + } + + Game::Scr_AddBool(Utils::String::EndsWith(s1, s2)); + }); + } + + void ScriptExtension::AddMethods() + { + // ScriptExtension methods + Script::AddMethod("GetIp", [](Game::scr_entref_t entref) // gsc: self GetIp() + { + const auto* ent = Game::GetPlayerEntity(entref); + const auto* client = Script::GetClient(ent); + + std::string ip = Game::NET_AdrToString(client->netchan.remoteAddress); + + if (const auto pos = ip.find_first_of(":"); pos != std::string::npos) + ip.erase(ip.begin() + pos, ip.end()); // Erase port + + Game::Scr_AddString(ip.data()); + }); + + Script::AddMethod("GetPing", [](Game::scr_entref_t entref) // gsc: self GetPing() + { + const auto* ent = Game::GetPlayerEntity(entref); + const auto* client = Script::GetClient(ent); + + Game::Scr_AddInt(client->ping); + }); + } + + void ScriptExtension::Scr_TableLookupIStringByRow() + { + if (Game::Scr_GetNumParam() < 3) + { + Game::Scr_Error("USAGE: tableLookupIStringByRow( filename, rowNum, returnValueColumnNum )\n"); + return; + } + + const auto* fileName = Game::Scr_GetString(0); + const auto rowNum = Game::Scr_GetInt(1); + const auto returnValueColumnNum = Game::Scr_GetInt(2); + + const auto* table = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_STRINGTABLE, fileName).stringTable; + + if (table == nullptr) + { + Game::Scr_ParamError(0, Utils::String::VA("%s does not exist\n", fileName)); + return; + } + + const auto* value = Game::StringTable_GetColumnValueForRow(table, rowNum, returnValueColumnNum); + Game::Scr_AddIString(value); + } + + void ScriptExtension::AddEntityFields() + { + ScriptExtension::AddEntityField("entityflags", Game::fieldtype_t::F_INT, + [](Game::gentity_s* ent, [[maybe_unused]] int offset) + { + ent->flags = Game::Scr_GetInt(0); + }, + [](Game::gentity_s* ent, [[maybe_unused]] int offset) + { + Game::Scr_AddInt(ent->flags); + }); + } + + void ScriptExtension::AddClientFields() + { + ScriptExtension::AddClientField("clientflags", Game::fieldtype_t::F_INT, + [](Game::gclient_s* pSelf, [[maybe_unused]] const Game::client_fields_s* pField) + { + pSelf->flags = Game::Scr_GetInt(0); + }, + [](Game::gclient_s* pSelf, [[maybe_unused]] const Game::client_fields_s* pField) + { + Game::Scr_AddInt(pSelf->flags); + }); + } + + ScriptExtension::ScriptExtension() + { + ScriptExtension::AddFunctions(); + ScriptExtension::AddMethods(); + ScriptExtension::AddEntityFields(); + ScriptExtension::AddClientFields(); + + // Correct builtin function pointer + Utils::Hook::Set(0x79A90C, ScriptExtension::Scr_TableLookupIStringByRow); + + Utils::Hook(0x4EC721, ScriptExtension::GScr_AddFieldsForEntityStub, HOOK_CALL).install()->quick(); // GScr_AddFieldsForEntity + + Utils::Hook(0x41BED2, ScriptExtension::Scr_SetObjectFieldStub, HOOK_CALL).install()->quick(); // SetEntityFieldValue + Utils::Hook(0x5FBF01, ScriptExtension::Scr_SetClientFieldStub, HOOK_CALL).install()->quick(); // Scr_SetObjectField + Utils::Hook(0x4FF413, ScriptExtension::Scr_GetEntityFieldStub, HOOK_CALL).install()->quick(); // Scr_GetObjectField + } +} diff --git a/src/Components/Modules/ScriptExtension.hpp b/src/Components/Modules/ScriptExtension.hpp new file mode 100644 index 00000000..b4722ea9 --- /dev/null +++ b/src/Components/Modules/ScriptExtension.hpp @@ -0,0 +1,34 @@ +#pragma once + +namespace Components +{ + class ScriptExtension : public Component + { + public: + ScriptExtension(); + + static void AddEntityField(const char* name, Game::fieldtype_t type, const Game::ScriptCallbackEnt& setter, const Game::ScriptCallbackEnt& getter); + static void AddClientField(const char* name, Game::fieldtype_t type, const Game::ScriptCallbackClient& setter, const Game::ScriptCallbackClient& getter); + + private: + static const char* QueryStrings[]; + + static std::unordered_map CustomEntityFields; + static std::unordered_map CustomClientFields; + + static void GScr_AddFieldsForEntityStub(); + + // Two hooks because it makes our code cleaner (luckily functions were not inlined) + static int Scr_SetObjectFieldStub(unsigned int classnum, int entnum, int offset); + static void Scr_SetClientFieldStub(Game::gclient_s* client, int offset); + + // One hook because functions were inlined + static void Scr_GetEntityFieldStub(int entnum, int offset); + + static void AddFunctions(); + static void AddMethods(); + static void AddEntityFields(); + static void AddClientFields(); + static void Scr_TableLookupIStringByRow(); + }; +} diff --git a/src/Components/Modules/Security.cpp b/src/Components/Modules/Security.cpp new file mode 100644 index 00000000..b2493cf1 --- /dev/null +++ b/src/Components/Modules/Security.cpp @@ -0,0 +1,112 @@ +#include + +namespace Components +{ + int Security::MsgReadBitsCompressCheckSV(const char* from, char* to, int size) + { + static char buffer[0x8000]; + + if (size > 0x800) return 0; + size = Game::MSG_ReadBitsCompress(from, buffer, size); + + if (size > 0x800) return 0; + std::memcpy(to, buffer, size); + + return size; + } + + int Security::MsgReadBitsCompressCheckCL(const char* from, char* to, int size) + { + static char buffer[0x100000]; + + if (size > 0x20000) return 0; + size = Game::MSG_ReadBitsCompress(from, buffer, size); + + if (size > 0x20000) return 0; + std::memcpy(to, buffer, size); + + return size; + } + + int Security::SVCanReplaceServerCommand(Game::client_t* /*client*/, const char* /*cmd*/) + { + // This is a fix copied from V2. As I don't have time to investigate, let's simply trust them + return -1; + } + + long Security::AtolAdjustPlayerLimit(const char* string) + { + return std::min(std::atol(string), 18); + } + + void Security::SelectStringTableEntryInDvarStub() + { + Command::ClientParams params; + + if (params.size() >= 4) + { + const auto* dvarName = params[3]; + const auto* dvar = Game::Dvar_FindVar(dvarName); + + if (Command::Find(dvarName) || + (dvar != nullptr && dvar->flags & (Game::DVAR_WRITEPROTECTED | Game::DVAR_CHEAT | Game::DVAR_READONLY))) + { + Logger::Print(0, "CL_SelectStringTableEntryInDvar_f: illegal parameter\n"); + return; + } + } + + Game::CL_SelectStringTableEntryInDvar_f(); + } + + __declspec(naked) int Security::G_GetClientScore() + { + __asm + { + mov eax, [esp + 4] // index + mov ecx, ds:1A831A8h // level: &g_clients + + test ecx, ecx + jz invalid_ptr + + imul eax, 366Ch + mov eax, [eax + ecx + 3134h] + ret + + invalid_ptr: + xor eax, eax + ret + } + } + + void Security::G_LogPrintfStub(const char* fmt) + { + Game::G_LogPrintf("%s", fmt); + } + + Security::Security() + { + // Exploit fixes + Utils::Hook(0x414D92, MsgReadBitsCompressCheckSV, HOOK_CALL).install()->quick(); // SV_ExecuteClientCommands + Utils::Hook(0x4A9F56, MsgReadBitsCompressCheckCL, HOOK_CALL).install()->quick(); // CL_ParseServerMessage + Utils::Hook(0x407376, SVCanReplaceServerCommand, HOOK_CALL).install()->quick(); // SV_CanReplaceServerCommand + + Utils::Hook::Set(0x412370, 0xC3); // SV_SteamAuthClient + Utils::Hook::Set(0x5A8C70, 0xC3); // CL_HandleRelayPacket + + Utils::Hook::Nop(0x41698E, 5); // Disable Svcmd_EntityList_f + + // Patch selectStringTableEntryInDvar + Utils::Hook::Set(0x405959, Security::SelectStringTableEntryInDvarStub); + + // Patch G_GetClientScore for uninitialized game + Utils::Hook(0x469AC0, G_GetClientScore, HOOK_JUMP).install()->quick(); + + // Requests can be malicious + Utils::Hook(0x5B67ED, AtolAdjustPlayerLimit, HOOK_CALL).install()->quick(); // PartyHost_HandleJoinPartyRequest + + // Patch unsecure call to G_LogPrint inside GScr_LogPrint + // This function is unsafe because IW devs forgot to G_LogPrintf("%s", fmt) + Utils::Hook(0x5F70B5, G_LogPrintfStub, HOOK_CALL).install()->quick(); + } +} diff --git a/src/Components/Modules/Security.hpp b/src/Components/Modules/Security.hpp new file mode 100644 index 00000000..82c605bd --- /dev/null +++ b/src/Components/Modules/Security.hpp @@ -0,0 +1,24 @@ +#pragma once + +namespace Components +{ + class Security : public Component + { + public: + Security(); + + private: + static int MsgReadBitsCompressCheckSV(const char* from, char* to, int size); + static int MsgReadBitsCompressCheckCL(const char* from, char* to, int size); + + static int SVCanReplaceServerCommand(Game::client_t* client, const char* cmd); + + static long AtolAdjustPlayerLimit(const char* string); + + static void SelectStringTableEntryInDvarStub(); + + static int G_GetClientScore(); + + static void G_LogPrintfStub(const char* fmt); + }; +} diff --git a/src/Components/Modules/ServerCommands.cpp b/src/Components/Modules/ServerCommands.cpp index 7e54f8a8..8c67c49b 100644 --- a/src/Components/Modules/ServerCommands.cpp +++ b/src/Components/Modules/ServerCommands.cpp @@ -2,24 +2,24 @@ namespace Components { - std::unordered_map> ServerCommands::Commands; + std::unordered_map> ServerCommands::Commands; - void ServerCommands::OnCommand(std::int32_t cmd, Utils::Slot cb) + void ServerCommands::OnCommand(std::int32_t cmd, std::function callback) { - ServerCommands::Commands[cmd] = cb; + ServerCommands::Commands.insert_or_assign(cmd, std::move(callback)); } bool ServerCommands::OnServerCommand() { Command::ClientParams params; - for (const auto& serverCommandCB : ServerCommands::Commands) + for (const auto& [id, callback] : ServerCommands::Commands) { if (params.size() >= 1) { - if (params.get(0)[0] == serverCommandCB.first) + if (params.get(0)[0] == id) // Compare ID of server command { - return serverCommandCB.second(¶ms); + return callback(¶ms); } } } @@ -27,7 +27,7 @@ namespace Components return false; } - __declspec(naked) void ServerCommands::OnServerCommandStub() + __declspec(naked) void ServerCommands::CG_DeployServerCommand_Stub() { __asm { @@ -44,7 +44,7 @@ namespace Components test eax, eax jle error - mov eax, DWORD PTR[edx * 4 + 1AAC634h] + mov eax, dword ptr [edx * 4 + 1AAC634h] mov eax, [eax] push 5944B3h @@ -63,6 +63,6 @@ namespace Components ServerCommands::ServerCommands() { // Server command receive hook - Utils::Hook(0x59449F, ServerCommands::OnServerCommandStub).install()->quick(); + Utils::Hook(0x59449F, ServerCommands::CG_DeployServerCommand_Stub).install()->quick(); } } diff --git a/src/Components/Modules/ServerCommands.hpp b/src/Components/Modules/ServerCommands.hpp index 61f823a6..3ba04c5f 100644 --- a/src/Components/Modules/ServerCommands.hpp +++ b/src/Components/Modules/ServerCommands.hpp @@ -7,12 +7,12 @@ namespace Components public: ServerCommands(); - static void OnCommand(std::int32_t cmd, Utils::Slot cb); + static void OnCommand(std::int32_t cmd, std::function callback); private: - static std::unordered_map> Commands; + static std::unordered_map> Commands; static bool OnServerCommand(); - static void OnServerCommandStub(); + static void CG_DeployServerCommand_Stub(); }; } diff --git a/src/Components/Modules/ServerInfo.cpp b/src/Components/Modules/ServerInfo.cpp index 69df260b..cd7d7b6f 100644 --- a/src/Components/Modules/ServerInfo.cpp +++ b/src/Components/Modules/ServerInfo.cpp @@ -4,6 +4,9 @@ namespace Components { ServerInfo::Container ServerInfo::PlayerContainer; + Game::dvar_t** ServerInfo::CGScoreboardHeight; + Game::dvar_t** ServerInfo::CGScoreboardWidth; + unsigned int ServerInfo::GetPlayerCount() { return ServerInfo::PlayerContainer.playerList.size(); @@ -74,22 +77,24 @@ namespace Components void ServerInfo::DrawScoreboardInfo(int localClientNum) { Game::Font_s* font = Game::R_RegisterFont("fonts/bigfont", 0); - void* cxt = Game::ScrPlace_GetActivePlacement(localClientNum); + const auto* cxt = Game::ScrPlace_GetActivePlacement(localClientNum); - std::string addressText = Network::Address(*Game::connectedHost).getString(); - if (addressText == "0.0.0.0:0" || addressText == "loopback") addressText = "Listen Server"; + auto addressText = Network::Address(*Game::connectedHost).getString(); - // get x positions - float fontSize = 0.35f; - float y = (480.0f - Dvar::Var("cg_scoreboardHeight").get()) * 0.5f; - y += Dvar::Var("cg_scoreboardHeight").get() + 6.0f; + if (addressText == "0.0.0.0:0" || addressText == "loopback") + addressText = "Listen Server"; - float x = 320.0f - Dvar::Var("cg_scoreboardWidth").get() * 0.5f; - float x2 = 320.0f + Dvar::Var("cg_scoreboardWidth").get() * 0.5f; + // Get x positions + auto y = (480.0f - (*ServerInfo::CGScoreboardHeight)->current.value) * 0.5f; + y += (*ServerInfo::CGScoreboardHeight)->current.value + 6.0f; - // draw only when stream friendly ui is not enabled - if (!Dvar::Var("ui_streamFriendly").get()) + const auto x = 320.0f - (*ServerInfo::CGScoreboardWidth)->current.value * 0.5f; + const auto x2 = 320.0f + (*ServerInfo::CGScoreboardWidth)->current.value * 0.5f; + + // Draw only when stream friendly ui is not enabled + if (!Friends::UIStreamFriendly.get()) { + constexpr auto fontSize = 0.35f; Game::UI_DrawText(cxt, reinterpret_cast(0x7ED3F8), 0x7FFFFFFF, font, x, y, 0, 0, fontSize, reinterpret_cast(0x747F34), 3); Game::UI_DrawText(cxt, addressText.data(), 0x7FFFFFFF, font, x2 - Game::UI_TextWidth(addressText.data(), 0, font, fontSize), y, 0, 0, fontSize, reinterpret_cast(0x747F34), 3); } @@ -125,7 +130,7 @@ namespace Components Utils::InfoString ServerInfo::GetInfo() { - int maxclientCount = *Game::svs_numclients; + int maxclientCount = *Game::svs_clientCount; if (!maxclientCount) { @@ -172,7 +177,9 @@ namespace Components ServerInfo::ServerInfo() { ServerInfo::PlayerContainer.currentPlayer = 0; - ServerInfo::PlayerContainer.playerList.clear(); + + ServerInfo::CGScoreboardHeight = reinterpret_cast(0x9FD070); + ServerInfo::CGScoreboardWidth = reinterpret_cast(0x9FD0AC); // Draw IP and hostname on the scoreboard Utils::Hook(0x4FC6EA, ServerInfo::DrawScoreboardStub, HOOK_CALL).install()->quick(); diff --git a/src/Components/Modules/ServerInfo.hpp b/src/Components/Modules/ServerInfo.hpp index 36dd8cf7..0eee5b6b 100644 --- a/src/Components/Modules/ServerInfo.hpp +++ b/src/Components/Modules/ServerInfo.hpp @@ -28,6 +28,9 @@ namespace Components Network::Address target; }; + static Game::dvar_t** CGScoreboardHeight; + static Game::dvar_t** CGScoreboardWidth; + static Container PlayerContainer; static void ServerStatus(UIScript::Token); diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index a4e61251..d6af313a 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -14,6 +14,13 @@ namespace Components std::vector ServerList::VisibleList; + Dvar::Var ServerList::UIServerSelected; + Dvar::Var ServerList::UIServerSelectedMap; + Dvar::Var ServerList::NETServerQueryLimit; + Dvar::Var ServerList::NETServerFrames; + + bool ServerList::useMasterServer = true; + std::vector* ServerList::GetList() { if (ServerList::IsOnlineList()) @@ -154,13 +161,13 @@ namespace Components if (info) { - Dvar::Var("ui_serverSelected").set(true); - Dvar::Var("ui_serverSelectedMap").set(info->mapname); + ServerList::UIServerSelected.set(true); + ServerList::UIServerSelectedMap.set(info->mapname); Dvar::Var("ui_serverSelectedGametype").set(info->gametype); } else { - Dvar::Var("ui_serverSelected").set(false); + ServerList::UIServerSelected.set(false); } } @@ -200,13 +207,6 @@ namespace Components auto list = ServerList::GetList(); if (!list) return; - // Refresh entirely, if there is no entry in the list - if (list->empty()) - { - ServerList::Refresh(UIScript::Token()); - return; - } - bool ui_browserShowFull = Dvar::Var("ui_browserShowFull").get(); bool ui_browserShowEmpty = Dvar::Var("ui_browserShowEmpty").get(); int ui_browserShowHardcore = Dvar::Var("ui_browserKillcam").get(); @@ -269,22 +269,28 @@ namespace Components } else if (ServerList::IsOnlineList()) { -#ifdef USE_LEGACY_SERVER_LIST + const auto masterPort = Dvar::Var("masterPort").get(); + const auto masterServerName = Dvar::Var("masterServerName").get(); + + // Check if our dvars can properly convert to a address + Game::netadr_t masterServerAddr; + if (!ServerList::GetMasterServer(masterServerName, masterPort, masterServerAddr)) + { + Logger::Print("Could not resolve address for %s:%u", masterServerName, masterPort); + Toast::Show("cardicon_headshot", "^1Error", Utils::String::VA("Could not resolve address for %s:%u", masterServerName, masterPort), 5000); + return; + } + + Toast::Show("cardicon_headshot", "Server Browser", "Fetching servers...", 3000); + + useMasterServer = true; + ServerList::RefreshContainer.awatingList = true; ServerList::RefreshContainer.awaitTime = Game::Sys_Milliseconds(); - - int masterPort = Dvar::Var("masterPort").get(); - const char* masterServerName = Dvar::Var("masterServerName").get(); - ServerList::RefreshContainer.host = Network::Address(Utils::String::VA("%s:%u", masterServerName, masterPort)); - Logger::Print("Sending serverlist request to master: %s:%u\n", masterServerName, masterPort); - + Logger::Print("Sending serverlist request to master\n"); Network::SendCommand(ServerList::RefreshContainer.host, "getservers", Utils::String::VA("IW4 %i full empty", PROTOCOL)); - //Network::SendCommand(ServerList::RefreshContainer.Host, "getservers", "0 full empty"); -#else - Node::Synchronize(); -#endif } else if (ServerList::IsFavouriteList()) { @@ -565,8 +571,7 @@ namespace Components void ServerList::SortList() { // Only sort when the serverlist is open - Game::menuDef_t* menu = Game::Menus_FindByName(Game::uiContext, "pc_join_unranked"); - if (!menu || !Game::Menu_IsVisible(Game::uiContext, menu)) return; + if (!ServerList::IsServerListOpen()) return; std::stable_sort(ServerList::VisibleList.begin(), ServerList::VisibleList.end(), [](const unsigned int &server1, const unsigned int &server2) -> bool { @@ -621,24 +626,37 @@ namespace Components void ServerList::Frame() { static Utils::Time::Interval frameLimit; - int interval = static_cast(1000.0f / Dvar::Var("net_serverFrames").get()); - if (!frameLimit.elapsed(std::chrono::milliseconds(interval))) return; + const auto interval = static_cast(1000.0f / ServerList::NETServerFrames.get()); + + if (!frameLimit.elapsed(std::chrono::milliseconds(interval))) + return; + frameLimit.update(); std::lock_guard _(ServerList::RefreshContainer.mutex); if (ServerList::RefreshContainer.awatingList) { - // Check if we haven't got a response within 10 seconds + // Stop counting if we are out of the server browser menu + if (!ServerList::IsServerListOpen()) + { + ServerList::RefreshContainer.awatingList = false; + } + + // Check if we haven't got a response within 5 seconds if (Game::Sys_Milliseconds() - ServerList::RefreshContainer.awaitTime > 5000) { ServerList::RefreshContainer.awatingList = false; Logger::Print("We haven't received a response from the master within %d seconds!\n", (Game::Sys_Milliseconds() - ServerList::RefreshContainer.awaitTime) / 1000); + Toast::Show("cardicon_headshot", "^1Error", "Failed to reach master server, using node servers instead.", 5000); + + useMasterServer = false; + Node::Synchronize(); } } - int requestLimit = Dvar::Var("net_serverQueryLimit").get(); + auto requestLimit = ServerList::NETServerQueryLimit.get(); for (unsigned int i = 0; i < ServerList::RefreshContainer.servers.size() && requestLimit > 0; ++i) { ServerList::Container::ServerContainer* server = &ServerList::RefreshContainer.servers[i]; @@ -725,6 +743,20 @@ namespace Components } } + bool ServerList::GetMasterServer(const char* ip, int port, Game::netadr_t& address) + { + return Game::NET_StringToAdr(Utils::String::VA("%s:%u", ip, port), &address); + } + + bool ServerList::IsServerListOpen() + { + auto* menu = Game::Menus_FindByName(Game::uiContext, "pc_join_unranked"); + if (!menu) + return false; + + return Game::Menu_IsVisible(Game::uiContext, menu); + } + ServerList::ServerList() { ServerList::OnlineList.clear(); @@ -734,11 +766,15 @@ namespace Components Dvar::OnInit([]() { - Dvar::Register("ui_serverSelected", false, Game::dvar_flag::DVAR_NONE, "Whether a server has been selected in the serverlist"); - Dvar::Register("ui_serverSelectedMap", "mp_afghan", Game::dvar_flag::DVAR_NONE, "Map of the selected server"); + ServerList::UIServerSelected = Dvar::Register("ui_serverSelected", false, + Game::dvar_flag::DVAR_NONE, "Whether a server has been selected in the serverlist"); + ServerList::UIServerSelectedMap = Dvar::Register("ui_serverSelectedMap", "mp_afghan", + Game::dvar_flag::DVAR_NONE, "Map of the selected server"); - Dvar::Register("net_serverQueryLimit", 1, 1, 10, Dedicated::IsEnabled() ? Game::dvar_flag::DVAR_NONE : Game::dvar_flag::DVAR_ARCHIVE, "Amount of server queries per frame"); - Dvar::Register("net_serverFrames", 30, 1, 60, Dedicated::IsEnabled() ? Game::dvar_flag::DVAR_NONE : Game::dvar_flag::DVAR_ARCHIVE, "Amount of server query frames per second"); + ServerList::NETServerQueryLimit = Dvar::Register("net_serverQueryLimit", 1, + 1, 10, Dedicated::IsEnabled() ? Game::dvar_flag::DVAR_NONE : Game::dvar_flag::DVAR_ARCHIVE, "Amount of server queries per frame"); + ServerList::NETServerFrames = Dvar::Register("net_serverFrames", 30, + 1, 60, Dedicated::IsEnabled() ? Game::dvar_flag::DVAR_NONE : Game::dvar_flag::DVAR_ARCHIVE, "Amount of server query frames per second"); }); // Fix ui_netsource dvar @@ -780,11 +816,9 @@ namespace Components }); // Set default masterServerName + port and save it -#ifdef USE_LEGACY_SERVER_LIST - Utils::Hook::Set(0x60AD92, "127.0.0.1"); + Utils::Hook::Set(0x60AD92, "master.xlabs.dev"); Utils::Hook::Set(0x60AD90, Game::dvar_flag::DVAR_ARCHIVE); // masterServerName Utils::Hook::Set(0x60ADC6, Game::dvar_flag::DVAR_ARCHIVE); // masterPort -#endif // Add server list feeder UIFeeder::Add(2.0f, ServerList::GetServerCount, ServerList::GetServerText, ServerList::SelectServer); @@ -794,6 +828,7 @@ namespace Components UIScript::Add("RefreshFilter", ServerList::UpdateVisibleList); UIScript::Add("RefreshServers", ServerList::Refresh); + UIScript::Add("JoinServer", [](UIScript::Token) { ServerList::ServerInfo* info = ServerList::GetServer(ServerList::CurrentServer); @@ -803,6 +838,7 @@ namespace Components Party::Connect(info->addr); } }); + UIScript::Add("ServerSort", [](UIScript::Token token) { int key = token.get(); @@ -820,6 +856,7 @@ namespace Components Logger::Print("Sorting server list by token: %d\n", ServerList::SortKey); ServerList::SortList(); }); + UIScript::Add("CreateListFavorite", [](UIScript::Token) { ServerList::ServerInfo* info = ServerList::GetCurrentServer(); @@ -829,10 +866,12 @@ namespace Components ServerList::StoreFavourite(info->addr.getString()); } }); + UIScript::Add("CreateFavorite", [](UIScript::Token) { ServerList::StoreFavourite(Dvar::Var("ui_favoriteAddress").get()); }); + UIScript::Add("CreateCurrentServerFavorite", [](UIScript::Token) { if (Game::CL_IsCgameInitialized()) @@ -844,6 +883,7 @@ namespace Components } } }); + UIScript::Add("DeleteFavorite", [](UIScript::Token) { ServerList::ServerInfo* info = ServerList::GetCurrentServer(); @@ -854,28 +894,24 @@ namespace Components }; }); +#ifdef _DEBUG Command::Add("playerCount", [](Command::Params*) { - int count = 0; - for (auto server : ServerList::OnlineList) + auto count = 0; + for (const auto& server : ServerList::OnlineList) { count += server.clients; } Logger::Print("There are %d players playing.\n", count); }); - +#endif // Add required ownerDraws UIScript::AddOwnerDraw(220, ServerList::UpdateSource); UIScript::AddOwnerDraw(253, ServerList::UpdateGameType); // Add frame callback Scheduler::OnFrame(ServerList::Frame); - - // This is placed here in case the anticheat has been disabled! -#if !defined(DISABLE_ANTICHEAT) && defined(PROCTECT_PROCESS) - Scheduler::OnFrame(AntiCheat::ReadIntegrityCheck, true); -#endif } ServerList::~ServerList() diff --git a/src/Components/Modules/ServerList.hpp b/src/Components/Modules/ServerList.hpp index d125f02f..22754f7b 100644 --- a/src/Components/Modules/ServerList.hpp +++ b/src/Components/Modules/ServerList.hpp @@ -50,6 +50,9 @@ namespace Components static void UpdateVisibleInfo(); + static bool GetMasterServer(const char* ip, int port, Game::netadr_t& address); + static bool useMasterServer; + private: enum Column { @@ -138,5 +141,12 @@ namespace Components static std::vector FavouriteList; static std::vector VisibleList; + + static Dvar::Var UIServerSelected; + static Dvar::Var UIServerSelectedMap; + static Dvar::Var NETServerQueryLimit; + static Dvar::Var NETServerFrames; + + static bool IsServerListOpen(); }; } diff --git a/src/Components/Modules/SlowMotion.cpp b/src/Components/Modules/SlowMotion.cpp index 268a2a0a..12f74e7e 100644 --- a/src/Components/Modules/SlowMotion.cpp +++ b/src/Components/Modules/SlowMotion.cpp @@ -68,7 +68,7 @@ namespace Components SlowMotion::Delay = delay; // set snapshot num to 1 behind (T6 does this, why shouldn't we?) - for (int i = 0; i < *Game::svs_numclients; ++i) + for (int i = 0; i < *Game::svs_clientCount; ++i) { Game::svs_clients[i].snapNum = *reinterpret_cast(0x31D9384) - 1; } diff --git a/src/Components/Modules/TextRenderer.cpp b/src/Components/Modules/TextRenderer.cpp index 2a11a676..2b7fcbf2 100644 --- a/src/Components/Modules/TextRenderer.cpp +++ b/src/Components/Modules/TextRenderer.cpp @@ -1356,11 +1356,11 @@ namespace Components if (*in) // height in++; - if(*in) // material name length + material name characters + if (*in) // material name length + material name characters { const auto materialNameLength = *in; in++; - for(auto i = 0; i < materialNameLength; i++) + for (auto i = 0; i < materialNameLength; i++) { if (*in) in++; @@ -1370,7 +1370,7 @@ namespace Components continue; } - if(*in == FONT_ICON_SEPARATOR_CHARACTER) + if (*in == FONT_ICON_SEPARATOR_CHARACTER) { const auto* fontIconEndPos = &in[1]; FontIconInfo fontIcon{}; @@ -1386,6 +1386,7 @@ namespace Components ++current; ++in; } + *out = '\0'; } diff --git a/src/Components/Modules/Weapon.cpp b/src/Components/Modules/Weapon.cpp index e1becfe7..3f57f50a 100644 --- a/src/Components/Modules/Weapon.cpp +++ b/src/Components/Modules/Weapon.cpp @@ -41,7 +41,17 @@ namespace Components if (params.size() <= 1) return 0; - int index = atoi(params[1]); + char* end; + const auto* input = params.get(1); + auto index = std::strtol(input, &end, 10); + + if (input == end) + { + Logger::Print("Warning: %s is not a valid input\n" + "Usage: %s \n", + input, params.get(0)); + return 0; + } if (index >= 4139) { @@ -56,7 +66,7 @@ namespace Components return 0; } - Utils::Hook::Call(0x4BD520)(0, index); + Game::CG_SetupWeaponDef(0, index); return 1; } diff --git a/src/Components/Modules/Window.cpp b/src/Components/Modules/Window.cpp index a5669656..3661625b 100644 --- a/src/Components/Modules/Window.cpp +++ b/src/Components/Modules/Window.cpp @@ -7,6 +7,8 @@ namespace Components HWND Window::MainWindow = nullptr; BOOL Window::CursorVisible = TRUE; + std::unordered_map> Window::WndMessageCallbacks; + Utils::Signal Window::CreateSignals; int Window::Width() { @@ -66,6 +68,16 @@ namespace Components return Window::MainWindow; } + void Window::OnWndMessage(UINT Msg, Utils::Slot callback) + { + WndMessageCallbacks.emplace(Msg, callback); + } + + void Window::OnCreate(Utils::Slot callback) + { + CreateSignals.connect(callback); + } + int Window::IsNoBorder() { return Window::NoBorder.get(); @@ -121,6 +133,9 @@ namespace Components HWND WINAPI Window::CreateMainWindow(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam) { Window::MainWindow = CreateWindowExA(dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam); + + CreateSignals(); + return Window::MainWindow; } @@ -132,15 +147,21 @@ namespace Components BOOL WINAPI Window::MessageHandler(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { - if (Msg == WM_SETCURSOR) + if (const auto cb = WndMessageCallbacks.find(Msg); cb != WndMessageCallbacks.end()) { - Window::ApplyCursor(); - return TRUE; + return cb->second(lParam, wParam); } return Utils::Hook::Call(0x4731F0)(hWnd, Msg, wParam, lParam); } + void Window::EnableDpiAwareness() + { + const Utils::Library user32{"user32.dll"}; + + user32.invokePascal("SetProcessDpiAwarenessContext", DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + } + Window::Window() { // Borderless window @@ -184,5 +205,13 @@ namespace Components // Use custom message handler Utils::Hook::Set(0x64D298, Window::MessageHandler); + + Window::OnWndMessage(WM_SETCURSOR, [](WPARAM, LPARAM) + { + Window::ApplyCursor(); + return TRUE; + }); + + Window::EnableDpiAwareness(); } } diff --git a/src/Components/Modules/Window.hpp b/src/Components/Modules/Window.hpp index 37a97547..9b0f09c6 100644 --- a/src/Components/Modules/Window.hpp +++ b/src/Components/Modules/Window.hpp @@ -5,6 +5,9 @@ namespace Components class Window : public Component { public: + typedef BOOL(WndProcCallback)(WPARAM wParam, LPARAM lParam); + typedef void(CreateCallback)(); + Window(); static int Width(); @@ -18,10 +21,15 @@ namespace Components static HWND GetWindow(); + static void OnWndMessage(UINT Msg, Utils::Slot callback); + + static void OnCreate(Utils::Slot callback); private: static BOOL CursorVisible; static Dvar::Var NoBorder; static Dvar::Var NativeCursor; + static std::unordered_map> WndMessageCallbacks; + static Utils::Signal CreateSignals; static HWND MainWindow; @@ -36,5 +44,7 @@ namespace Components static void StyleHookStub(); static HWND WINAPI CreateMainWindow(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam); + + static void EnableDpiAwareness(); }; } diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 95a3c7e9..5fb7505a 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -5,23 +5,23 @@ namespace Game std::vector Sys_ListFilesWrapper(const std::string& directory, const std::string& extension) { auto fileCount = 0; - auto files = Game::Sys_ListFiles(directory.data(), extension.data(), 0, &fileCount, 0); + auto** const files = Sys_ListFiles(directory.data(), extension.data(), nullptr, &fileCount, 0); std::vector result; for (auto i = 0; i < fileCount; i++) { - if (files[i]) + if (files[i] != nullptr) { - result.push_back(files[i]); + result.emplace_back(files[i]); } } - Game::FS_FreeFileList(files); + FS_FreeFileList(files); return result; } - + AddRefToObject_t AddRefToObject = AddRefToObject_t(0x61C360); AllocObject_t AllocObject = AllocObject_t(0x434320); @@ -31,6 +31,7 @@ namespace Game BG_GetWeaponName_t BG_GetWeaponName = BG_GetWeaponName_t(0x4E6EC0); BG_LoadWeaponDef_LoadObj_t BG_LoadWeaponDef_LoadObj = BG_LoadWeaponDef_LoadObj_t(0x57B5F0); BG_GetWeaponDef_t BG_GetWeaponDef = BG_GetWeaponDef_t(0x440EB0); + BG_GetEntityTypeName_t BG_GetEntityTypeName = BG_GetEntityTypeName_t(0x43A0E0); Cbuf_AddServerText_t Cbuf_AddServerText = Cbuf_AddServerText_t(0x4BB9B0); Cbuf_AddText_t Cbuf_AddText = Cbuf_AddText_t(0x404B20); @@ -44,7 +45,9 @@ namespace Game CG_ScoresUp_f_t CG_ScoresUp_f = CG_ScoresUp_f_t(0x5802C0); CG_ScrollScoreboardUp_t CG_ScrollScoreboardUp = CG_ScrollScoreboardUp_t(0x47A5C0); CG_ScrollScoreboardDown_t CG_ScrollScoreboardDown = CG_ScrollScoreboardDown_t(0x493B50); - + CG_GetTeamName_t CG_GetTeamName = CG_GetTeamName_t(0x4B6210); + CG_SetupWeaponDef_t CG_SetupWeaponDef = CG_SetupWeaponDef_t(0x4BD520); + CL_GetClientName_t CL_GetClientName = CL_GetClientName_t(0x4563D0); CL_IsCgameInitialized_t CL_IsCgameInitialized = CL_IsCgameInitialized_t(0x43EB20); CL_ConnectFromParty_t CL_ConnectFromParty = CL_ConnectFromParty_t(0x433D30); @@ -73,6 +76,7 @@ namespace Game Com_MatchToken_t Com_MatchToken = Com_MatchToken_t(0x447130); Com_SetSlowMotion_t Com_SetSlowMotion = Com_SetSlowMotion_t(0x446E20); Com_Quitf_t Com_Quit_f = Com_Quitf_t(0x4D4000); + Com_PrintWarning_t Com_PrintWarning = Com_PrintWarning_t(0x4E0200); Con_DrawMiniConsole_t Con_DrawMiniConsole = Con_DrawMiniConsole_t(0x464F30); Con_DrawSolidConsole_t Con_DrawSolidConsole = Con_DrawSolidConsole_t(0x5A5040); @@ -149,8 +153,13 @@ namespace Game FS_IsShippedIWD_t FS_IsShippedIWD = FS_IsShippedIWD_t(0x642440); FS_Delete_t FS_Delete = FS_Delete_t(0x48A5B0); + G_LogPrintf_t G_LogPrintf = G_LogPrintf_t(0x4B0150); G_GetWeaponIndexForName_t G_GetWeaponIndexForName = G_GetWeaponIndexForName_t(0x49E540); G_SpawnEntitiesFromString_t G_SpawnEntitiesFromString = G_SpawnEntitiesFromString_t(0x4D8840); + G_PrintEntities_t G_PrintEntities = G_PrintEntities_t(0x4E6A50); + G_GetEntityTypeName_t G_GetEntityTypeName = G_GetEntityTypeName_t(0x4EB810); + + Svcmd_EntityList_f_t Svcmd_EntityList_f = Svcmd_EntityList_f_t(0x4B6A70); GScr_LoadGameTypeScript_t GScr_LoadGameTypeScript = GScr_LoadGameTypeScript_t(0x4ED9A0); @@ -184,7 +193,7 @@ namespace Game Load_snd_alias_list_nameArray_t Load_snd_alias_list_nameArray = Load_snd_alias_list_nameArray_t(0x4499F0); Menus_CloseAll_t Menus_CloseAll = Menus_CloseAll_t(0x4BA5B0); - Menus_CloseRequest_t Menus_CloseRequest = Menus_CloseRequest_t(0x430D50); + Menus_CloseRequest_t Menus_CloseRequest = Menus_CloseRequest_t(0x430D50); Menus_OpenByName_t Menus_OpenByName = Menus_OpenByName_t(0x4CCE60); Menus_FindByName_t Menus_FindByName = Menus_FindByName_t(0x487240); Menu_IsVisible_t Menu_IsVisible = Menu_IsVisible_t(0x4D77D0); @@ -259,6 +268,8 @@ namespace Game Scr_GetFunctionHandle_t Scr_GetFunctionHandle = Scr_GetFunctionHandle_t(0x4234F0); Scr_GetString_t Scr_GetString = Scr_GetString_t(0x425900); + Scr_GetConstString_t Scr_GetConstString = Scr_GetConstString_t(0x494830); + Scr_GetDebugString_t Scr_GetDebugString = Scr_GetDebugString_t(0x4EBF50); Scr_GetFloat_t Scr_GetFloat = Scr_GetFloat_t(0x443140); Scr_GetInt_t Scr_GetInt = Scr_GetInt_t(0x4F31D0); Scr_GetObject_t Scr_GetObject = Scr_GetObject_t(0x462100); @@ -269,16 +280,31 @@ namespace Game Scr_AddEntity_t Scr_AddEntity = Scr_AddEntity_t(0x4BFB40); Scr_AddString_t Scr_AddString = Scr_AddString_t(0x412310); + Scr_AddConstString_t Scr_AddConstString = Scr_AddConstString_t(0x488860); + Scr_AddIString_t Scr_AddIString = Scr_AddIString_t(0x455F20); Scr_AddInt_t Scr_AddInt = Scr_AddInt_t(0x41D7D0); Scr_AddFloat_t Scr_AddFloat = Scr_AddFloat_t(0x61E860); Scr_AddObject_t Scr_AddObject = Scr_AddObject_t(0x430F40); Scr_Notify_t Scr_Notify = Scr_Notify_t(0x4A4750); Scr_NotifyLevel_t Scr_NotifyLevel = Scr_NotifyLevel_t(0x4D9C30); + Scr_Error_t Scr_Error = Scr_Error_t(0x61E8B0); + Scr_ObjectError_t Scr_ObjectError = Scr_ObjectError_t(0x42EF40); + Scr_ParamError_t Scr_ParamError = Scr_ParamError_t(0x4FBC70); + Scr_GetType_t Scr_GetType = Scr_GetType_t(0x422900); Scr_ClearOutParams_t Scr_ClearOutParams = Scr_ClearOutParams_t(0x4386E0); + Scr_GetObjectField_t Scr_GetObjectField = Scr_GetObjectField_t(0x4FF3D0); + Scr_SetObjectField_t Scr_SetObjectField = Scr_SetObjectField_t(0x4F20F0); + Scr_GetEntityField_t Scr_GetEntityField = Scr_GetEntityField_t(0x4E8390); + Scr_SetClientField_t Scr_SetClientField = Scr_SetClientField_t(0x4A6DF0); + Scr_AddClassField_t Scr_AddClassField = Scr_AddClassField_t(0x4C0E70); + + GetEntity_t GetEntity = GetEntity_t(0x4BC270); + GetPlayerEntity_t GetPlayerEntity = GetPlayerEntity_t(0x49C4A0); + Scr_RegisterFunction_t Scr_RegisterFunction = Scr_RegisterFunction_t(0x492D50); Scr_ShutdownAllocNode_t Scr_ShutdownAllocNode = Scr_ShutdownAllocNode_t(0x441650); Scr_IsSystemActive_t Scr_IsSystemActive = Scr_IsSystemActive_t(0x4B24E0); @@ -302,6 +328,8 @@ namespace Game SL_ConvertToString_t SL_ConvertToString = SL_ConvertToString_t(0x4EC1D0); SL_GetString_t SL_GetString = SL_GetString_t(0x4CDC10); + SL_AddRefToString_t SL_AddRefToString = SL_AddRefToString_t(0x4D9B00); + SL_RemoveRefToString_t SL_RemoveRefToString = SL_RemoveRefToString_t(0x47CD70); SND_Init_t SND_Init = SND_Init_t(0x46A630); SND_InitDriver_t SND_InitDriver = SND_InitDriver_t(0x4F5090); @@ -311,9 +339,11 @@ namespace Game Steam_JoinLobby_t Steam_JoinLobby = Steam_JoinLobby_t(0x49CF70); StringTable_Lookup_t StringTable_Lookup = StringTable_Lookup_t(0x42F0E0); + StringTable_GetColumnValueForRow_t StringTable_GetColumnValueForRow = StringTable_GetColumnValueForRow_t(0x4F2C80); StringTable_HashString_t StringTable_HashString = StringTable_HashString_t(0x475EB0); SV_AddTestClient_t SV_AddTestClient = SV_AddTestClient_t(0x48AD30); + SV_IsTestClient_t SV_IsTestClient = SV_IsTestClient_t(0x4D6E40); SV_GameClientNum_Score_t SV_GameClientNum_Score = SV_GameClientNum_Score_t(0x469AC0); SV_GameSendServerCommand_t SV_GameSendServerCommand = SV_GameSendServerCommand_t(0x4BC3A0); SV_Cmd_TokenizeString_t SV_Cmd_TokenizeString = SV_Cmd_TokenizeString_t(0x4B5780); @@ -323,10 +353,10 @@ namespace Game SV_SetConfigstring_t SV_SetConfigstring = SV_SetConfigstring_t(0x4982E0); SV_Loaded_t SV_Loaded = SV_Loaded_t(0x4EE3E0); SV_ClientThink_t SV_ClientThink = SV_ClientThink_t(0x44ADD0); + SV_DropClient_t SV_DropClient = SV_DropClient_t(0x4D1600); SV_GetPlayerByName_t SV_GetPlayerByName = SV_GetPlayerByName_t(0x6242B0); SV_GetPlayerByNum_t SV_GetPlayerByNum = SV_GetPlayerByNum_t(0x624390); - Sys_Error_t Sys_Error = Sys_Error_t(0x4E0200); Sys_FreeFileList_t Sys_FreeFileList = Sys_FreeFileList_t(0x4D8580); Sys_IsDatabaseReady_t Sys_IsDatabaseReady = Sys_IsDatabaseReady_t(0x4CA4A0); Sys_IsDatabaseReady2_t Sys_IsDatabaseReady2 = Sys_IsDatabaseReady2_t(0x441280); @@ -339,6 +369,7 @@ namespace Game Sys_SuspendOtherThreads_t Sys_SuspendOtherThreads = Sys_SuspendOtherThreads_t(0x45A190); Sys_ListFiles_t Sys_ListFiles = Sys_ListFiles_t(0x45A660); Sys_Milliseconds_t Sys_Milliseconds = Sys_Milliseconds_t(0x42A660); + Sys_Error_t Sys_Error = Sys_Error_t(0x43D570); Sys_LockWrite_t Sys_LockWrite = Sys_LockWrite_t(0x435880); Sys_TempPriorityAtLeastNormalBegin_t Sys_TempPriorityAtLeastNormalBegin = Sys_TempPriorityAtLeastNormalBegin_t(0x478680); Sys_TempPriorityEnd_t Sys_TempPriorityEnd = Sys_TempPriorityEnd_t(0x4DCF00); @@ -390,6 +421,13 @@ namespace Game PM_Trace_t PM_Trace = PM_Trace_t(0x441F60); PM_GetEffectiveStance_t PM_GetEffectiveStance = PM_GetEffectiveStance_t(0x412540); + CL_MouseEvent_t CL_MouseEvent = CL_MouseEvent_t(0x4D7C50); + IN_RecenterMouse_t IN_RecenterMouse = IN_RecenterMouse_t(0x463D80); + + IN_MouseMove_t IN_MouseMove = IN_MouseMove_t(0x64C490); + IN_Init_t IN_Init = IN_Init_t(0x45D620); + IN_Shutdown_t IN_Shutdown = IN_Shutdown_t(0x426360); + XAssetHeader* DB_XAssetPool = reinterpret_cast(0x7998A8); unsigned int* g_poolSize = reinterpret_cast(0x7995E8); @@ -405,10 +443,10 @@ namespace Game float* cgameFOVSensitivityScale = reinterpret_cast(0xB2F884); int* svs_time = reinterpret_cast(0x31D9384); - int* svs_numclients = reinterpret_cast(0x31D938C); + int* svs_clientCount = reinterpret_cast(0x31D938C); client_t* svs_clients = reinterpret_cast(0x31D9390); - UiContext *uiContext = reinterpret_cast(0x62E2858); + UiContext* uiContext = reinterpret_cast(0x62E2858); int* arenaCount = reinterpret_cast(0x62E6930); mapArena_t* arenas = reinterpret_cast(0x62E6934); @@ -473,6 +511,7 @@ namespace Game unsigned short* db_hashTable = reinterpret_cast(0x12412B0); scrVmPub_t* scrVmPub = reinterpret_cast(0x2040CF0); + scrVarPub_t* scrVarPub = reinterpret_cast(0x201A408); clientstate_t* clcState = reinterpret_cast(0xB2C540); @@ -501,10 +540,23 @@ namespace Game GraphFloat* aaInputGraph = reinterpret_cast(0x7A2FC0); + const char* MY_CMDS = reinterpret_cast(0x73C9C4); // Count 5 + + XModel** cached_models = reinterpret_cast(0x1AA20C8); + FastCriticalSection* db_hashCritSect = reinterpret_cast(0x16B8A54); vec3_t* CorrectSolidDeltas = reinterpret_cast(0x739BB8); // Count 26 + level_locals_t* level = reinterpret_cast(0x1A831A8); + + float(*penetrationDepthTable)[PENETRATE_TYPE_COUNT][SURF_TYPE_COUNT] = reinterpret_cast(0x7C4878); + + WinMouseVars_t* s_wmv = reinterpret_cast(0x649D640); + + int* window_center_x = reinterpret_cast(0x649D638); + int* window_center_y = reinterpret_cast(0x649D630); + void Sys_LockRead(FastCriticalSection* critSect) { InterlockedIncrement(&critSect->readCount); @@ -517,6 +569,13 @@ namespace Game InterlockedDecrement(&critSect->readCount); } + XModel* G_GetModel(const int index) + { + assert(index > 0); + assert(index < MAX_MODELS); + return cached_models[index]; + } + XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize) { int elSize = DB_GetXAssetSizeHandlers[type](); @@ -703,24 +762,49 @@ namespace Game return hash; } - void SV_KickClientError(client_t* client, const std::string& reason) + void SV_GameDropClient(int clientNum, const char* reason) { - if (client->state < 5) + const auto maxClients = Dvar_FindVar("sv_maxclients")->current.integer; + assert(maxClients >= 1 && maxClients <= 18); + + if (clientNum >= 0 && clientNum < maxClients) { - Components::Network::SendCommand(client->netchan.remoteAddress, "error", reason); + SV_DropClient(&svs_clients[clientNum], reason, true); + } + } + + void SV_DropAllBots() + { + for (auto i = 0; i < *svs_clientCount; ++i) + { + if (svs_clients[i].state != clientstate_t::CS_FREE + && svs_clients[i].netchan.remoteAddress.type == netadrtype_t::NA_BOT) + { + SV_GameDropClient(i, "GAME_GET_TO_COVER"); + } + } + } + + void IncInParam() + { + Scr_ClearOutParams(); + + if (scrVmPub->top == scrVmPub->maxStack) + { + Sys_Error("Internal script stack overflow"); } - SV_KickClient(client, reason.data()); + scrVmPub->top++; + scrVmPub->inparamcount++; } - void Scr_iPrintLn(int clientNum, const std::string& message) + void Scr_AddBool(int value) { - Game::SV_GameSendServerCommand(clientNum, 0, Utils::String::VA("%c \"%s\"", 0x66, message.data())); - } + assert(value == 0 || value == 1); - void Scr_iPrintLnBold(int clientNum, const std::string& message) - { - Game::SV_GameSendServerCommand(clientNum, 0, Utils::String::VA("%c \"%s\"", 0x67, message.data())); + IncInParam(); + scrVmPub->top->type = VAR_INTEGER; + scrVmPub->top->u.intValue = value; } int FS_FOpenFileReadCurrentThread(const char* file, int* fh) @@ -1100,19 +1184,23 @@ namespace Game } } - bool PM_IsAdsAllowed(Game::playerState_s* playerState) + __declspec(naked) bool PM_IsAdsAllowed(playerState_s* /*ps*/) { - bool result; - __asm { - mov esi, playerState - mov ebx, 0x5755A0 - call ebx - mov result, al // AL - } + push eax + pushad - return result; + mov esi, [esp + 0x24 + 0x4] // ps + mov ecx, 0x5755A0 + call ecx + + mov [esp + 0x20], eax + popad + pop eax + + ret + } } __declspec(naked) void FS_AddLocalizedGameDirectory(const char* /*path*/, const char* /*dir*/) @@ -1164,28 +1252,6 @@ namespace Game } } - __declspec(naked) void SV_KickClient(client_t* /*client*/, const char* /*reason*/) - { - __asm - { - pushad - - mov edi, 0 - mov esi, [esp + 24h] - push [esp + 28h] - push 0 - push 0 - - mov eax, 6249A0h - call eax - add esp, 0Ch - - popad - - retn - } - } - __declspec(naked) void Scr_NotifyId(unsigned int /*id*/, unsigned __int16 /*stringValue*/, unsigned int /*paramcount*/) { __asm @@ -1207,6 +1273,27 @@ namespace Game } } + __declspec(naked) void RuntimeErrorInternal(int /*channel*/, const char* /*codePos*/, unsigned int /*index*/, const char* /*msg*/) + { + __asm + { + pushad + + mov eax, [esp + 0x10 + 0x20] // msg + mov edi, [esp + 0x4 + 0x20] // channel + + push [esp + 0xC + 0x20] // index + push [esp + 0xC + 0x20] // codePos + + mov edx, 0x61ABE0 + call edx + add esp, 0x8 + + popad + ret + } + } + __declspec(naked) void IN_KeyUp(kbutton_t* /*button*/) { __asm @@ -1508,7 +1595,7 @@ namespace Game __declspec(naked) void AimAssist_UpdateAdsLerp(const AimInput* /*aimInput*/) { - __asm + __asm { mov eax, [esp + 0x4] mov ebx, 0x569AA0 @@ -1540,5 +1627,20 @@ namespace Game } } + constexpr auto SV_BotUserMove_Addr = 0x626E50; + __declspec(naked) void SV_BotUserMove(client_t* /*client*/) + { + __asm + { + pushad + + mov edi, [esp + 0x20 + 0x4] + call SV_BotUserMove_Addr + + popad + ret + } + } + #pragma optimize("", on) } diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 5ec72a9b..4369bbad 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -43,6 +43,9 @@ namespace Game typedef WeaponDef* (__cdecl * BG_GetWeaponDef_t)(int weaponIndex); extern BG_GetWeaponDef_t BG_GetWeaponDef; + typedef const char*(__cdecl * BG_GetEntityTypeName_t)(const int eType); + extern BG_GetEntityTypeName_t BG_GetEntityTypeName; + typedef void(__cdecl * Cbuf_AddServerText_t)(); extern Cbuf_AddServerText_t Cbuf_AddServerText; @@ -75,7 +78,13 @@ namespace Game typedef void(__cdecl * CG_ScrollScoreboardDown_t)(cg_s* cgameGlob); extern CG_ScrollScoreboardDown_t CG_ScrollScoreboardDown; - + + typedef const char*(__cdecl * CG_GetTeamName_t)(team_t team); + extern CG_GetTeamName_t CG_GetTeamName; + + typedef void(__cdecl * CG_SetupWeaponDef_t)(int localClientNum, unsigned int weapIndex); + extern CG_SetupWeaponDef_t CG_SetupWeaponDef; + typedef char*(__cdecl * CL_GetClientName_t)(int localClientNum, int index, char *buf, size_t size); extern CL_GetClientName_t CL_GetClientName; @@ -154,6 +163,9 @@ namespace Game typedef void(__cdecl * Com_Quitf_t)(); extern Com_Quitf_t Com_Quit_f; + typedef void(__cdecl * Com_PrintWarning_t)(int, const char*, ...); + extern Com_PrintWarning_t Com_PrintWarning; + typedef char* (__cdecl * Con_DrawMiniConsole_t)(int localClientNum, int xPos, int yPos, float alpha); extern Con_DrawMiniConsole_t Con_DrawMiniConsole; @@ -329,7 +341,7 @@ namespace Game typedef int(__cdecl * FS_FOpenFileReadForThread_t)(const char *filename, int *file, int thread); extern FS_FOpenFileReadForThread_t FS_FOpenFileReadForThread; - typedef int(__cdecl * FS_FCloseFile_t)(int fh); + typedef int(__cdecl * FS_FCloseFile_t)(int stream); extern FS_FCloseFile_t FS_FCloseFile; typedef bool(__cdecl * FS_FileExists_t)(const char* file); @@ -368,12 +380,24 @@ namespace Game typedef int(__cdecl* FS_Delete_t)(const char* fileName); extern FS_Delete_t FS_Delete; - typedef int(__cdecl* G_GetWeaponIndexForName_t)(char*); + typedef void(__cdecl * G_LogPrintf_t)(const char* fmt, ...); + extern G_LogPrintf_t G_LogPrintf; + + typedef unsigned int(__cdecl * G_GetWeaponIndexForName_t)(const char*); extern G_GetWeaponIndexForName_t G_GetWeaponIndexForName; - typedef void(__cdecl* G_SpawnEntitiesFromString_t)(); + typedef void(__cdecl * G_SpawnEntitiesFromString_t)(); extern G_SpawnEntitiesFromString_t G_SpawnEntitiesFromString; + typedef void(__cdecl * G_PrintEntities_t)(); + extern G_PrintEntities_t G_PrintEntities; + + typedef const char*(__cdecl * G_GetEntityTypeName_t)(const gentity_s* ent); + extern G_GetEntityTypeName_t G_GetEntityTypeName; + + typedef void(__cdecl * Svcmd_EntityList_f_t)(); + extern Svcmd_EntityList_f_t Svcmd_EntityList_f; + typedef void(__cdecl * GScr_LoadGameTypeScript_t)(); extern GScr_LoadGameTypeScript_t GScr_LoadGameTypeScript; @@ -399,7 +423,7 @@ namespace Game typedef void(__cdecl * LargeLocalInit_t)(); extern LargeLocalInit_t LargeLocalInit; - typedef bool(__cdecl * Load_Stream_t)(bool atStreamStart, const void *ptr, unsigned int size); + typedef bool(__cdecl * Load_Stream_t)(bool atStreamStart, const void* ptr, unsigned int size); extern Load_Stream_t Load_Stream; typedef void(__cdecl * Load_XString_t)(bool atStreamStart); @@ -408,64 +432,64 @@ namespace Game typedef void(__cdecl * Load_XModelPtr_t)(bool atStreamStart); extern Load_XModelPtr_t Load_XModelPtr; - typedef void(__cdecl * Load_XModelSurfsFixup_t)(XModelSurfs **, XModelLodInfo *); + typedef void(__cdecl * Load_XModelSurfsFixup_t)(XModelSurfs**, XModelLodInfo*); extern Load_XModelSurfsFixup_t Load_XModelSurfsFixup; typedef void(__cdecl * Load_XStringArray_t)(bool atStreamStart, int count); extern Load_XStringArray_t Load_XStringArray; - typedef void(__cdecl * Load_XStringCustom_t)(const char **str); + typedef void(__cdecl * Load_XStringCustom_t)(const char** str); extern Load_XStringCustom_t Load_XStringCustom; - typedef void(__cdecl *Load_FxEffectDefHandle_t)(bool atStreamStart); + typedef void(__cdecl * Load_FxEffectDefHandle_t)(bool atStreamStart); extern Load_FxEffectDefHandle_t Load_FxEffectDefHandle; - typedef void(__cdecl *Load_FxElemDef_t)(bool atStreamStart); + typedef void(__cdecl * Load_FxElemDef_t)(bool atStreamStart); extern Load_FxElemDef_t Load_FxElemDef; - typedef void(__cdecl *Load_GfxImagePtr_t)(bool atStreamStart); + typedef void(__cdecl * Load_GfxImagePtr_t)(bool atStreamStart); extern Load_GfxImagePtr_t Load_GfxImagePtr; - typedef void(__cdecl *Load_GfxTextureLoad_t)(bool atStreamStart); + typedef void(__cdecl * Load_GfxTextureLoad_t)(bool atStreamStart); extern Load_GfxTextureLoad_t Load_GfxTextureLoad; - typedef int(__cdecl *Load_Texture_t)(GfxImageLoadDef **loadDef, GfxImage *image); + typedef int(__cdecl * Load_Texture_t)(GfxImageLoadDef** loadDef, GfxImage* image); extern Load_Texture_t Load_Texture; typedef void(__cdecl * Load_SndAliasCustom_t)(snd_alias_list_t** var); extern Load_SndAliasCustom_t Load_SndAliasCustom; - typedef void(__cdecl *Load_MaterialHandle_t)(bool atStreamStart); + typedef void(__cdecl * Load_MaterialHandle_t)(bool atStreamStart); extern Load_MaterialHandle_t Load_MaterialHandle; - typedef void(__cdecl *Load_PhysCollmapPtr_t)(bool atStreamStart); + typedef void(__cdecl * Load_PhysCollmapPtr_t)(bool atStreamStart); extern Load_PhysCollmapPtr_t Load_PhysCollmapPtr; - typedef void(__cdecl *Load_PhysPresetPtr_t)(bool atStreamStart); + typedef void(__cdecl * Load_PhysPresetPtr_t)(bool atStreamStart); extern Load_PhysPresetPtr_t Load_PhysPresetPtr; - typedef void(__cdecl *Load_TracerDefPtr_t)(bool atStreamStart); + typedef void(__cdecl * Load_TracerDefPtr_t)(bool atStreamStart); extern Load_TracerDefPtr_t Load_TracerDefPtr; - typedef void(__cdecl *Load_snd_alias_list_nameArray_t)(bool atStreamStart, int count); + typedef void(__cdecl * Load_snd_alias_list_nameArray_t)(bool atStreamStart, int count); extern Load_snd_alias_list_nameArray_t Load_snd_alias_list_nameArray; - typedef void(__cdecl * Menus_CloseAll_t)(UiContext *dc); + typedef void(__cdecl * Menus_CloseAll_t)(UiContext* dc); extern Menus_CloseAll_t Menus_CloseAll; - typedef void(__cdecl * Menus_CloseRequest_t)(UiContext *dc, menuDef_t* menu); - extern Menus_CloseRequest_t Menus_CloseRequest; + typedef void(__cdecl * Menus_CloseRequest_t)(UiContext* dc, menuDef_t* menu); + extern Menus_CloseRequest_t Menus_CloseRequest; - typedef int(__cdecl * Menus_OpenByName_t)(UiContext *dc, const char *p); + typedef int(__cdecl * Menus_OpenByName_t)(UiContext* dc, const char* p); extern Menus_OpenByName_t Menus_OpenByName; - typedef menuDef_t *(__cdecl * Menus_FindByName_t)(UiContext *dc, const char *name); + typedef menuDef_t *(__cdecl * Menus_FindByName_t)(UiContext* dc, const char* name); extern Menus_FindByName_t Menus_FindByName; - typedef bool(__cdecl * Menu_IsVisible_t)(UiContext *dc, menuDef_t *menu); + typedef bool(__cdecl * Menu_IsVisible_t)(UiContext* dc, menuDef_t* menu); extern Menu_IsVisible_t Menu_IsVisible; - typedef bool(__cdecl * Menus_MenuIsInStack_t)(UiContext *dc, menuDef_t *menu); + typedef bool(__cdecl * Menus_MenuIsInStack_t)(UiContext* dc, menuDef_t* menu); extern Menus_MenuIsInStack_t Menus_MenuIsInStack; typedef menuDef_t*(__cdecl * Menu_GetFocused_t)(UiContext* ctx); @@ -477,16 +501,16 @@ namespace Game typedef bool(__cdecl * UI_KeyEvent_t)(int clientNum, int key, int down); extern UI_KeyEvent_t UI_KeyEvent; - typedef const char* (__cdecl * UI_SafeTranslateString_t)(const char* reference); + typedef const char*(__cdecl * UI_SafeTranslateString_t)(const char* reference); extern UI_SafeTranslateString_t UI_SafeTranslateString; typedef void(__cdecl * UI_ReplaceConversions_t)(const char* sourceString, ConversionArguments* arguments, char* outputString, size_t outputStringSize); extern UI_ReplaceConversions_t UI_ReplaceConversions; - typedef void(__cdecl * MSG_Init_t)(msg_t *buf, char *data, int length); + typedef void(__cdecl * MSG_Init_t)(msg_t* buf, char* data, int length); extern MSG_Init_t MSG_Init; - typedef void(__cdecl * MSG_ReadData_t)(msg_t *msg, void *data, int len); + typedef void(__cdecl * MSG_ReadData_t)(msg_t* msg, void* data, int len); extern MSG_ReadData_t MSG_ReadData; typedef int(__cdecl * MSG_ReadLong_t)(msg_t* msg); @@ -555,7 +579,7 @@ namespace Game typedef bool(__cdecl * NET_IsLocalAddress_t)(netadr_t adr); extern NET_IsLocalAddress_t NET_IsLocalAddress; - typedef bool(__cdecl * NET_StringToAdr_t)(const char *s, netadr_t *a); + typedef int(__cdecl * NET_StringToAdr_t)(const char *s, netadr_t *a); extern NET_StringToAdr_t NET_StringToAdr; typedef void(__cdecl * NET_OutOfBandPrint_t)(netsrc_t sock, netadr_t adr, const char *data); @@ -603,7 +627,7 @@ namespace Game typedef void(__cdecl * Playlist_ParsePlaylists_t)(const char* data); extern Playlist_ParsePlaylists_t Playlist_ParsePlaylists; - typedef Font_s* (__cdecl * R_RegisterFont_t)(const char* asset, int safe); + typedef Font_s*(__cdecl * R_RegisterFont_t)(const char* asset, int safe); extern R_RegisterFont_t R_RegisterFont; typedef void(__cdecl * R_AddCmdDrawText_t)(const char *text, int maxChars, Font_s *font, float x, float y, float xScale, float yScale, float rotation, const float *color, int style); @@ -636,16 +660,22 @@ namespace Game typedef void(__cdecl * RemoveRefToObject_t)(unsigned int id); extern RemoveRefToObject_t RemoveRefToObject; - typedef void(__cdecl * Scr_AddEntity_t)(gentity_s const*); + typedef void(__cdecl * Scr_AddEntity_t)(const gentity_s* ent); extern Scr_AddEntity_t Scr_AddEntity; - typedef void(__cdecl * Scr_AddString_t)(const char* str); + typedef void(__cdecl * Scr_AddString_t)(const char* value); extern Scr_AddString_t Scr_AddString; - typedef void(__cdecl * Scr_AddInt_t)(int num); + typedef void(__cdecl * Scr_AddConstString_t)(unsigned int value); + extern Scr_AddConstString_t Scr_AddConstString; + + typedef void(__cdecl * Scr_AddIString_t)(const char* value); + extern Scr_AddIString_t Scr_AddIString; + + typedef void(__cdecl * Scr_AddInt_t)(int value); extern Scr_AddInt_t Scr_AddInt; - typedef void(__cdecl * Scr_AddFloat_t)(float); + typedef void(__cdecl * Scr_AddFloat_t)(float value); extern Scr_AddFloat_t Scr_AddFloat; typedef void(__cdecl * Scr_AddObject_t)(unsigned int id); @@ -660,22 +690,28 @@ namespace Game typedef int(__cdecl * Scr_LoadScript_t)(const char*); extern Scr_LoadScript_t Scr_LoadScript; - typedef char* (__cdecl * Scr_GetString_t)(int); + typedef const char*(__cdecl * Scr_GetString_t)(unsigned int index); extern Scr_GetString_t Scr_GetString; - typedef float(__cdecl * Scr_GetFloat_t)(int); + typedef scr_string_t(__cdecl * Scr_GetConstString_t)(unsigned int index); + extern Scr_GetConstString_t Scr_GetConstString; + + typedef const char*(__cdecl * Scr_GetDebugString_t)(unsigned int index); + extern Scr_GetDebugString_t Scr_GetDebugString; + + typedef float(__cdecl * Scr_GetFloat_t)(unsigned int index); extern Scr_GetFloat_t Scr_GetFloat; - typedef int(__cdecl * Scr_GetInt_t)(int); + typedef int(__cdecl * Scr_GetInt_t)(unsigned int index); extern Scr_GetInt_t Scr_GetInt; - typedef unsigned int(__cdecl * Scr_GetObject_t)(int); + typedef unsigned int(__cdecl * Scr_GetObject_t)(unsigned int index); extern Scr_GetObject_t Scr_GetObject; typedef unsigned int(__cdecl * Scr_GetNumParam_t)(); extern Scr_GetNumParam_t Scr_GetNumParam; - typedef int(__cdecl * Scr_GetFunctionHandle_t)(const char*, const char*); + typedef int(__cdecl * Scr_GetFunctionHandle_t)(const char* filename, const char* name); extern Scr_GetFunctionHandle_t Scr_GetFunctionHandle; typedef int(__cdecl * Scr_ExecThread_t)(int, int); @@ -693,18 +729,45 @@ namespace Game typedef void(__cdecl * Scr_ClearOutParams_t)(); extern Scr_ClearOutParams_t Scr_ClearOutParams; - typedef void(__cdecl * Scr_RegisterFunction_t)(scr_function_t function); + typedef void(__cdecl * Scr_RegisterFunction_t)(int func, const char* name); extern Scr_RegisterFunction_t Scr_RegisterFunction; typedef bool(__cdecl * Scr_IsSystemActive_t)(); extern Scr_IsSystemActive_t Scr_IsSystemActive; - typedef int(__cdecl* Scr_GetType_t)(unsigned int); + typedef int(__cdecl * Scr_GetType_t)(unsigned int); extern Scr_GetType_t Scr_GetType; - typedef void(__cdecl* Scr_Error_t)(const char*); + typedef void(__cdecl * Scr_Error_t)(const char*); extern Scr_Error_t Scr_Error; + typedef void(__cdecl * Scr_ObjectError_t)(const char*); + extern Scr_ObjectError_t Scr_ObjectError; + + typedef void(__cdecl * Scr_ParamError_t)(unsigned int paramIndex, const char*); + extern Scr_ParamError_t Scr_ParamError; + + typedef void(__cdecl * Scr_GetObjectField_t)(unsigned int classnum, int entnum, int offset); + extern Scr_GetObjectField_t Scr_GetObjectField; + + typedef int(__cdecl * Scr_SetObjectField_t)(unsigned int classnum, int entnum, int offset); + extern Scr_SetObjectField_t Scr_SetObjectField; + + typedef void(__cdecl * Scr_SetClientField_t)(gclient_s* client, int offset); + extern Scr_SetClientField_t Scr_SetClientField; + + typedef void(__cdecl * Scr_GetEntityField_t)(int entnum, int offset); + extern Scr_GetEntityField_t Scr_GetEntityField; + + typedef void(__cdecl * Scr_AddClassField_t)(unsigned int classnum, const char* name, unsigned int offset); + extern Scr_AddClassField_t Scr_AddClassField; + + typedef gentity_s*(__cdecl * GetPlayerEntity_t)(scr_entref_t entref); + extern GetPlayerEntity_t GetPlayerEntity; + + typedef gentity_s*(__cdecl * GetEntity_t)(scr_entref_t entref); + extern GetEntity_t GetEntity; + typedef script_t* (__cdecl * Script_Alloc_t)(int length); extern Script_Alloc_t Script_Alloc; @@ -714,21 +777,27 @@ namespace Game typedef int(__cdecl * Script_CleanString_t)(char* buffer); extern Script_CleanString_t Script_CleanString; - typedef char* (__cdecl * SE_Load_t)(const char* file, int Unk); + typedef char*(__cdecl * SE_Load_t)(const char* file, int Unk); extern SE_Load_t SE_Load; - typedef char* (__cdecl * SEH_StringEd_GetString_t)(const char* string); + typedef char*(__cdecl * SEH_StringEd_GetString_t)(const char* string); extern SEH_StringEd_GetString_t SEH_StringEd_GetString; typedef unsigned int(__cdecl* SEH_ReadCharFromString_t)(const char** text, int* isTrailingPunctuation); extern SEH_ReadCharFromString_t SEH_ReadCharFromString; - typedef char* (__cdecl * SL_ConvertToString_t)(unsigned short stringValue); + typedef const char*(__cdecl * SL_ConvertToString_t)(scr_string_t stringValue); extern SL_ConvertToString_t SL_ConvertToString; typedef short(__cdecl * SL_GetString_t)(const char *str, unsigned int user); extern SL_GetString_t SL_GetString; + typedef void(__cdecl * SL_AddRefToString_t)(unsigned int stringValue); + extern SL_AddRefToString_t SL_AddRefToString; + + typedef void(__cdecl * SL_RemoveRefToString_t)(unsigned int stringValue); + extern SL_RemoveRefToString_t SL_RemoveRefToString; + typedef void(__cdecl * SND_Init_t)(int a1, int a2, int a3); extern SND_Init_t SND_Init; @@ -741,15 +810,21 @@ namespace Game typedef void(__cdecl * Steam_JoinLobby_t)(SteamID, char); extern Steam_JoinLobby_t Steam_JoinLobby; - typedef const char*(__cdecl * StringTable_Lookup_t)(StringTable *table, const int comparisonColumn, const char *value, const int valueColumn); + typedef const char*(__cdecl * StringTable_Lookup_t)(const StringTable *table, const int comparisonColumn, const char *value, const int valueColumn); extern StringTable_Lookup_t StringTable_Lookup; + typedef const char* (__cdecl * StringTable_GetColumnValueForRow_t)(const StringTable* table, int, int column); + extern StringTable_GetColumnValueForRow_t StringTable_GetColumnValueForRow; + typedef int(__cdecl * StringTable_HashString_t)(const char* string); extern StringTable_HashString_t StringTable_HashString; typedef gentity_t*(__cdecl* SV_AddTestClient_t)(); extern SV_AddTestClient_t SV_AddTestClient; + typedef int(__cdecl * SV_IsTestClient_t)(int clientNum); + extern SV_IsTestClient_t SV_IsTestClient; + typedef int(__cdecl* SV_GameClientNum_Score_t)(int clientID); extern SV_GameClientNum_Score_t SV_GameClientNum_Score; @@ -777,13 +852,16 @@ namespace Game typedef void(__cdecl * SV_ClientThink_t)(client_s*, usercmd_s*); extern SV_ClientThink_t SV_ClientThink; + typedef void(__cdecl * SV_DropClient_t)(client_t* drop, const char* reason, bool tellThem); + extern SV_DropClient_t SV_DropClient; + typedef client_t*(__cdecl * SV_GetPlayerByName_t)(); extern SV_GetPlayerByName_t SV_GetPlayerByName; typedef client_t*(__cdecl * SV_GetPlayerByNum_t)(); extern SV_GetPlayerByNum_t SV_GetPlayerByNum; - typedef int(__cdecl * Sys_Error_t)(int, char *, ...); + typedef void(__cdecl * Sys_Error_t)(const char* error, ...); extern Sys_Error_t Sys_Error; typedef void(__cdecl * Sys_FreeFileList_t)(char** list); @@ -834,16 +912,16 @@ namespace Game typedef void(__cdecl * Sys_SuspendOtherThreads_t)(); extern Sys_SuspendOtherThreads_t Sys_SuspendOtherThreads; - typedef void(__cdecl * UI_AddMenuList_t)(UiContext *dc, MenuList *menuList, int close); + typedef void(__cdecl * UI_AddMenuList_t)(UiContext* dc, MenuList* menuList, int close); extern UI_AddMenuList_t UI_AddMenuList; typedef uiMenuCommand_t(__cdecl * UI_GetActiveMenu_t)(int localClientNum); extern UI_GetActiveMenu_t UI_GetActiveMenu; - typedef char* (__cdecl * UI_CheckStringTranslation_t)(char*, char*); + typedef char*(__cdecl * UI_CheckStringTranslation_t)(char*, char*); extern UI_CheckStringTranslation_t UI_CheckStringTranslation; - typedef MenuList *(__cdecl * UI_LoadMenus_t)(const char *menuFile, int imageTrack); + typedef MenuList*(__cdecl * UI_LoadMenus_t)(const char* menuFile, int imageTrack); extern UI_LoadMenus_t UI_LoadMenus; typedef void(__cdecl * UI_UpdateArenas_t)(); @@ -855,13 +933,13 @@ namespace Game typedef void(__cdecl * UI_DrawHandlePic_t)(/*ScreenPlacement*/void *scrPlace, float x, float y, float w, float h, int horzAlign, int vertAlign, const float *color, Material *material); extern UI_DrawHandlePic_t UI_DrawHandlePic; - typedef ScreenPlacement* (__cdecl * ScrPlace_GetActivePlacement_t)(int localClientNum); + typedef ScreenPlacement*(__cdecl * ScrPlace_GetActivePlacement_t)(int localClientNum); extern ScrPlace_GetActivePlacement_t ScrPlace_GetActivePlacement; typedef int(__cdecl * UI_TextWidth_t)(const char *text, int maxChars, Font_s *font, float scale); extern UI_TextWidth_t UI_TextWidth; - typedef void(__cdecl * UI_DrawText_t)(void* scrPlace, const char *text, int maxChars, Font_s *font, float x, float y, int horzAlign, int vertAlign, float scale, const float *color, int style); + typedef void(__cdecl * UI_DrawText_t)(const ScreenPlacement* scrPlace, const char* text, int maxChars, Font_s* font, float x, float y, int horzAlign, int vertAlign, float scale, const float* color, int style); extern UI_DrawText_t UI_DrawText; typedef Font_s* (__cdecl* UI_GetFontHandle_t)(ScreenPlacement* scrPlace, int fontEnum, float scale); @@ -927,6 +1005,21 @@ namespace Game typedef EffectiveStance(__cdecl * PM_GetEffectiveStance_t)(const playerState_s* ps); extern PM_GetEffectiveStance_t PM_GetEffectiveStance; + typedef int(__cdecl * CL_MouseEvent_t)(int x, int y, int dx, int dy); + extern CL_MouseEvent_t CL_MouseEvent; + + typedef void(__cdecl * IN_RecenterMouse_t)(); + extern IN_RecenterMouse_t IN_RecenterMouse; + + typedef void(__cdecl * IN_MouseMove_t)(); + extern IN_MouseMove_t IN_MouseMove; + + typedef void(__cdecl * IN_Init_t)(); + extern IN_Init_t IN_Init; + + typedef void(__cdecl * IN_Shutdown_t)(); + extern IN_Shutdown_t IN_Shutdown; + extern XAssetHeader* DB_XAssetPool; extern unsigned int* g_poolSize; @@ -940,13 +1033,13 @@ namespace Game extern float* cgameFOVSensitivityScale; extern int* svs_time; - extern int* svs_numclients; + extern int* svs_clientCount; extern client_t* svs_clients; extern source_t **sourceFiles; extern keywordHash_t **menuParseKeywordHash; - extern UiContext *uiContext; + extern UiContext* uiContext; extern int* arenaCount; extern mapArena_t* arenas; @@ -1012,6 +1105,7 @@ namespace Game extern unsigned short* db_hashTable; extern scrVmPub_t* scrVmPub; + extern scrVarPub_t* scrVarPub; extern clientstate_t* clcState; @@ -1043,13 +1137,29 @@ namespace Game constexpr auto AIM_ASSIST_GRAPH_COUNT = 4u; extern GraphFloat* aaInputGraph; + extern const char* MY_CMDS; + + constexpr auto MAX_MODELS = 512; + extern XModel** cached_models; + extern vec3_t* CorrectSolidDeltas; extern FastCriticalSection* db_hashCritSect; + extern level_locals_t* level; + + extern float(*penetrationDepthTable)[PENETRATE_TYPE_COUNT][SURF_TYPE_COUNT]; + + extern WinMouseVars_t* s_wmv; + + extern int* window_center_x; + extern int* window_center_y; + void Sys_LockRead(FastCriticalSection* critSect); void Sys_UnlockRead(FastCriticalSection* critSect); + XModel* G_GetModel(int index); + XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize); void Menu_FreeItemMemory(Game::itemDef_s* item); void Menu_SetNextCursorItem(Game::UiContext* ctx, Game::menuDef_t* currentMenu, int unk = 1); @@ -1069,7 +1179,7 @@ namespace Game void FS_AddLocalizedGameDirectory(const char *path, const char *dir); - bool PM_IsAdsAllowed(Game::playerState_s* playerState); + bool PM_IsAdsAllowed(playerState_s* ps); void ShowMessageBox(const std::string& message, const std::string& title); @@ -1078,12 +1188,15 @@ namespace Game void R_LoadSunThroughDvars(const char* mapname, sunflare_t* sun); void R_SetSunFromDvars(sunflare_t* sun); - void SV_KickClient(client_t* client, const char* reason); - void SV_KickClientError(client_t* client, const std::string& reason); + void SV_GameDropClient(int clientNum, const char* reason); + void SV_DropAllBots(); + void SV_BotUserMove(client_t* client); + + void RuntimeErrorInternal(int channel, const char* codePos, unsigned int index, const char* msg); + void IncInParam(); - void Scr_iPrintLn(int clientNum, const std::string& message); - void Scr_iPrintLnBold(int clientNum, const std::string& message); void Scr_NotifyId(unsigned int id, unsigned __int16 stringValue, unsigned int paramcount); + void Scr_AddBool(int value); void IN_KeyUp(kbutton_t* button); void IN_KeyDown(kbutton_t* button); diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index fd4acc95..e0eb5dcc 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -20,8 +20,30 @@ namespace Game typedef vec_t vec3_t[3]; typedef vec_t vec4_t[4]; - typedef unsigned int scr_entref_t; - typedef void(__cdecl * scr_function_t)(scr_entref_t); + typedef unsigned __int16 scr_string_t; + + struct scr_entref_t + { + unsigned __int16 entnum; + unsigned __int16 classnum; + }; + + typedef void(__cdecl * BuiltinFunction)(); + typedef void(__cdecl * BuiltinMethod)(scr_entref_t); + + struct BuiltinFunctionDef + { + const char* actionString; + BuiltinFunction actionFunc; + int type; + }; + + struct BuiltinMethodDef + { + const char* actionString; + BuiltinMethod actionFunc; + int type; + }; enum XAssetType { @@ -74,6 +96,43 @@ namespace Game ASSET_TYPE_INVALID = -1, }; + enum materialSurfType_t + { + SURF_TYPE_DEFAULT, + SURF_TYPE_BARK, + SURF_TYPE_BRICK, + SURF_TYPE_CARPET, + SURF_TYPE_CLOTH, + SURF_TYPE_CONCRETE, + SURF_TYPE_DIRT, + SURF_TYPE_FLESH, + SURF_TYPE_FOLIAGE, + SURF_TYPE_GLASS, + SURF_TYPE_GRASS, + SURF_TYPE_GRAVEL, + SURF_TYPE_ICE, + SURF_TYPE_METAL, + SURF_TYPE_MUD, + SURF_TYPE_PAPER, + SURF_TYPE_PLASTER, + SURF_TYPE_ROCK, + SURF_TYPE_SAND, + SURF_TYPE_SNOW, + SURF_TYPE_WATER, + SURF_TYPE_WOOD, + SURF_TYPE_ASPHALT, + SURF_TYPE_CERAMIC, + SURF_TYPE_PLASTIC, + SURF_TYPE_RUBBER, + SURF_TYPE_CUSHION, + SURF_TYPE_FRUIT, + SURF_TYPE_PAINTED_METAL, + SURF_TYPE_RIOT_SHIELD, + SURF_TYPE_SLUSH, + + SURF_TYPE_COUNT + }; + enum dvar_flag : unsigned __int16 { DVAR_NONE = 0x0, // No flags @@ -209,6 +268,17 @@ namespace Game FL_MOVER_SLIDE = 0x8000000 }; + enum ClassNum : unsigned int + { + CLASS_NUM_ENTITY = 0x0, + CLASS_NUM_HUDELEM = 0x1, + CLASS_NUM_PATHNODE = 0x2, + CLASS_NUM_VEHICLENODE = 0x3, + CLASS_NUM_VEHTRACK_SEGMENT = 0x4, + CLASS_NUM_FXENTITY = 0x5, + CLASS_NUM_COUNT = 0x6, + }; + typedef enum { HITLOC_NONE, @@ -1451,21 +1521,23 @@ namespace Game enum usercmdButtonBits { - CMD_BUTTON_ATTACK = 0x1, - CMD_BUTTON_SPRINT = 0x2, - CMD_BUTTON_MELEE = 0x4, - CMD_BUTTON_ACTIVATE = 0x8, - CMD_BUTTON_RELOAD = 0x10, - CMD_BUTTON_USE_RELOAD = 0x20, - CMD_BUTTON_PRONE = 0x100, - CMD_BUTTON_CROUCH = 0x200, - CMD_BUTTON_UP = 0x400, - CMD_BUTTON_ADS = 0x800, - CMD_BUTTON_DOWN = 0x1000, - CMD_BUTTON_BREATH = 0x2000, - CMD_BUTTON_FRAG = 0x4000, - CMD_BUTTON_OFFHAND_SECONDARY = 0x8000, - CMD_BUTTON_THROW = 0x80000, + CMD_BUTTON_ATTACK = 0x1, + CMD_BUTTON_SPRINT = 0x2, + CMD_BUTTON_MELEE = 0x4, + CMD_BUTTON_ACTIVATE = 0x8, + CMD_BUTTON_RELOAD = 0x10, + CMD_BUTTON_USE_RELOAD = 0x20, + CMD_BUTTON_LEAN_LEFT = 0x40, + CMD_BUTTON_LEAN_RIGHT = 0x80, + CMD_BUTTON_PRONE = 0x100, + CMD_BUTTON_CROUCH = 0x200, + CMD_BUTTON_UP = 0x400, + CMD_BUTTON_ADS = 0x800, + CMD_BUTTON_DOWN = 0x1000, + CMD_BUTTON_BREATH = 0x2000, + CMD_BUTTON_FRAG = 0x4000, + CMD_BUTTON_OFFHAND_SECONDARY = 0x8000, + CMD_BUTTON_THROW = 0x80000 }; #pragma pack(push, 4) @@ -4999,6 +5071,21 @@ namespace Game int dataCount; } gameState; + struct HunkUser + { + HunkUser* current; + HunkUser* next; + int maxSize; + int end; + int pos; + const char* name; + bool fixed; + int type; + char buf[1]; + }; + + static_assert(sizeof(HunkUser) == 36); + struct VariableStackBuffer { const char *pos; @@ -5094,6 +5181,40 @@ namespace Game VariableValue stack[2048]; }; + struct scrVarPub_t + { + const char* fieldBuffer; + unsigned __int16 canonicalStrCount; + bool developer_script; + bool evaluate; + const char* error_message; + int error_index; + int time; + int timeArrayId; + int pauseArrayId; + int notifyArrayId; + int objectStackId; + int levelId; + int gameId; + int animId; + int freeEntList; + int tempVariable; + int numScriptValues[2]; + bool bInited; + unsigned __int16 savecount; + unsigned __int16 savecountMark; + int checksum; + int entId; + int entFieldName; + HunkUser* programHunkUser; + const char* programBuffer; + const char* endScriptBuffer; + unsigned __int16 saveIdMap[36864]; + unsigned __int16 saveIdMapRev[36864]; + }; + + static_assert(sizeof(scrVarPub_t) == 0x24060); + enum UILocalVarType { UILOCALVAR_INT = 0x0, @@ -5503,15 +5624,25 @@ namespace Game CON_CONNECTED = 0x2 } clientConnected_t; + typedef enum + { + VISIONSET_NORMAL, + VISIONSET_NIGHT, + VISIONSET_MISSILECAM, + VISIONSET_THERMAL, + VISIONSET_PAIN, + VISIONSETCOUNT + } visionSetMode_t; + typedef struct gclient_s { playerState_s ps; sessionState_t sessionState; // 12572 - char pad0[40]; + unsigned char __pad0[40]; clientConnected_t connected; // 12616 - char pad1[144]; - unsigned int team; // 12764 - char pad2[436]; + unsigned char __pad1[144]; + team_t team; // 12764 + unsigned char __pad2[436]; int flags; // 13204 int spectatorClient; int lastCmdTime; @@ -5519,7 +5650,10 @@ namespace Game int oldbuttons; // 13220 int latched_buttons; // 13224 int buttonsSinceLastFrame; // 13228 - char pad3[700]; // 13232 + unsigned char __pad3[324]; // 13232 + int visionDuration[5]; + char visionName[5][64]; + unsigned char __pad4[36]; } gclient_t; static_assert(sizeof(gclient_t) == 13932); @@ -5619,6 +5753,53 @@ namespace Game static_assert(sizeof(gentity_s) == 0x274); + enum $1C4253065710F064DA9E4D59ED6EC544 + { + ENTFIELD_ENTITY = 0x0, + ENTFIELD_SENTIENT = 0x2000, + ENTFIELD_ACTOR = 0x4000, + ENTFIELD_CLIENT = 0x6000, + ENTFIELD_VEHICLE = 0x8000, + ENTFIELD_MASK = 0xE000, + }; + + enum fieldtype_t + { + F_INT = 0x0, + F_SHORT = 0x1, + F_BYTE = 0x2, + F_FLOAT = 0x3, + F_CSTRING = 0x4, + F_STRING = 0x5, + F_VECTOR = 0x6, + F_ENTITY = 0x7, + F_ENTHANDLE = 0x8, + F_ANGLES_YAW = 0x9, + F_OBJECT = 0xA, + F_MODEL = 0xB, + }; + + struct ent_field_t + { + const char* name; + int ofs; + fieldtype_t type; + void(__cdecl * setter)(gentity_s*, int); + void(__cdecl * getter)(gentity_s*, int); + }; + + struct client_fields_s + { + const char* name; + int ofs; + fieldtype_t type; + void(__cdecl * setter)(gclient_s*, const client_fields_s*); + void(__cdecl * getter)(gclient_s*, const client_fields_s*); + }; + + typedef void(__cdecl * ScriptCallbackEnt)(gentity_s*, int); + typedef void(__cdecl * ScriptCallbackClient)(gclient_s*, const client_fields_s*); + struct lockonFireParms { bool lockon; @@ -5661,7 +5842,7 @@ namespace Game int pureAuthentic; // 135896 char __pad7[133138]; // 135900 short scriptID; // 269038 - int isBot; // 269040 + int bIsTestClient; // 269040 int serverID; // 269044 char __pad8[9224]; // 269048 unsigned __int64 steamID; // 278272 @@ -6880,7 +7061,7 @@ namespace Game SHELLSHOCK_VIEWTYPE_NONE = 0x2, }; - struct shellshock_parms_t + struct shellshock_parms_t { struct { @@ -7196,6 +7377,149 @@ namespace Game TempPriority tempPriority; }; + struct trigger_info_t + { + unsigned __int16 entnum; + unsigned __int16 otherEntnum; + int useCount; + int otherUseCount; + }; + + struct com_parse_mark_t + { + int lines; + const char* text; + int ungetToken; + int backup_lines; + const char* backup_text; + }; + + struct cached_tag_mat_t + { + int time; + int entnum; + unsigned __int16 name; + float tagMat[4][3]; + }; + + struct Turret + { + bool inuse; + int flags; + int fireTime; + float arcmin[2]; + float arcmax[2]; + float dropPitch; + int stance; + int prevStance; + int fireSndDelay; + float userOrigin[3]; + float playerSpread; + int state; + EntHandle target; + float targetOffset[3]; + EntHandle manualTarget; + float manualTargetOffset[3]; + int targetTime; + int stateChangeTime; + int modeChangeTime; + float maxRangeSquared; + int prevTargetIndex; + team_t eTeam; + int convergenceTime[2]; + float targetPos[3]; + float missOffsetNormalized[3]; + float scanSpeed; + float scanDecelYaw; + int scanPauseTime; + bool triggerDown; + float heatLevel; + int heatPenaltyEndTime; + float barrelRollRate; + int autoRotationStopDelay; + int lastAutoRotationRequestTime; + unsigned __int8 fireSnd; + unsigned __int8 fireSndPlayer; + unsigned __int8 stopSnd; + unsigned __int8 stopSndPlayer; + unsigned __int8 scanSnd; + }; + + static_assert(sizeof(Turret) == 0xC4); + + struct level_locals_t + { + gclient_s* clients; + gentity_s* gentities; + int num_entities; + gentity_s* firstFreeEnt; + gentity_s* lastFreeEnt; + Turret* turrets; + void* logFile; + int initializing; + int clientIsSpawning; + objective_t objectives[32]; + int maxclients; + int framenum; + int time; + int previousTime; + int frametime; + int startTime; + int teamScores[4]; + int lastTeammateHealthTime; + int bUpdateScoresForIntermission; + bool teamHasRadar[4]; + bool teamRadarBlocked[4]; + int manualNameChange; + int numConnectedClients; + int sortedClients[18]; + char voteString[1024]; + char voteDisplayString[1024]; + int voteTime; + int voteExecuteTime; + int voteYes; + int voteNo; + int numVotingClients; + SpawnVar spawnVar; + int savepersist; + EntHandle droppedWeaponCue[32]; + float fFogOpaqueDist; + float fFogOpaqueDistSqrd; + int currentPlayerClone; + trigger_info_t pendingTriggerList[256]; + trigger_info_t currentTriggerList[256]; + int pendingTriggerListSize; + int currentTriggerListSize; + int finished; + int bPlayerIgnoreRadiusDamage; + int bPlayerIgnoreRadiusDamageLatched; + int registerWeapons; + int bRegisterItems; + int currentEntityThink; + void* openScriptIOFileHandles[1]; + char* openScriptIOFileBuffers[1]; + com_parse_mark_t currentScriptIOLineMark[1]; + cached_tag_mat_t cachedTagMat; + int scriptPrintChannel; + float compassMapUpperLeft[2]; + float compassMapWorldSize[2]; + float compassNorth[2]; + void* vehicles; + int hudElemLastAssignedSoundID; + }; + + static_assert(sizeof(level_locals_t) == 0x2F78); + + struct WinMouseVars_t + { + int oldButtonState; + tagPOINT oldPos; + bool mouseActive; + bool mouseInitialized; + }; + + static_assert(sizeof(WinMouseVars_t) == 0x10); + #pragma endregion #ifndef IDA diff --git a/src/Main.cpp b/src/Main.cpp index 0e78434b..db9c14ad 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -55,7 +55,7 @@ BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*l Steam::Proxy::RunMod(); // Ensure we're working with our desired binary - char* _module = reinterpret_cast(0x400000); + auto* _module = reinterpret_cast(0x400000); auto hash1 = Utils::Cryptography::JenkinsOneAtATime::Compute(_module + 0x1000, 0x2D531F); // .text auto hash2 = Utils::Cryptography::JenkinsOneAtATime::Compute(_module + 0x2D75FC, 0xBDA04); // .rdata if ((hash1 != 0x54684DBE @@ -67,17 +67,6 @@ BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*l return FALSE; } -#ifndef DISABLE_ANTICHEAT - []() - { - if (!Components::Dedicated::IsEnabled() && !Components::Loader::IsPerformingUnitTests()) - { - Components::AntiCheat::ProtectProcess(); - Components::AntiCheat::PatchThreadCreation(); - } - }(); -#endif - DWORD oldProtect; VirtualProtect(_module + 0x1000, 0x2D6000, PAGE_EXECUTE_READ, &oldProtect); // Protect the .text segment @@ -88,18 +77,6 @@ BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*l { Main::Uninitialize(); } - else if (ul_reason_for_call == DLL_THREAD_ATTACH) - { -#ifndef DISABLE_ANTICHEAT - []() - { - if (!Components::Dedicated::IsEnabled() && !Components::Loader::IsPerformingUnitTests()) - { - Components::AntiCheat::VerifyThreadIntegrity(); - } - }(); -#endif - } return TRUE; } diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index bdec22d1..4480f216 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -90,17 +90,6 @@ #undef min #endif -// VMProtect -// #define USE_VMP -#ifdef USE_VMP -#include -#define __VMProtectBeginUltra VMProtectBeginUltra -#define __VMProtectEnd VMProtectEnd() -#else -#define __VMProtectBeginUltra -#define __VMProtectEnd -#endif - // Protobuf #include "proto/session.pb.h" #include "proto/party.pb.h" diff --git a/src/Utils/String.cpp b/src/Utils/String.cpp index 422a9158..223334a7 100644 --- a/src/Utils/String.cpp +++ b/src/Utils/String.cpp @@ -23,16 +23,24 @@ namespace Utils return result; } - std::string ToLower(std::string input) + std::string ToLower(std::string text) { - std::transform(input.begin(), input.end(), input.begin(), ::tolower); - return input; + std::transform(text.begin(), text.end(), text.begin(), [](const unsigned char input) + { + return static_cast(std::tolower(input)); + }); + + return text; } - std::string ToUpper(std::string input) + std::string ToUpper(std::string text) { - std::transform(input.begin(), input.end(), input.begin(), ::toupper); - return input; + std::transform(text.begin(), text.end(), text.begin(), [](const unsigned char input) + { + return static_cast(std::toupper(input)); + }); + + return text; } std::string DumpHex(const std::string& data, const std::string& separator) @@ -89,12 +97,13 @@ namespace Utils bool StartsWith(const std::string& haystack, const std::string& needle) { - return (haystack.size() >= needle.size() && haystack.substr(0, needle.size()) == needle); + return haystack.find(needle) == 0; // If the pos of the first found char is 0, string starts with 'needle' } bool EndsWith(const std::string& haystack, const std::string& needle) { - return (haystack.size() >= needle.size() && haystack.substr(haystack.size() - needle.size()) == needle); + if (needle.size() > haystack.size()) return false; + return std::equal(needle.rbegin(), needle.rend(), haystack.rbegin()); } int IsSpace(int c) diff --git a/src/Utils/String.hpp b/src/Utils/String.hpp index 0da5f00b..72af16e6 100644 --- a/src/Utils/String.hpp +++ b/src/Utils/String.hpp @@ -11,7 +11,7 @@ namespace Utils static_assert(Buffers != 0 && MinBufferSize != 0, "Buffers and MinBufferSize mustn't be 0"); VAProvider() : currentBuffer(0) {} - ~VAProvider() {} + ~VAProvider() = default; const char* get(const char* format, va_list ap) { @@ -25,7 +25,7 @@ namespace Utils while (true) { - int res = vsnprintf_s(entry->buffer, entry->size, _TRUNCATE, format, ap); + const auto res = _vsnprintf_s(entry->buffer, entry->size, _TRUNCATE, format, ap); if (res > 0) break; // Success if (res == 0) return ""; // Error @@ -75,12 +75,13 @@ namespace Utils const char *VA(const char *fmt, ...); int IsSpace(int c); - std::string ToLower(std::string input); - std::string ToUpper(std::string input); - bool EndsWith(const std::string& haystack, const std::string& needle); + std::string ToLower(std::string text); + std::string ToUpper(std::string text); std::vector Split(const std::string& str, const char delim); void Replace(std::string& string, const std::string& find, const std::string& replace); bool StartsWith(const std::string& haystack, const std::string& needle); + bool EndsWith(const std::string& haystack, const std::string& needle); + std::string& LTrim(std::string& str); std::string& RTrim(std::string& str); std::string& Trim(std::string& str); diff --git a/src/Utils/Utils.cpp b/src/Utils/Utils.cpp index e17480bf..5166b84f 100644 --- a/src/Utils/Utils.cpp +++ b/src/Utils/Utils.cpp @@ -123,10 +123,6 @@ namespace Utils void SafeShellExecute(HWND hwnd, LPCSTR lpOperation, LPCSTR lpFile, LPCSTR lpParameters, LPCSTR lpDirectory, INT nShowCmd) { -#ifndef DISABLE_ANTICHEAT - Components::AntiCheat::LibUnlocker _; -#endif - [=]() { __try