commit 0c2c47899096a1ce7921552759eaa07038d9ef01 Author: ineedbots Date: Mon Jun 28 12:13:34 2021 -0600 Init iw4x base diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3843510 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +*.hpp eol=crlf +*.cpp eol=crlf +*.lua eol=crlf +*.proto eol=crlf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5278875 --- /dev/null +++ b/.gitignore @@ -0,0 +1,153 @@ +### Windows + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Shortcuts +*.lnk + +### OSX + +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Visual Studio + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +build + +# Visual Studio 2015 cache/options directory +.vs/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +### IDA +*.id0 +*.id1 +*.id2 +*.nam +*.til +*.idb +*.i64 +ida/* + +### Custom user files +# User scripts +user*.bat + +# Premake binary +#premake5.exe diff --git a/generate.bat b/generate.bat new file mode 100644 index 0000000..65a3b20 --- /dev/null +++ b/generate.bat @@ -0,0 +1,4 @@ +@echo off +echo Updating submodules... +call git submodule update --init --recursive +call tools\premake5 %* vs2019 diff --git a/premake5.lua b/premake5.lua new file mode 100644 index 0000000..24de5ac --- /dev/null +++ b/premake5.lua @@ -0,0 +1,317 @@ +gitVersioningCommand = "git describe --tags --dirty --always" +gitCurrentBranchCommand = "git symbolic-ref -q --short HEAD" + +-- Quote the given string input as a C string +function cstrquote(value) + result = value:gsub("\\", "\\\\") + result = result:gsub("\"", "\\\"") + result = result:gsub("\n", "\\n") + result = result:gsub("\t", "\\t") + result = result:gsub("\r", "\\r") + result = result:gsub("\a", "\\a") + result = result:gsub("\b", "\\b") + result = "\"" .. result .. "\"" + return result +end + +-- Converts tags in "vX.X.X" format to an array of numbers {X,X,X}. +-- In the case where the format does not work fall back to old {4,2,REVISION}. +function vertonumarr(value, vernumber) + vernum = {} + for num in string.gmatch(value, "%d+") do + table.insert(vernum, tonumber(num)) + end + if #vernum < 3 then + return {4,2,tonumber(vernumber)} + end + return vernum +end + +-- Option to allow copying the DLL file to a custom folder after build +newoption { + trigger = "copy-to", + description = "Optional, copy the DLL to a custom folder after build, define the path here if wanted.", + value = "PATH" +} + +newoption { + trigger = "no-new-structure", + description = "Do not use new virtual path structure (separating headers and source files)." +} + +newoption { + trigger = "copy-pdb", + description = "Copy debug information for binaries as well to the path given via --copy-to." +} + +newaction { + trigger = "version", + description = "Returns the version string for the current commit of the source code.", + onWorkspace = function(wks) + -- get current version via git + local proc = assert(io.popen(gitVersioningCommand, "r")) + local gitDescribeOutput = assert(proc:read('*a')):gsub("%s+", "") + proc:close() + local version = gitDescribeOutput + + proc = assert(io.popen(gitCurrentBranchCommand, "r")) + local gitCurrentBranchOutput = assert(proc:read('*a')):gsub("%s+", "") + local gitCurrentBranchSuccess = proc:close() + if gitCurrentBranchSuccess then + -- We got a branch name, check if it is a feature branch + if gitCurrentBranchOutput ~= "develop" and gitCurrentBranchOutput ~= "master" then + version = version .. "-" .. gitCurrentBranchOutput + end + end + + print(version) + os.exit(0) + end +} + +newaction { + trigger = "generate-buildinfo", + description = "Sets up build information file like version.h.", + onWorkspace = function(wks) + -- get revision number via git + local proc = assert(io.popen("git rev-list --count HEAD", "r")) + local revNumber = assert(proc:read('*a')):gsub("%s+", "") + + -- get current version via git + local proc = assert(io.popen(gitVersioningCommand, "r")) + local gitDescribeOutput = assert(proc:read('*a')):gsub("%s+", "") + proc:close() + + -- get whether this is a clean revision (no uncommitted changes) + proc = assert(io.popen("git status --porcelain", "r")) + local revDirty = (assert(proc:read('*a')) ~= "") + if revDirty then revDirty = 1 else revDirty = 0 end + proc:close() + + -- get current tag name + proc = assert(io.popen("git describe --tags --abbrev=0")) + local tagName = assert(proc:read('*l')) + + -- get old version number from version.hpp if any + local oldVersion = "(none)" + local oldVersionHeader = io.open(wks.location .. "/src/version.h", "r") + if oldVersionHeader ~= nil then + local oldVersionHeaderContent = assert(oldVersionHeader:read('*l')) + while oldVersionHeaderContent do + m = string.match(oldVersionHeaderContent, "#define GIT_DESCRIBE (.+)%s*$") + if m ~= nil then + oldVersion = m + end + + oldVersionHeaderContent = oldVersionHeader:read('*l') + end + end + + -- generate version.hpp with a revision number if not equal + gitDescribeOutputQuoted = cstrquote(gitDescribeOutput) + if oldVersion ~= gitDescribeOutputQuoted then + print ("Update " .. oldVersion .. " -> " .. gitDescribeOutputQuoted) + local versionHeader = assert(io.open(wks.location .. "/src/version.h", "w")) + versionHeader:write("/*\n") + versionHeader:write(" * Automatically generated by premake5.\n") + versionHeader:write(" * Do not touch, you fucking moron!\n") + versionHeader:write(" */\n") + versionHeader:write("\n") + versionHeader:write("#define GIT_DESCRIBE " .. gitDescribeOutputQuoted .. "\n") + versionHeader:write("#define GIT_DIRTY " .. revDirty .. "\n") + versionHeader:write("#define GIT_TAG " .. cstrquote(tagName) .. "\n") + versionHeader:write("\n") + versionHeader:write("// Legacy definitions (needed for update check)\n") + versionHeader:write("#define REVISION " .. revNumber .. "\n") + versionHeader:write("\n") + versionHeader:write("// Version transformed for RC files\n") + versionHeader:write("#define VERSION_RC " .. table.concat(vertonumarr(tagName, revNumber), ",") .. "\n") + versionHeader:write("\n") + versionHeader:write("// Alias definitions\n") + versionHeader:write("#define VERSION GIT_DESCRIBE\n") + versionHeader:write("#define SHORTVERSION " .. cstrquote(table.concat(vertonumarr(tagName, revNumber), ".")) .. "\n") + versionHeader:close() + local versionHeader = assert(io.open(wks.location .. "/src/version.hpp", "w")) + versionHeader:write("/*\n") + versionHeader:write(" * Automatically generated by premake5.\n") + versionHeader:write(" * Do not touch, you fucking moron!\n") + versionHeader:write(" *\n") + versionHeader:write(" * This file exists for reasons of complying with our coding standards.\n") + versionHeader:write(" *\n") + versionHeader:write(" * The Resource Compiler will ignore any content from C++ header files if they're not from STDInclude.hpp.\n") + versionHeader:write(" * That's the reason why we now place all version info in version.h instead.\n") + versionHeader:write(" */\n") + versionHeader:write("\n") + versionHeader:write("#include \".\\version.h\"\n") + versionHeader:close() + end + end +} + +workspace "cod2m" + startproject "cod2m" + location "./build" + objdir "%{wks.location}/obj" + targetdir "%{wks.location}/bin/%{cfg.buildcfg}" + buildlog "%{wks.location}/obj/%{cfg.architecture}/%{cfg.buildcfg}/%{prj.name}/%{prj.name}.log" + configurations { "Debug", "Release" } + architecture "x86" + platforms "x86" + --exceptionhandling ("SEH") + + staticruntime "On" + + configuration "windows" + defines { "_WINDOWS", "WIN32" } + + configuration "Release*" + defines { "NDEBUG" } + flags { "MultiProcessorCompile", "LinkTimeOptimization", "No64BitChecks" } + optimize "On" + rtti ("Off") + + configuration "Debug*" + defines { "DEBUG", "_DEBUG" } + flags { "MultiProcessorCompile", "No64BitChecks" } + optimize "Debug" + if symbols ~= nil then + symbols "On" + else + flags { "Symbols" } + end + + project "d3d9" + kind "SharedLib" + language "C++" + files { + "./src/**.rc", + "./src/**.hpp", + "./src/**.cpp", + --"./src/**.proto", + } + includedirs { + "%{prj.location}/src", + "./src", + "./lib/include", + } + syslibdirs { + "./lib/bin", + } + resincludedirs { + "$(ProjectDir)src" -- fix for VS IDE + } + + + -- Pre-compiled header + pchheader "STDInclude.hpp" -- must be exactly same as used in #include directives + pchsource "src/STDInclude.cpp" -- real path + buildoptions { "/Zm200" } + + -- fix vpaths for protobuf sources + vpaths + { + ["*"] = { "./src/**" }, + --["Proto/Generated"] = { "**.pb.*" }, -- meh. + } + + -- Virtual paths + if not _OPTIONS["no-new-structure"] then + vpaths + { + ["Headers/*"] = { "./src/**.hpp" }, + ["Sources/*"] = { "./src/**.cpp" }, + ["Resource/*"] = { "./src/**.rc" }, + --["Proto/Definitions/*"] = { "./src/Proto/**.proto" }, + --["Proto/Generated/*"] = { "**.pb.*" }, -- meh. + } + end + + vpaths + { + ["Docs/*"] = { "**.txt","**.md" }, + } + + -- Pre-build + prebuildcommands + { + "pushd %{_MAIN_SCRIPT_DIR}", + "tools\\premake5 generate-buildinfo", + "popd", + } + + -- Post-build + if _OPTIONS["copy-to"] then + saneCopyToPath = string.gsub(_OPTIONS["copy-to"] .. "\\", "\\\\", "\\") + postbuildcommands { + "if not exist \"" .. saneCopyToPath .. "\" mkdir \"" .. saneCopyToPath .. "\"", + } + + if _OPTIONS["copy-pdb"] then + postbuildcommands { + "copy /y \"$(TargetDir)*.pdb\" \"" .. saneCopyToPath .. "\"", + } + end + + -- This has to be the last one, as otherwise VisualStudio will succeed building even if copying fails + postbuildcommands { + "copy /y \"$(TargetDir)*.dll\" \"" .. saneCopyToPath .. "\"", + } + end + + -- Specific configurations + flags { "UndefinedIdentifiers" } + warnings "Extra" + + if symbols ~= nil then + symbols "On" + else + flags { "Symbols" } + end + + configuration "Release*" + flags { + "FatalCompileWarnings", + "FatalLinkWarnings", + } + configuration {} + + --[[ + -- Generate source code from protobuf definitions + rules { "ProtobufCompiler" } + + -- Workaround: Consume protobuf generated source files + matches = os.matchfiles(path.join("src/Proto/**.proto")) + for i, srcPath in ipairs(matches) do + basename = path.getbasename(srcPath) + files + { + string.format("%%{prj.location}/src/proto/%s.pb.h", basename), + string.format("%%{prj.location}/src/proto/%s.pb.cc", basename), + } + end + includedirs + { + "%{prj.location}/src/proto", + } + filter "files:**.pb.*" + flags { + "NoPCH", + } + buildoptions { + "/wd4100", -- "Unused formal parameter" + "/wd4389", -- "Signed/Unsigned mismatch" + "/wd6011", -- "Dereferencing NULL pointer" + "/wd4125", -- "Decimal digit terminates octal escape sequence" + } + defines { + "_SCL_SECURE_NO_WARNINGS", + } + filter {} + ]] + +workspace "*" + buildoptions { + "/std:c++latest" + } + systemversion "latest" + defines { "_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS" } diff --git a/src/Game/MP.cpp b/src/Game/MP.cpp new file mode 100644 index 0000000..e4ee6ef --- /dev/null +++ b/src/Game/MP.cpp @@ -0,0 +1,9 @@ +#include "STDInclude.hpp" + +namespace MP +{ + void PatchT4() + { + MessageBoxA(nullptr, "MP", "DEBUG", 0); + } +} diff --git a/src/Game/MP.hpp b/src/Game/MP.hpp new file mode 100644 index 0000000..2f3ddc8 --- /dev/null +++ b/src/Game/MP.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace MP +{ + void PatchT4(); +} diff --git a/src/Game/MP/Functions.hpp b/src/Game/MP/Functions.hpp new file mode 100644 index 0000000..e69de29 diff --git a/src/Game/MP/Structs.hpp b/src/Game/MP/Structs.hpp new file mode 100644 index 0000000..e69de29 diff --git a/src/Game/SP.cpp b/src/Game/SP.cpp new file mode 100644 index 0000000..fe5bfc1 --- /dev/null +++ b/src/Game/SP.cpp @@ -0,0 +1,9 @@ +#include "STDInclude.hpp" + +namespace SP +{ + void PatchT4() + { + MessageBoxA(nullptr, "SP", "DEBUG", 0); + } +} diff --git a/src/Game/SP.hpp b/src/Game/SP.hpp new file mode 100644 index 0000000..2befd43 --- /dev/null +++ b/src/Game/SP.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace SP +{ + void PatchT4(); +} diff --git a/src/Game/SP/Functions.hpp b/src/Game/SP/Functions.hpp new file mode 100644 index 0000000..e69de29 diff --git a/src/Game/SP/Structs.hpp b/src/Game/SP/Structs.hpp new file mode 100644 index 0000000..e69de29 diff --git a/src/Main.cpp b/src/Main.cpp new file mode 100644 index 0000000..2d6e8e5 --- /dev/null +++ b/src/Main.cpp @@ -0,0 +1,89 @@ +#include "STDInclude.hpp" + +namespace Main +{ + static BYTE originalCode[5]; + static PBYTE originalEP = 0; + + void UnprotectModule(HMODULE hModule) + { + PIMAGE_DOS_HEADER header = (PIMAGE_DOS_HEADER)hModule; + PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD)hModule + header->e_lfanew); + + // unprotect the entire PE image + SIZE_T size = ntHeader->OptionalHeader.SizeOfImage; + DWORD oldProtect; + VirtualProtect((LPVOID)hModule, size, PAGE_EXECUTE_READWRITE, &oldProtect); + } + + void DoInit() + { + // return to the original EP + memcpy(originalEP, &originalCode, sizeof(originalCode)); + + // unprotect our entire PE image + HMODULE hModule; + if (SUCCEEDED(GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)DoInit, &hModule))) + { + UnprotectModule(hModule); + } + + // detect which executable's patches to apply + DWORD dataStrData = Utils::Hook::Get(0x881CAC); + if (dataStrData == 0x62616E55) + MP::PatchT4(); + else + SP::PatchT4(); + + hModule = GetModuleHandle(NULL); + PIMAGE_DOS_HEADER header = (PIMAGE_DOS_HEADER)hModule; + PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD)hModule + header->e_lfanew); + + // back up original code + originalEP = (PBYTE)((DWORD)hModule + ntHeader->OptionalHeader.AddressOfEntryPoint); + + __asm + { + jmp originalEP + } + } + + void SetSafeInit() + { + // find the entry point for the executable process, set page access, and replace the EP + HMODULE hModule = GetModuleHandle(NULL); // passing NULL should be safe even with the loader lock being held (according to ReactOS ldr.c) + + if (hModule) + { + PIMAGE_DOS_HEADER header = (PIMAGE_DOS_HEADER)hModule; + PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD)hModule + header->e_lfanew); + + UnprotectModule(hModule); + + // back up original code + PBYTE ep = (PBYTE)((DWORD)hModule + ntHeader->OptionalHeader.AddressOfEntryPoint); + memcpy(originalCode, ep, sizeof(originalCode)); + + // patch to call our EP + int newEP = (int)DoInit - ((int)ep + 5); + ep[0] = 0xE9; // for some reason this doesn't work properly when run under the debugger + memcpy(&ep[1], &newEP, 4); + + originalEP = ep; + } + } +} + + +bool APIENTRY DllMain(HMODULE, DWORD dwReason, LPVOID) +{ + if (dwReason == DLL_PROCESS_ATTACH) + { + DWORD textSegData = Utils::Hook::Get(0x401000); + + // detect for all executables to hook into + //if (textSegData == 0x9EF490B8 || textSegData == 0x83EC8B55) + Main::SetSafeInit(); + } + return true; +} diff --git a/src/Resource.rc b/src/Resource.rc new file mode 100644 index 0000000..e2c3bdb --- /dev/null +++ b/src/Resource.rc @@ -0,0 +1,97 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) + +#include "STDInclude.hpp" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "windows.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "#include ""windows.h""\r\n" + "\0" +END + +2 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE VFT_DLL + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "CoD2m" +#ifdef _DEBUG + VALUE "FileDescription", "CoD2 client/server modification (DEBUG)" +#else + VALUE "FileDescription", "CoD2 client/server modification" +#endif + VALUE "FileVersion", SHORTVERSION + VALUE "InternalName", "cod2m" + VALUE "LegalCopyright", "All rights reserved." + VALUE "OriginalFilename", "d3d9.dll" + VALUE "ProductName", "CoD2m" + VALUE "ProductVersion", SHORTVERSION + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/src/SDLLP.cpp b/src/SDLLP.cpp new file mode 100644 index 0000000..d3c134b --- /dev/null +++ b/src/SDLLP.cpp @@ -0,0 +1,90 @@ +// --------------------------------------+ +// System Dynamic Link Library Proxy +// by momo5502 +// --------------------------------------+ + +#include "STDInclude.hpp" + +// Macro to declare an export +// --------------------------------------+ +#define EXPORT(_export) extern "C" __declspec(naked) __declspec(dllexport) void _export() { static FARPROC function = 0; if(!function) function = SDLLP::GetExport(__FUNCTION__, LIBRARY); __asm { jmp function } } + +// Static class +// --------------------------------------+ +class SDLLP +{ +private: + static std::map mLibraries; + + static void Log(const char* message, ...); + static void LoadLibrary(const char* library); + static bool IsLibraryLoaded(const char* library); + +public: + static FARPROC GetExport(const char* function, const char* library); +}; + +// Class variable declarations +// --------------------------------------+ +std::map SDLLP::mLibraries; + +// Load necessary library +// --------------------------------------+ +void SDLLP::LoadLibrary(const char* library) +{ + Log("[SDLLP] Loading library '%s'.", library); + + CHAR mPath[MAX_PATH]; + + GetSystemDirectoryA(mPath, MAX_PATH); + strcat_s(mPath, "\\"); + strcat_s(mPath, library); + + mLibraries[library] = ::LoadLibraryA(mPath); + + if (!IsLibraryLoaded(library)) Log("[SDLLP] Unable to load library '%s'.", library); +} + +// Check if export already loaded +// --------------------------------------+ +bool SDLLP::IsLibraryLoaded(const char* library) +{ + return (mLibraries.find(library) != mLibraries.end() && mLibraries[library]); +} + +// Get export address +// --------------------------------------+ +FARPROC SDLLP::GetExport(const char* function, const char* library) +{ + Log("[SDLLP] Export '%s' requested from %s.", function, library); + + if (!IsLibraryLoaded(library)) LoadLibrary(library); + + FARPROC address = GetProcAddress(mLibraries[library], function); + + if (!address) Log("[SDLLP] Unable to load export '%s' from library '%s'.", function, library); + return address; +} + +// Write debug string +// --------------------------------------+ +void SDLLP::Log(const char* message, ...) +{ + CHAR buffer[1024]; + va_list ap; + + va_start(ap, message); + vsprintf(buffer, message, ap); + va_end(ap); + + OutputDebugStringA(buffer); +} + +// --------------------------------------+ +// Adapt export functions and library +// --------------------------------------+ + +#define LIBRARY "d3d9.dll" +EXPORT(D3DPERF_BeginEvent) +EXPORT(D3DPERF_EndEvent) +EXPORT(Direct3DCreate9) diff --git a/src/STDInclude.cpp b/src/STDInclude.cpp new file mode 100644 index 0000000..534f016 --- /dev/null +++ b/src/STDInclude.cpp @@ -0,0 +1,115 @@ +#include "STDInclude.hpp" + +// Rename sections +#ifndef DEBUG +#pragma comment(linker, "/merge:.text=.UPX0") +#pragma comment(linker, "/merge:.data=.UPX1") +#pragma comment(linker, "/merge:.rdata=.UPX2") +#pragma comment(linker, "/merge:.tls=.UPX3") +#pragma comment(linker, "/merge:.gfids=.UPX4") +#endif + +#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") + +// Do necessary assertions here +// Some compilers treat them differently which causes a size mismatch + +// WinAPI types +AssertSize(DWORD, 4); +AssertSize(WORD, 2); +AssertSize(BYTE, 1); + +// 128 bit integers (only x64) +//AssertSize(__int128, 16); +//AssertSize(unsigned __int128, 16); + +// 64 bit integers +AssertSize(__int64, 8); +AssertSize(unsigned __int64, 8); +AssertSize(long long, 8); +AssertSize(unsigned long long, 8); +AssertSize(int64_t, 8); +AssertSize(uint64_t, 8); +AssertSize(std::int64_t, 8); +AssertSize(std::uint64_t, 8); + +// 64 bit double precision floating point numbers +AssertSize(double, 8); + +// 32 bit integers +AssertSize(__int32, 4); +AssertSize(unsigned __int32, 4); +AssertSize(int, 4); +AssertSize(unsigned int, 4); +AssertSize(long, 4); +AssertSize(unsigned long, 4); +AssertSize(int32_t, 4); +AssertSize(uint32_t, 4); +AssertSize(std::int32_t, 4); +AssertSize(std::uint32_t, 4); + +// 32 bit single precision floating point numbers +AssertSize(float, 4); + +// 16 bit integers +AssertSize(__int16, 2); +AssertSize(unsigned __int16, 2); +AssertSize(short, 2); +AssertSize(unsigned short, 2); +AssertSize(int16_t, 2); +AssertSize(uint16_t, 2); +AssertSize(std::int16_t, 2); +AssertSize(std::uint16_t, 2); + +// 8 bit integers +AssertSize(bool, 1); +AssertSize(__int8, 1); +AssertSize(unsigned __int8, 1); +AssertSize(char, 1); +AssertSize(unsigned char, 1); +AssertSize(int8_t, 1); +AssertSize(uint8_t, 1); +AssertSize(std::int8_t, 1); +AssertSize(std::uint8_t, 1); + +// Ensure pointers are 4 bytes in size (32-bit) +// ReSharper disable CppRedundantBooleanExpressionArgument +static_assert(sizeof(intptr_t) == 4 && sizeof(void*) == 4 && sizeof(size_t) == 4, "This doesn't seem to be a 32-bit environment!"); +// ReSharper restore CppRedundantBooleanExpressionArgument + +#if !defined(_M_IX86) +#error "Invalid processor achritecture!" +#endif + +extern "C" +{ + // Disable telemetry data logging + void __cdecl __vcrt_initialize_telemetry_provider() {} + void __cdecl __telemetry_main_invoke_trigger() {} + void __cdecl __telemetry_main_return_trigger() {} + void __cdecl __vcrt_uninitialize_telemetry_provider() {} + + // Enable 'High Performance Graphics' + __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; + + // Tommath fixes + int s_read_arc4random(void*, size_t) + { + return -1; + } + + int s_read_getrandom(void*, size_t) + { + return -1; + } + + int s_read_urandom(void*, size_t) + { + return -1; + } + + int s_read_ltm_rng(void*, size_t) + { + return -1; + } +}; diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp new file mode 100644 index 0000000..c1e271b --- /dev/null +++ b/src/STDInclude.hpp @@ -0,0 +1,120 @@ +#pragma once + +// Version number +#include "version.h" + +#ifndef RC_INVOKED + +#define _HAS_CXX17 1 +#define _HAS_CXX20 1 +#define VC_EXTRALEAN +#define WIN32_LEAN_AND_MEAN +#define _CRT_SECURE_NO_WARNINGS + +// Requires Visual Leak Detector plugin: http://vld.codeplex.com/ +#define VLD_FORCE_ENABLE +//#include +#pragma warning(disable: 4740) + +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma warning(push) +#pragma warning(disable: 4091) +#pragma warning(disable: 4244) +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +// Experimental C++17 features +#include +#include + +#pragma warning(pop) + +// Usefull for debugging +template class Sizer { }; +#define BindNum(x, y) Sizer y; +#define Size_Of(x, y) BindNum(sizeof(x), y) +#define Offset_Of(x, y, z) BindNum(offsetof(x, y), z) + +// Submodules +// Ignore the warnings, it's not our code! +#pragma warning(push) +#pragma warning(disable: 4005) +#pragma warning(disable: 4091) +#pragma warning(disable: 4100) +#pragma warning(disable: 4244) +#pragma warning(disable: 4389) +#pragma warning(disable: 4702) +#pragma warning(disable: 4800) +#pragma warning(disable: 4996) // _CRT_SECURE_NO_WARNINGS +#pragma warning(disable: 5054) +#pragma warning(disable: 6001) +#pragma warning(disable: 6011) +#pragma warning(disable: 6031) +#pragma warning(disable: 6255) +#pragma warning(disable: 6258) +#pragma warning(disable: 6386) +#pragma warning(disable: 6387) + +#ifdef max +#undef max +#endif + +#ifdef min +#undef min +#endif + +#pragma warning(pop) + +#include "Utils/Hooking.hpp" + +#include "Game/MP.hpp" +#include "Game/SP.hpp" + +// Libraries +#pragma comment(lib, "Winmm.lib") +#pragma comment(lib, "Crypt32.lib") +#pragma comment(lib, "Ws2_32.lib") +#pragma comment(lib, "Wininet.lib") +#pragma comment(lib, "shlwapi.lib") +#pragma comment(lib, "Urlmon.lib") +#pragma comment(lib, "Advapi32.lib") +#pragma comment(lib, "rpcrt4.lib") +#pragma comment(lib, "dbghelp.lib") + +// Enable additional literals +using namespace std::literals; + +#endif + +#define STRINGIZE_(x) #x +#define STRINGIZE(x) STRINGIZE_(x) + +#define AssertSize(x, size) static_assert(sizeof(x) == size, STRINGIZE(x) " structure has an invalid size.") +#define AssertOffset(x, y, offset) static_assert(offsetof(x, y) == offset, STRINGIZE(x) "::" STRINGIZE(y) " is not at the right offset.") + +// Resource stuff +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +// Defines below make accessing the resources from the code easier. +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/Utils/Hooking.cpp b/src/Utils/Hooking.cpp new file mode 100644 index 0000000..bf529da --- /dev/null +++ b/src/Utils/Hooking.cpp @@ -0,0 +1,256 @@ +#include "STDInclude.hpp" + +namespace Utils +{ + std::map Hook::Interceptor::IReturn; + std::map Hook::Interceptor::ICallbacks; + + void Hook::Signature::process() + { + if (this->signatures.empty()) return; + + char* _start = reinterpret_cast(this->start); + + unsigned int sigCount = this->signatures.size(); + Hook::Signature::Container* containers = this->signatures.data(); + + for (size_t i = 0; i < this->length; ++i) + { + char* address = _start + i; + + for (unsigned int k = 0; k < sigCount; ++k) + { + Hook::Signature::Container* container = &containers[k]; + + unsigned int j; + for (j = 0; j < strlen(container->mask); ++j) + { + if (container->mask[j] != '?' &&container->signature[j] != address[j]) + { + break; + } + } + + if (j == strlen(container->mask)) + { + container->callback(address); + } + } + } + } + + void Hook::Signature::add(Hook::Signature::Container& container) + { + Hook::Signature::signatures.push_back(container); + } + + void Hook::Interceptor::Install(void* place, void* stub) + { + return Hook::Interceptor::Install(place, static_cast(stub)); + } + + void Hook::Interceptor::Install(void* place, void(*stub)()) + { + return Hook::Interceptor::Install(reinterpret_cast(place), stub); + } + + void Hook::Interceptor::Install(void** place, void(*stub)()) + { + Hook::Interceptor::IReturn[place] = *place; + Hook::Interceptor::ICallbacks[place] = stub; + *place = Hook::Interceptor::InterceptionStub; + } + + __declspec(naked) void Hook::Interceptor::InterceptionStub() + { + __asm + { + sub esp, 4h // Reserve space on the stack for the return address + pushad // Store registers + + lea eax, [esp + 20h] // Load initial stack pointer + push eax // Push it onto the stack + + call Hook::Interceptor::RunCallback // Run the callback based on the given stack pointer + call Hook::Interceptor::PopReturn // Get the initial return address according to the stack pointer + + add esp, 4h // Clear the stack + + mov [esp + 20h], eax // Store the return address at the reserved space + popad // Restore the registers + + retn // Return (jump to our return address) + } + } + + void Hook::Interceptor::RunCallback(void* place) + { + auto iCallback = Hook::Interceptor::ICallbacks.find(place); + if (iCallback != Hook::Interceptor::ICallbacks.end()) + { + iCallback->second(); + Hook::Interceptor::ICallbacks.erase(iCallback); + } + } + + void* Hook::Interceptor::PopReturn(void* _place) + { + void* retVal = nullptr; + + auto iReturn = Hook::Interceptor::IReturn.find(_place); + if (iReturn != Hook::Interceptor::IReturn.end()) + { + retVal = iReturn->second; + Hook::Interceptor::IReturn.erase(iReturn); + } + + return retVal; + } + + Hook::~Hook() + { + if (this->initialized) + { + this->uninstall(); + } + } + + Hook* Hook::initialize(DWORD _place, void(*_stub)(), bool _useJump) + { + return this->initialize(_place, reinterpret_cast(_stub), _useJump); + } + + Hook* Hook::initialize(DWORD _place, void* _stub, bool _useJump) + { + return this->initialize(reinterpret_cast(_place), _stub, _useJump); + } + + Hook* Hook::initialize(void* _place, void* _stub, bool _useJump) + { + if (this->initialized) return this; + this->initialized = true; + + this->useJump = _useJump; + this->place = _place; + this->stub = _stub; + + this->original = static_cast(this->place) + 5 + *reinterpret_cast((static_cast(this->place) + 1)); + + return this; + } + + Hook* Hook::install(bool unprotect, bool keepUnprotected) + { + std::lock_guard _(this->stateMutex); + + if (!this->initialized || this->installed) + { + return this; + } + + this->installed = true; + + if (unprotect) VirtualProtect(this->place, sizeof(this->buffer), PAGE_EXECUTE_READWRITE, &this->protection); + std::memcpy(this->buffer, this->place, sizeof(this->buffer)); + + char* code = static_cast(this->place); + + *code = static_cast(this->useJump ? 0xE9 : 0xE8); + + *reinterpret_cast(code + 1) = reinterpret_cast(this->stub) - (reinterpret_cast(this->place) + 5); + + if (unprotect && !keepUnprotected) VirtualProtect(this->place, sizeof(this->buffer), this->protection, &this->protection); + + FlushInstructionCache(GetCurrentProcess(), this->place, sizeof(this->buffer)); + + return this; + } + + void Hook::quick() + { + if (Hook::installed) + { + Hook::installed = false; + } + } + + Hook* Hook::uninstall(bool unprotect) + { + std::lock_guard _(this->stateMutex); + + if (!this->initialized || !this->installed) + { + return this; + } + + this->installed = false; + + if (unprotect) VirtualProtect(this->place, sizeof(this->buffer), PAGE_EXECUTE_READWRITE, &this->protection); + + std::memcpy(this->place, this->buffer, sizeof(this->buffer)); + + if (unprotect) VirtualProtect(this->place, sizeof(this->buffer), this->protection, &this->protection); + + FlushInstructionCache(GetCurrentProcess(), this->place, sizeof(this->buffer)); + + return this; + } + + void* Hook::getAddress() + { + return this->place; + } + + void Hook::Nop(void* place, size_t length) + { + DWORD oldProtect; + VirtualProtect(place, length, PAGE_EXECUTE_READWRITE, &oldProtect); + + memset(place, 0x90, length); + + VirtualProtect(place, length, oldProtect, &oldProtect); + FlushInstructionCache(GetCurrentProcess(), place, length); + } + + void Hook::Nop(DWORD place, size_t length) + { + Nop(reinterpret_cast(place), length); + } + + void Hook::SetString(void* place, const char* string, size_t length) + { + DWORD oldProtect; + VirtualProtect(place, length + 1, PAGE_EXECUTE_READWRITE, &oldProtect); + + strncpy_s(static_cast(place), length, string, length); + + VirtualProtect(place, length + 1, oldProtect, &oldProtect); + } + + void Hook::SetString(DWORD place, const char* string, size_t length) + { + Hook::SetString(reinterpret_cast(place), string, length); + } + + void Hook::SetString(void* place, const char* string) + { + Hook::SetString(place, string, strlen(static_cast(place))); + } + + void Hook::SetString(DWORD place, const char* string) + { + Hook::SetString(reinterpret_cast(place), string); + } + + void Hook::RedirectJump(void* place, void* stub) + { + char* operandPtr = static_cast(place) + 2; + int newOperand = reinterpret_cast(stub) - (reinterpret_cast(place) + 6); + Utils::Hook::Set(operandPtr, newOperand); + } + + void Hook::RedirectJump(DWORD place, void* stub) + { + Hook::RedirectJump(reinterpret_cast(place), stub); + } +} diff --git a/src/Utils/Hooking.hpp b/src/Utils/Hooking.hpp new file mode 100644 index 0000000..d40e25f --- /dev/null +++ b/src/Utils/Hooking.hpp @@ -0,0 +1,185 @@ +#pragma once + +#define HOOK_JUMP true +#define HOOK_CALL false + +namespace Utils +{ + class Hook + { + public: + class Signature + { + public: + struct Container + { + const char* signature; + const char* mask; + std::function callback; + }; + + Signature(void* _start, size_t _length) : start(_start), length(_length) {} + Signature(DWORD _start, size_t _length) : Signature(reinterpret_cast(_start), _length) {} + Signature() : Signature(0x400000, 0x800000) {} + + void process(); + void add(Container& container); + + private: + void* start; + size_t length; + std::vector signatures; + }; + + class Interceptor + { + public: + static void Install(void* place, void* stub); + static void Install(void* place, void(*stub)()); + static void Install(void** place, void(*stub)()); + + private: + static std::map IReturn; + static std::map ICallbacks; + + static void InterceptionStub(); + static void RunCallback(void* place); + static void* PopReturn(void* place); + }; + + Hook() : initialized(false), installed(false), place(nullptr), stub(nullptr), original(nullptr), useJump(false), protection(0) { ZeroMemory(this->buffer, sizeof(this->buffer)); } + + Hook(void* place, void* stub, bool useJump = true) : Hook() { this->initialize(place, stub, useJump); } + Hook(void* place, void(*stub)(), bool useJump = true) : Hook(place, reinterpret_cast(stub), useJump) {} + + Hook(DWORD place, void* stub, bool useJump = true) : Hook(reinterpret_cast(place), stub, useJump) {} + Hook(DWORD place, DWORD stub, bool useJump = true) : Hook(reinterpret_cast(place), reinterpret_cast(stub), useJump) {} + Hook(DWORD place, void(*stub)(), bool useJump = true) : Hook(reinterpret_cast(place), reinterpret_cast(stub), useJump) {} + + ~Hook(); + + Hook* initialize(void* place, void* stub, bool useJump = true); + Hook* initialize(DWORD place, void* stub, bool useJump = true); + Hook* initialize(DWORD place, void(*stub)(), bool useJump = true); // For lambdas + Hook* install(bool unprotect = true, bool keepUnprotected = false); + Hook* uninstall(bool unprotect = true); + + void* getAddress(); + void quick(); + + template static std::function Call(DWORD function) + { + return std::function(reinterpret_cast(function)); + } + + template static std::function Call(FARPROC function) + { + return Call(reinterpret_cast(function)); + } + + template static std::function Call(void* function) + { + return Call(reinterpret_cast(function)); + } + + static void SetString(void* place, const char* string, size_t length); + static void SetString(DWORD place, const char* string, size_t length); + + static void SetString(void* place, const char* string); + static void SetString(DWORD place, const char* string); + + static void Nop(void* place, size_t length); + static void Nop(DWORD place, size_t length); + + static void RedirectJump(void* place, void* stub); + static void RedirectJump(DWORD place, void* stub); + + template static void Set(void* place, T value) + { + DWORD oldProtect; + VirtualProtect(place, sizeof(T), PAGE_EXECUTE_READWRITE, &oldProtect); + + *static_cast(place) = value; + + VirtualProtect(place, sizeof(T), oldProtect, &oldProtect); + FlushInstructionCache(GetCurrentProcess(), place, sizeof(T)); + } + + template static void Set(DWORD place, T value) + { + return Set(reinterpret_cast(place), value); + } + + template static void Xor(void* place, T value) + { + DWORD oldProtect; + VirtualProtect(place, sizeof(T), PAGE_EXECUTE_READWRITE, &oldProtect); + + *static_cast(place) ^= value; + + VirtualProtect(place, sizeof(T), oldProtect, &oldProtect); + FlushInstructionCache(GetCurrentProcess(), place, sizeof(T)); + } + + template static void Xor(DWORD place, T value) + { + return Xor(reinterpret_cast(place), value); + } + + template static void Or(void* place, T value) + { + DWORD oldProtect; + VirtualProtect(place, sizeof(T), PAGE_EXECUTE_READWRITE, &oldProtect); + + *static_cast(place) |= value; + + VirtualProtect(place, sizeof(T), oldProtect, &oldProtect); + FlushInstructionCache(GetCurrentProcess(), place, sizeof(T)); + } + + template static void Or(DWORD place, T value) + { + return Or(reinterpret_cast(place), value); + } + + template static void And(void* place, T value) + { + DWORD oldProtect; + VirtualProtect(place, sizeof(T), PAGE_EXECUTE_READWRITE, &oldProtect); + + *static_cast(place) &= value; + + VirtualProtect(place, sizeof(T), oldProtect, &oldProtect); + FlushInstructionCache(GetCurrentProcess(), place, sizeof(T)); + } + + template static void And(DWORD place, T value) + { + return And(reinterpret_cast(place), value); + } + + template static T Get(void* place) + { + return *static_cast(place); + } + + template static T Get(DWORD place) + { + return Get(reinterpret_cast(place)); + } + + private: + bool initialized; + bool installed; + + void* place; + void* stub; + void* original; + char buffer[5]; + bool useJump; + + DWORD protection; + + std::mutex stateMutex; + }; +} diff --git a/tools/premake5.exe b/tools/premake5.exe new file mode 100644 index 0000000..9048d51 Binary files /dev/null and b/tools/premake5.exe differ diff --git a/tools/protoc.exe b/tools/protoc.exe new file mode 100644 index 0000000..0745228 Binary files /dev/null and b/tools/protoc.exe differ