diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4a97a60 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Lovro Pleše + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3152025 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# UbiTMNTHax - for Teenage Mutant Ninja Turtles (2007/Ubisoft) + +This is a very simple plugin for TMNT by Ubisoft which introduces some quality of life fixes to the game. + +## Features + +- Restore native windowed mode - no need for DXWnd + +- Fixes native window mode cursor position reading + +- Allow borderless windowed mode + +- Allow window resizing, relocating of borderless window + +- Allow launching without the TMNT Launcher/HW detector + +- Allow launching multiple instances of the game + +- Allow launching of game without the disc inserted (no need for mini image in virtual CD) + +- Add console window for debug output + +## Instructions + +- Extract the release package on game root directory + +- Configure your settings in scripts\UbiTMNTHax.ini + +- If launching for the first time, launch the game with the regular TMNT.exe + +- Else, you can launch the game with TMNTGame.exe directly + +- For windowed mode, set Fullscreen to 0 in Hardware.ini found in the TMNT data directory (AppData\Roaming\TMNT) and then launch the game + +## Compatibility + +This was made with the executable version 1.0.0.188 (md5: 5D084D4367688490CB2FC2D2E24CF906) in mind + +## Credits + +ThirteenAG - [Ultimate ASI Loader](https://github.com/ThirteenAG/Ultimate-ASI-Loader) and [IniReader](https://github.com/ThirteenAG/IniReader) + +thelink2012 - [injector](https://github.com/thelink2012/injector) diff --git a/UbiTMNTHax.filters b/UbiTMNTHax.filters new file mode 100644 index 0000000..95c6df3 --- /dev/null +++ b/UbiTMNTHax.filters @@ -0,0 +1,33 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/UbiTMNTHax.sln b/UbiTMNTHax.sln new file mode 100644 index 0000000..0d3d262 --- /dev/null +++ b/UbiTMNTHax.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UbiTMNTHax", "UbiTMNTHax.vcxproj", "{3C558AD9-5F9C-4A14-8F07-800F46C132C7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x86 = Debug|x86 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3C558AD9-5F9C-4A14-8F07-800F46C132C7}.Debug|x86.ActiveCfg = Debug|Win32 + {3C558AD9-5F9C-4A14-8F07-800F46C132C7}.Debug|x86.Build.0 = Debug|Win32 + {3C558AD9-5F9C-4A14-8F07-800F46C132C7}.Release|x86.ActiveCfg = Release|Win32 + {3C558AD9-5F9C-4A14-8F07-800F46C132C7}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/UbiTMNTHax.vcxproj b/UbiTMNTHax.vcxproj new file mode 100644 index 0000000..0e59560 --- /dev/null +++ b/UbiTMNTHax.vcxproj @@ -0,0 +1,109 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {3C558AD9-5F9C-4A14-8F07-800F46C132C7} + Win32Proj + UbiTMNTHax + UbiTMNTHax + 10.0 + + + + DynamicLibrary + true + v143 + MultiByte + + + DynamicLibrary + false + v143 + true + MultiByte + + + + + + + + + + + + + true + .asi + $(ProjectName) + + + false + .asi + $(SolutionDir)$(Configuration)\$(ProjectName)\scripts\ + $(ProjectName) + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + + + Windows + true + + + + + Level4 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + + + Windows + true + true + true + + + + + + + + + false + + + false + + + + + Create + Create + + + + + + + + + + + \ No newline at end of file diff --git a/dllmain.cpp b/dllmain.cpp new file mode 100644 index 0000000..3064020 --- /dev/null +++ b/dllmain.cpp @@ -0,0 +1,213 @@ +#include "stdafx.h" +#include "stdio.h" +#include "includes\injector\injector.hpp" +#include "includes\IniReader.h" + +bool bBorderlessWindowed = false; +bool bEnableWindowResize = false; +bool bEnableConsole = false; +bool bAllowMultipleInstances = false; +bool bRelocateWindow = false; + +int RelocateX = 0; +int RelocateY = 0; + +int DesktopX = 0; +int DesktopY = 0; + +HWND GameHWND = NULL; +tagRECT windowRect; + +void ReadFullscreenSetting(char* IniPath) +{ + // restore the config read + *(int*)((*(int*)0x823F60) + 0xD8) = GetPrivateProfileIntA("CONFIG", "Fullscreen", 1, IniPath); +} + +int InitConfig() +{ + CIniReader inireader(""); + bBorderlessWindowed = inireader.ReadInteger("TMNTHax", "BorderlessWindowed", 0); + // breaks mouse positioning... needs aspect ratio recalculation from 16:9 + bEnableWindowResize = inireader.ReadInteger("TMNTHax", "EnableWindowResize", 0); + bEnableConsole = inireader.ReadInteger("TMNTHax", "EnableConsole", 0); + bAllowMultipleInstances = inireader.ReadInteger("TMNTHax", "AllowMultipleInstances", 0); + bRelocateWindow = inireader.ReadInteger("TMNTHax", "RelocateWindow", 0); + + if (bRelocateWindow) + { + RelocateX = inireader.ReadInteger("WindowLocation", "X", 0); + RelocateY = inireader.ReadInteger("WindowLocation", "Y", 0); + } + + return 0; +} + +int __stdcall cave_sprintf(char* buf, const char* Format, ...) +{ + va_list ArgList; + int Result = 0; + + __crt_va_start(ArgList, Format); + Result = vsprintf(buf, Format, ArgList); + __crt_va_end(ArgList); + + ReadFullscreenSetting(buf); + + return Result; +} + +int __stdcall cave_sprintf2(char* buf, const char* Format, ...) +{ + va_list ArgList; + int Result = 0; + + __crt_va_start(ArgList, Format); + Result = vsprintf(buf, Format, ArgList); + __crt_va_end(ArgList); + + puts(buf); + + return Result; +} + +int cave_vsnprintf(char* buf, const size_t bufcount, const char* Format, va_list ArgList) +{ + int Result = 0; + + Result = vsnprintf(buf, bufcount, Format, ArgList); + vprintf(Format, ArgList); + + return Result; +} + +void __stdcall OutputDebugStringHook(LPCSTR lpOutputString) +{ + printf("%s", lpOutputString); + return OutputDebugStringA(lpOutputString); +} + +int GetDesktopRes(int32_t *DesktopResW, int32_t *DesktopResH) +{ + HMONITOR monitor = MonitorFromWindow(GetDesktopWindow(), MONITOR_DEFAULTTONEAREST); + MONITORINFO info = {}; + info.cbSize = sizeof(MONITORINFO); + GetMonitorInfo(monitor, &info); + *DesktopResW = info.rcMonitor.right - info.rcMonitor.left; + *DesktopResH = info.rcMonitor.bottom - info.rcMonitor.top; + return 0; +} + +BOOL WINAPI AdjustWindowRect_Hook(LPRECT lpRect, DWORD dwStyle, BOOL bMenu) +{ + DWORD newStyle = 0; + + if (!bBorderlessWindowed) + newStyle = WS_CAPTION; + + return AdjustWindowRect(lpRect, newStyle, bMenu); +} + +HWND WINAPI CreateWindowExA_Hook(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam) +{ + int WindowPosX = RelocateX; + int WindowPosY = RelocateY; + + GetDesktopRes(&DesktopX, &DesktopY); + + if (!bRelocateWindow) + { + // fix the window to open at the center of the screen... + WindowPosX = (int)(((float)DesktopX / 2.0f) - ((float)nWidth / 2.0f)); + WindowPosY = (int)(((float)DesktopY / 2.0f) - ((float)nHeight / 2.0f)); + } + + GameHWND = CreateWindowExA(dwExStyle, lpClassName, lpWindowName, 0, WindowPosX, WindowPosY, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam); + LONG lStyle = GetWindowLong(GameHWND, GWL_STYLE); + + if (bBorderlessWindowed) + lStyle &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU); + else + { + lStyle |= (WS_MINIMIZEBOX | WS_SYSMENU); + if (bEnableWindowResize) + lStyle |= (WS_MAXIMIZEBOX | WS_THICKFRAME); + } + + SetWindowLong(GameHWND, GWL_STYLE, lStyle); + SetWindowPos(GameHWND, 0, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED); + + return GameHWND; +} + +BOOL WINAPI GetCursorPos_Hook(LPPOINT lpPoint) +{ + BOOL retval = GetCursorPos(lpPoint); + GetWindowRect(GameHWND, &windowRect); + + // correct the mouse position on moved window... this needs a lot more detail fixing - when window is resized, this breaks + lpPoint->x = lpPoint->x - windowRect.left; + lpPoint->y = lpPoint->y - windowRect.top; + + return retval; +} + +bool RetTrue() +{ + return true; +} + +int Init() +{ + injector::MakeCALL(0x0040B91B, cave_sprintf, true); + + injector::WriteMemory(0x007A6278, (int)&CreateWindowExA_Hook, true); + injector::WriteMemory(0x007A627C, (int)&AdjustWindowRect_Hook, true); + injector::WriteMemory(0x007A62A0, (int)&GetCursorPos_Hook, true); + + // console window stuff... + if (bEnableConsole) + { + injector::MakeCALL(0x004B65BA, cave_sprintf2, true); + injector::MakeCALL(0x004B61B4, cave_sprintf2, true); + injector::MakeCALL(0x004B61F4, cave_sprintf2, true); + injector::MakeCALL(0x004B654F, cave_sprintf2, true); + injector::MakeCALL(0x004181FD, cave_vsnprintf, true); + injector::WriteMemory(0x007A61E0, (int)&OutputDebugStringHook, true); + //injector::MakeCALL(0x0041819C, cave_vsnprintf, true); + } + + // kill Launcher mutex creation so the game can be started directly... + injector::MakeJMP(0x0041507E, 0x41510B, true); + + // kill instance mutex -- doesn't work for some reason... broken by Launcher mutex kill + if (bAllowMultipleInstances) + injector::MakeJMP(0x00414FAF, 0x41510B, true); + + // Disable CD check... + injector::MakeNOP(0x0040607C, 2, true); + + return 0; +} + +BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD reason, LPVOID /*lpReserved*/) +{ + if (reason == DLL_PROCESS_ATTACH) + { + InitConfig(); + + if (bEnableConsole) + { + AttachConsole(ATTACH_PARENT_PROCESS); + AllocConsole(); + freopen("CON", "w", stdout); + freopen("CON", "w", stderr); + freopen("CON", "r", stdin); + } + + Init(); + } + return TRUE; +} + + diff --git a/includes/CPatch.h b/includes/CPatch.h new file mode 100644 index 0000000..05f4ee2 --- /dev/null +++ b/includes/CPatch.h @@ -0,0 +1,145 @@ +#pragma once +#include + +class CPatch +{ +public: + inline static void Patch(void* address, void* data, int size) + { + unsigned long protect[2]; + VirtualProtect(address, size, PAGE_EXECUTE_READWRITE, &protect[0]); + memcpy(address, data, size); + VirtualProtect(address, size, protect[0], &protect[1]); + } + + inline static void Patch2(int address, void* data, int size) + { + unsigned long protect[2]; + VirtualProtect((void *)address, size, PAGE_EXECUTE_READWRITE, &protect[0]); + memcpy((void *)address, data, size); + VirtualProtect((void *)address, size, protect[0], &protect[1]); + } + + inline static void Unprotect(int address, int size) + { + unsigned long protect[2]; + VirtualProtect((void *)address, size, PAGE_EXECUTE_READWRITE, &protect[0]); + } + inline static void Nop(int address, int size) + { + unsigned long protect[2]; + VirtualProtect((void *)address, size, PAGE_EXECUTE_READWRITE, &protect[0]); + memset((void *)address, 0x90, size); + VirtualProtect((void *)address, size, protect[0], &protect[1]); + } + inline static void FillWithZeroes(int address, int size) + { + unsigned long protect[2]; + VirtualProtect((void *)address, size, PAGE_EXECUTE_READWRITE, &protect[0]); + memset((void *)address, 0x00, size); + VirtualProtect((void *)address, size, protect[0], &protect[1]); + } + inline static void RedirectCall(int address, void *func) + { + int temp = 0xE8; + Patch((void *)address, &temp, 1); + temp = (int)func - ((int)address + 5); + Patch((void *)((int)address + 1), &temp, 4); + } + inline static void RedirectJump(int address, void *func) + { + int temp = 0xE9; + Patch((void *)address, &temp, 1); + temp = (int)func - ((int)address + 5); + Patch((void *)((int)address + 1), &temp, 4); + } + inline static void SetChar(int address, char value) + { + Patch((void *)address, &value, 1); + } + inline static void SetUChar(int address, unsigned char value) + { + Patch((void *)address, &value, 1); + } + inline static void SetShort(int address, short value) + { + Patch((void *)address, &value, 2); + } + inline static void SetUShort(int address, unsigned short value) + { + Patch((void *)address, &value, 2); + } + inline static void SetInt(int address, int value) + { + Patch((void *)address, &value, 4); + } + inline static void SetUInt(int address, unsigned int value) + { + Patch((void *)address, &value, 4); + } + inline static void SetUIntWithCheck(int address, unsigned int value, unsigned int expectedValue) + { + if (*(unsigned int *)address == expectedValue) + Patch((void *)address, &value, 4); + } + inline static void SetFloat(int address, float value) + { + Patch((void *)address, &value, 4); + } + inline static void SetDouble(int address, double value) + { + Patch((void *)address, &value, 8); + } + inline static void SetPointer(int address, void *value) + { + Patch((void *)address, &value, 4); + } + + inline static void AdjustPointer(int address, void *value, DWORD offset, DWORD end) + { + int result; + if((DWORD)*(DWORD*)address >= offset && (DWORD)*(DWORD*)address <= end) { + result = (DWORD)value + (DWORD)*(DWORD*)address - (DWORD)offset; + Patch((void *)address, &result, 4); + } else { + address = address + 1; + if((DWORD)*(DWORD*)address >= offset && (DWORD)*(DWORD*)address <= end) { + result = (DWORD)value + (DWORD)*(DWORD*)address - (DWORD)offset; + Patch((void *)address, &result, 4); + } else { + address = address + 1; + if((DWORD)*(DWORD*)address >= offset && (DWORD)*(DWORD*)address <= end) { + result = (DWORD)value + (DWORD)*(DWORD*)address - (DWORD)offset; + Patch((void *)address, &result, 4); + } else { + address = address + 1; + if((DWORD)*(DWORD*)address >= offset && (DWORD)*(DWORD*)address <= end) { + result = (DWORD)value + (DWORD)*(DWORD*)address - (DWORD)offset; + Patch((void *)address, &result, 4); + } else { + address = address + 1; + if((DWORD)*(DWORD*)address >= offset && (DWORD)*(DWORD*)address <= end) { + result = (DWORD)value + (DWORD)*(DWORD*)address - (DWORD)offset; + Patch((void *)address, &result, 4); + } else { + address = address + 1; + if((DWORD)*(DWORD*)address >= offset && (DWORD)*(DWORD*)address <= end) { + result = (DWORD)value + (DWORD)*(DWORD*)address - (DWORD)offset; + Patch((void *)address, &result, 4); + } + } + } + } + } + } + } + + inline static bool FileExists(const TCHAR *fileName) + { + DWORD fileAttr; + fileAttr = GetFileAttributes(fileName); + if (0xFFFFFFFF == fileAttr && GetLastError()==ERROR_FILE_NOT_FOUND) + return false; + return true; + } +}; \ No newline at end of file diff --git a/includes/IniReader.h b/includes/IniReader.h new file mode 100644 index 0000000..1ca2d4d --- /dev/null +++ b/includes/IniReader.h @@ -0,0 +1,103 @@ +#ifndef INIREADER_H +#define INIREADER_H + +#include "stdafx.h" +#include +#include +using namespace std; +#pragma warning(disable:4996) +EXTERN_C IMAGE_DOS_HEADER __ImageBase; + +class CIniReader +{ +public: + CIniReader(char* szFileName) + { + char moduleName[MAX_PATH]; + char dllPath[MAX_PATH]; + char iniName[MAX_PATH]; + char* tempPointer; + + GetModuleFileName((HINSTANCE)&__ImageBase, moduleName, MAX_PATH); + tempPointer = strrchr(moduleName, '.'); + *tempPointer = '\0'; + tempPointer = strrchr(moduleName, '\\'); + strncpy(iniName, tempPointer + 1, 255); + strcat(iniName, ".ini"); + strncpy(dllPath, moduleName, (tempPointer - moduleName + 1)); + dllPath[tempPointer - moduleName + 1] = '\0'; + if (strcmp(szFileName, "") == 0) + { + strcat(dllPath, iniName); + } + else { + strcat(dllPath, szFileName); + } + + memset(m_szFileName, 0x00, 255); + memcpy(m_szFileName, dllPath, strlen(dllPath)); + } + int ReadInteger(char* szSection, char* szKey, int iDefaultValue) + { + int iResult = GetPrivateProfileInt(szSection, szKey, iDefaultValue, m_szFileName); + return iResult; + } + float ReadFloat(char* szSection, char* szKey, float fltDefaultValue) + { + char szResult[255]; + char szDefault[255]; + float fltResult; + _snprintf(szDefault, 255, "%f", fltDefaultValue); + GetPrivateProfileString(szSection, szKey, szDefault, szResult, 255, m_szFileName); + fltResult = (float)atof(szResult); + return fltResult; + } + bool ReadBoolean(char* szSection, char* szKey, bool bolDefaultValue) + { + char szResult[255]; + char szDefault[255]; + bool bolResult; + _snprintf(szDefault, 255, "%s", bolDefaultValue ? "True" : "False"); + GetPrivateProfileString(szSection, szKey, szDefault, szResult, 255, m_szFileName); + bolResult = (strcmp(szResult, "True") == 0 || + strcmp(szResult, "true") == 0) ? true : false; + return bolResult; + } + char* ReadString(char* szSection, char* szKey, const char* szDefaultValue) + { + char* szResult = new char[255]; + memset(szResult, 0x00, 255); + GetPrivateProfileString(szSection, szKey, + szDefaultValue, szResult, 255, m_szFileName); + return szResult; + } + void WriteInteger(char* szSection, char* szKey, int iValue) + { + char szValue[255]; + _snprintf(szValue, 255, "%s%d", " ", iValue); + WritePrivateProfileString(szSection, szKey, szValue, m_szFileName); + } + void WriteFloat(char* szSection, char* szKey, float fltValue) + { + char szValue[255]; + _snprintf(szValue, 255, "%s%f", " ", fltValue); + WritePrivateProfileString(szSection, szKey, szValue, m_szFileName); + } + void WriteBoolean(char* szSection, char* szKey, bool bolValue) + { + char szValue[255]; + _snprintf(szValue, 255, "%s%s", " ", bolValue ? "True" : "False"); + WritePrivateProfileString(szSection, szKey, szValue, m_szFileName); + } + void WriteString(char* szSection, char* szKey, char* szValue) + { + WritePrivateProfileString(szSection, szKey, szValue, m_szFileName); + } + char* GetIniPath() + { + return m_szFileName; + } +private: + char m_szFileName[MAX_PATH]; +}; +#endif//INIREADER_H \ No newline at end of file diff --git a/includes/injector/LICENSE b/includes/injector/LICENSE new file mode 100644 index 0000000..65960fb --- /dev/null +++ b/includes/injector/LICENSE @@ -0,0 +1,21 @@ +Copyright (C) 2012-2014 LINK/2012 + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + diff --git a/includes/injector/assembly.hpp b/includes/injector/assembly.hpp new file mode 100644 index 0000000..9854a34 --- /dev/null +++ b/includes/injector/assembly.hpp @@ -0,0 +1,179 @@ +/* + * Injectors - Useful Assembly Stuff + * + * Copyright (C) 2012-2014 LINK/2012 + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + */ +#pragma once + +// This header is very restrict about compiler and architecture +#ifndef _MSC_VER // MSVC is much more flexible when we're talking about inline assembly +#error Cannot use this header in another compiler other than MSVC +#endif +#ifndef _M_IX86 +#error Supported only in x86 +#endif + +// +#include +#include "injector.hpp" + +namespace injector +{ + struct reg_pack + { + // The ordering is very important, don't change + // The first field is the last to be pushed and first to be poped + + // PUSHFD / POPFD + uint32_t ef; + + // PUSHAD/POPAD -- must be the lastest fields (because of esp) + union + { + uint32_t arr[8]; + struct { uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax; }; + }; + + enum reg_name { + reg_edi, reg_esi, reg_ebp, reg_esp, reg_ebx, reg_edx, reg_ecx, reg_eax + }; + + enum ef_flag { + carry_flag = 0, parity_flag = 2, adjust_flag = 4, zero_flag = 6, sign_flag = 7, + direction_flag = 10, overflow_flag = 11 + }; + + uint32_t& operator[](size_t i) + { return this->arr[i]; } + const uint32_t& operator[](size_t i) const + { return this->arr[i]; } + + template // bit starts from 0, use ef_flag enum + bool flag() + { + return (this->ef & (1 << bit)) != 0; + } + + bool jnb() + { + return flag() == false; + } + }; + + // Lowest level stuff (actual assembly) goes on the following namespace + // PRIVATE! Skip this, not interesting for you. + namespace injector_asm + { + // Wrapper functor, so the assembly can use some templating + template + struct wrapper + { + static void call(reg_pack* regs) + { + T fun; fun(*regs); + } + }; + + // Constructs a reg_pack and calls the wrapper functor + template // where W is of type wrapper + inline void __declspec(naked) make_reg_pack_and_call() + { + _asm + { + // Construct the reg_pack structure on the stack + pushad // Pushes general purposes registers to reg_pack + add [esp+12], 4 // Add 4 to reg_pack::esp 'cuz of our return pointer, let it be as before this func is called + pushfd // Pushes EFLAGS to reg_pack + + // Call wrapper sending reg_pack as parameter + push esp + call W::call + add esp, 4 + + // Destructs the reg_pack from the stack + sub [esp+12+4], 4 // Fix reg_pack::esp before popping it (doesn't make a difference though) (+4 because eflags) + popfd // Warning: Do not use any instruction that changes EFLAGS after this (-> sub affects EF!! <-) + popad + + // Back to normal flow + ret + } + } + }; + + + /* + * MakeInline + * Makes inline assembly (but not assembly, an actual functor of type FuncT) at address + */ + template + void MakeInline(memory_pointer_tr at) + { + typedef injector_asm::wrapper functor; + if(false) functor::call(nullptr); // To instantiate the template, if not done _asm will fail + MakeCALL(at, injector_asm::make_reg_pack_and_call); + } + + /* + * MakeInline + * Same as above, but it NOPs everything between at and end (exclusive), then performs MakeInline + */ + template + void MakeInline(memory_pointer_tr at, memory_pointer_tr end) + { + MakeRangedNOP(at, end); + MakeInline(at); + } + + /* + * MakeInline + * Same as above, but (at,end) are template parameters. + * On this case the functor can be passed as argument since there will be one func instance for each at,end not just for each FuncT + */ + template + void MakeInline(FuncT func) + { + static std::unique_ptr static_func; + static_func.reset(new FuncT(std::move(func))); + + // Encapsulates the call to static_func + struct Caps + { + void operator()(reg_pack& regs) + { (*static_func)(regs); } + }; + + // Does the actual MakeInline + return MakeInline(lazy_pointer::get(), lazy_pointer::get()); + } + + /* + * MakeInline + * Same as above, but (end) is calculated by the length of a call instruction + */ + template + void MakeInline(FuncT func) + { + return MakeInline(func); + } +}; diff --git a/includes/injector/calling.hpp b/includes/injector/calling.hpp new file mode 100644 index 0000000..ebf3bdf --- /dev/null +++ b/includes/injector/calling.hpp @@ -0,0 +1,126 @@ +/* + * Injectors - Function Calls Using Variadic Templates + * + * Copyright (C) 2014 LINK/2012 + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + */ +#pragma once +#include "injector.hpp" +#include + +#if __cplusplus >= 201103L || _MSC_VER >= 1800 // MSVC 2013 +#else +#error "This feature is not supported on this compiler" +#endif + +namespace injector +{ + template + struct cstd; + + template + struct cstd + { + // Call function at @p returning @Ret with args @Args + static Ret call(memory_pointer_tr p, Args... a) + { + auto fn = (Ret(*)(Args...)) p.get(); + return fn(std::forward(a)...); + } + + template // Uses lazy pointer + static Ret call(Args... a) + { + return call(lazy_pointer::get(), std::forward(a)...); + } + }; + + template + struct stdcall; + + template + struct stdcall + { + // Call function at @p returning @Ret with args @Args + static Ret call(memory_pointer_tr p, Args... a) + { + auto fn = (Ret(__stdcall *)(Args...)) p.get(); + return fn(std::forward(a)...); + } + + template // Uses lazy pointer + static Ret call(Args... a) + { + return call(lazy_pointer::get(), std::forward(a)...); + } + }; + + template + struct fastcall; + + template + struct fastcall + { + // Call function at @p returning @Ret with args @Args + static Ret call(memory_pointer_tr p, Args... a) + { + auto fn = (Ret(__fastcall *)(Args...)) p.get();; + return fn(std::forward(a)...); + } + + template // Uses lazy pointer + static Ret call(Args... a) + { + return call(lazy_pointer::get(), std::forward(a)...); + } + }; + + template + struct thiscall; + + template + struct thiscall + { + // Call function at @p returning @Ret with args @Args + static Ret call(memory_pointer_tr p, Args... a) + { + auto fn = (Ret(__thiscall *)(Args...)) p.get(); + return fn(std::forward(a)...); + } + + // Call function at the index @i from the vtable of the object @a[0] + template + static Ret vtbl(Args... a) + { + auto obj = raw_ptr(std::get<0>(std::forward_as_tuple(a...))); + auto p = raw_ptr( (*obj.template get()) [i] ); + return call(p, std::forward(a)...); + } + + template // Uses lazy pointer + static Ret call(Args... a) + { + return call(lazy_pointer::get(), std::forward(a)...); + } + }; +} + diff --git a/includes/injector/gvm/gvm.hpp b/includes/injector/gvm/gvm.hpp new file mode 100644 index 0000000..a957929 --- /dev/null +++ b/includes/injector/gvm/gvm.hpp @@ -0,0 +1,232 @@ +/* + * Injectors - Base Header + * + * Copyright (C) 2012-2014 LINK/2012 + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + */ +#pragma once +#include +#include +#include + +namespace injector +{ + +#if 1 // GVM and Address Translator, Not very interesting for the users, so skip reading those... + +/* + * game_version_manager + * Detects the game, the game version and the game region + * This assumes the executable is decrypted, so, Silent's ASI Loader is recommended. + */ +#ifndef INJECTOR_OWN_GVM +#ifndef INJECTOR_GVM_DUMMY +class game_version_manager +{ + public: + // Set this if you would like that MessagesBox contain PluginName as caption + const char* PluginName; + + private: + char game, region, major, minor, majorRevision, minorRevision, cracker, steam; + + public: + game_version_manager() + { + #ifdef INJECTOR_GVM_PLUGIN_NAME + PluginName = INJECTOR_GVM_PLUGIN_NAME; + #else + PluginName = "LODLights.asi"; + #endif + + this->Clear(); + } + + + // Clear any information about game version + void Clear() + { + game = region = major = minor = cracker = steam = 0; + } + + // Checks if I don't know the game we are attached to + bool IsUnknown() { return game == 0; } + // Checks if this is the steam version + bool IsSteam() { return steam != 0; } + // Gets the game we are attached to (0, '3', 'V', 'S', 'I', 'E') + char GetGame() { return game; } + // Gets the region from the game we are attached to (0, 'U', 'E'); + char GetRegion() { return region; } + // Get major and minor version of the game (e.g. [major = 1, minor = 0] = 1.0) + int GetMajorVersion() { return major; } + int GetMinorVersion() { return minor; } + int GetMajorRevisionVersion() { return majorRevision; } + int GetMinorRevisionVersion() { return minorRevision; } + + bool IsHoodlum() { return cracker == 'H'; } + + // Region conditions + bool IsUS() { return region == 'U'; } + bool IsEU() { return region == 'E'; } + + // Game Conditions + bool IsIII() { return game == '3'; } + bool IsVC () { return game == 'V'; } + bool IsSA () { return game == 'S'; } + bool IsIV () { return game == 'I'; } + bool IsEFLC(){ return game == 'E'; } + + // Detects game, region and version; returns false if could not detect it + bool Detect(); + + // Gets the game version as text, the buffer must contain at least 32 bytes of space. + char* GetVersionText(char* buffer) + { + if(this->IsUnknown()) + { + strcpy(buffer, "UNKNOWN GAME"); + return buffer; + } + + const char* g = this->IsIII() ? "III" : this->IsVC() ? "VC" : this->IsSA() ? "SA" : this->IsIV() ? "IV" : this->IsEFLC() ? "EFLC" : "UNK"; + const char* r = this->IsUS()? "US" : this->IsEU()? "EURO" : "UNK_REGION"; + const char* s = this->IsSteam()? "Steam" : ""; + if (this->IsIII() || this->IsVC() || this->IsSA()) + sprintf(buffer, "GTA %s %d.%d %s%s", g, major, minor, r, s); + else + sprintf(buffer, "GTA %s %d.%d.%d.%d %s%s", g, major, minor, majorRevision, minorRevision, r, s); + return buffer; + } + + + public: + // Raises a error saying that you could not detect the game version + void RaiseCouldNotDetect() + { + MessageBoxA(0, + "Could not detect the game version\nContact the mod creator!", + PluginName, MB_ICONERROR + ); + } + + // Raises a error saying that the exe version is incompatible (and output the exe name) + void RaiseIncompatibleVersion() + { + char buf[128], v[32]; + sprintf(buf, + "An incompatible exe version has been detected! (%s)\nContact the mod creator!", + GetVersionText(v) + ); + MessageBoxA(0, buf, PluginName, MB_ICONERROR); + } +}; +#else // INJECTOR_GVM_DUMMY +class game_version_manager +{ + public: + bool Detect() { return true; } +}; +#endif // INJECTOR_GVM_DUMMY +#endif // INJECTOR_OWN_GVM + + +/* + * address_manager + * Address translator from 1.0 executables to other executables offsets + * Inherits from game_version_manager ;) + */ +class address_manager : public game_version_manager +{ + private: + address_manager() + { + this->Detect(); + } + + // You could implement your translator for the address your plugin uses + // If not implemented, the translator won't translate anything, just return the samething as before + #ifdef INJECTOR_GVM_HAS_TRANSLATOR + void* translator(void* p); + #else + void* translator(void* p) { return p; } + #endif + + public: + // Translates address p to the running executable pointer + void* translate(void* p) + { + return translator(p); + } + + + public: + // Address manager singleton + static address_manager& singleton() + { + static address_manager m; + return m; + } + + // Static version of translate() + static void* translate_address(void* p) + { + return singleton().translate(p); + } + + // + static void set_name(const char* modname) + { + singleton().PluginName = modname; + } + + public: + // Functors for memory translation: + + // Translates aslr translator + struct fn_mem_translator_aslr + { + void* operator()(void* p) const + { + static uintptr_t module = (uintptr_t)GetModuleHandle(NULL); + return (void*)((uintptr_t)(p)-(0x400000 - module)); + } + }; + + // Translates nothing translator + struct fn_mem_translator_nop + { + void* operator()(void* p) const + { return p; } + }; + + // Real translator + struct fn_mem_translator + { + void* operator()(void* p) const + { return translate_address(p); } + }; +}; + +#endif // #if 1 + + +} \ No newline at end of file diff --git a/includes/injector/gvm/translator.hpp b/includes/injector/gvm/translator.hpp new file mode 100644 index 0000000..c6ac430 --- /dev/null +++ b/includes/injector/gvm/translator.hpp @@ -0,0 +1,203 @@ +/* + * Injectors - Address Translation Management + * + * Copyright (C) 2014 LINK/2012 + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + */ +#pragma once + +#if !defined(INJECTOR_GVM_HAS_TRANSLATOR) +#error Missing INJECTOR_GVM_HAS_TRANSLATOR on compiler definitions +#endif + +/* + * This is a quick solution for address translations if you're too lazy to implement a proper address_manager::translator by yourself + * So, just call address_translator_manager::singleton().translate(p) from your address_manager::translator and that's it. + * It'll translate addresses based on 'address_translator' objects, when one gets constructed it turns into a possible translator. + * At the constructor of your derived 'address_translator' make the map object to have [addr_to_translate] = translated_addr; + * There's also the virtual method 'fallback' that will get called when the translation wasn't possible, you can do some fallback stuff here + * (such as return the pointer as is or output a error message) + */ + +#include "../injector.hpp" +#include +#include +#include + +namespace injector +{ + /* + * address_translator + * Base for an address translator + */ + class address_translator + { + private: + bool enabled; + void add(); + void remove(); + + protected: + friend class address_translator_manager; + std::map map; + + public: + address_translator() : enabled(true) + { + // Must have bounds filled with min ptr and max ptr to have search working properly + map.insert(std::make_pair(raw_ptr(0x00000000u), raw_ptr(0x00000000u))); + map.insert(std::make_pair(raw_ptr(0xffffffffu), raw_ptr(0xffffffffu))); + add(); + } + + ~address_translator() + { + remove(); + } + + virtual void* fallback(void*) const + { + return nullptr; + } + + + // Enables or disables this translator + void enable(bool enable_it) + { + if(enable_it) this->enable(); + else this->disable(); + } + + // Enables this translator + void enable() + { + this->enabled = true; + } + + // Disables this translator + void disable() + { + this->enabled = false; + } + + // Checks if this translator is enabled + bool is_enabled() const + { + return enabled; + } + }; + + /* + * address_translator_manager + * Manages the address_translator objects + */ + class address_translator_manager + { + protected: + friend class address_manager; + friend class address_translator; + + std::list translators; + + void add(const address_translator& t) + { + translators.push_front(&t); + } + + void remove(const address_translator& t) + { + translators.remove(&t); + } + + public: + // Translates the address p + void* translator(void* p); + + // Singleton object + static address_translator_manager& singleton() + { + static address_translator_manager mgr; + return mgr; + } + }; + + + + inline void* address_translator_manager::translator(void* p_) + { + static const size_t max_ptr_dist = 7; + + // Tries to find an address in a translator map + auto try_map = [](const std::map& map, memory_pointer_raw p) -> memory_pointer_raw + { + memory_pointer_raw result = nullptr; + + // Find first element in the map that is greater than or equal to p + auto it = map.lower_bound(p); + if(it != map.end()) + { + // If it's not exactly the address, get back one position on the table + if(it->first != p) --it; + + auto diff = (p - it->first).as_int(); // What's the difference between p and that address? + if(diff <= max_ptr_dist) // Could we live with this difference in hands? + result = it->second + raw_ptr(diff); // Yes, we can! + } + + return result; + }; + + + // + memory_pointer_raw result = nullptr; + + // Try to find translation for this pointer + auto& mgr = address_translator_manager::singleton().translators; + for(auto it = mgr.begin(); result == nullptr && it != mgr.end(); ++it) + { + auto& t = **it; + if(t.is_enabled()) result = try_map(t.map, p_); + } + + // If we couldn't translate the address, notify and try to fallback + if(result.is_null()) + { + for(auto it = mgr.begin(); result == nullptr && it != mgr.end(); ++it) + { + auto& t = **it; + if(t.is_enabled()) result = t.fallback(p_); + } + } + + return result.get(); + } + + inline void address_translator::add() + { + address_translator_manager::singleton().add(*this); + } + + inline void address_translator::remove() + { + address_translator_manager::singleton().remove(*this); + } +} diff --git a/includes/injector/hooking.hpp b/includes/injector/hooking.hpp new file mode 100644 index 0000000..ec7d797 --- /dev/null +++ b/includes/injector/hooking.hpp @@ -0,0 +1,687 @@ +/* + * Injectors - Classes for making your hooking life easy + * + * Copyright (C) 2013-2014 LINK/2012 + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + */ +#pragma once +#include "injector.hpp" +#include +#include +#include // for std::shared_ptr +#include + +namespace injector +{ + /* + * scoped_base + * Base for any scoped hooking type + * !!!! NOTICE !!!! --> Any derived which implements/reimplements restore() should implement a destructor calling it + */ + class scoped_base + { + public: + virtual ~scoped_base() {} + virtual void restore() = 0; + }; + + /* + * scoped_basic + * Base for scoped types which will need a buffer to save/restore stuff + */ + template // TODO specialize bufsize=0 to be dynamic + class scoped_basic : public scoped_base + { + private: + uint8_t buf[bufsize];// Saved content + memory_pointer_raw addr; // Data saved from this address + size_t size; // Size saved + bool saved; // Something saved? + bool vp; // Virtual protect? + + public: + + static const bool is_dynamic = false; + + // Restore the previosly saved data + // Problems may arise if someone else hooked the same place using the same method + virtual void restore() + { + #ifndef INJECTOR_SCOPED_NOSAVE_NORESTORE + if(this->saved) + { + WriteMemoryRaw(this->addr, this->buf, this->size, this->vp); + this->saved = false; + } + #endif + } + + // Save buffer at @addr with @size and virtual protect @vp + virtual void save(memory_pointer_tr addr, size_t size, bool vp) + { + #ifndef INJECTOR_SCOPED_NOSAVE_NORESTORE + assert(size <= bufsize); // Debug Safeness + this->restore(); // Restore anything we have saved + this->saved = true; // Mark that we have data save + this->addr = addr.get(); // Save address + this->size = size; // Save size + this->vp = vp; // Save virtual protect + ReadMemoryRaw(addr, buf, size, vp); // Save buffer + #endif + } + + public: + // Constructor, initialises + scoped_basic() : saved(false) + {} + + ~scoped_basic() + { + this->restore(); + } + + // No copy construction, we can't do this! Sure we can move construct :) + scoped_basic(const scoped_basic&) = delete; + scoped_basic(scoped_basic&& rhs) + { + *this = std::move(rhs); + } + + scoped_basic& operator=(const scoped_basic& rhs) = delete; + scoped_basic& operator=(scoped_basic&& rhs) + { + if(this->saved = rhs.saved) + { + assert(bufsize >= rhs.size); + + this->addr = rhs.addr; + this->size = rhs.size; + this->vp = rhs.vp; + memcpy(buf, rhs.buf, rhs.size); + + rhs.saved = false; + } + return *this; + } + }; + + /* + * RAII wrapper for memory writes + * Can save only basic and POD types + */ + template + class scoped_write : public scoped_basic + { + public: + // Save buffer at @addr with @size and virtual protect @vp and then overwrite it with @value + void write(memory_pointer_tr addr, void* value, size_t size, bool vp) + { + this->save(addr, size, vp); + return WriteMemoryRaw(addr, value, size, vp); + } + + // Save buffer at @addr with size sizeof(@value) and virtual protect @vp and then overwrite it with @value + template + void write(memory_pointer_tr addr, T value, bool vp = false) + { + this->save(addr, sizeof(T), vp); + return WriteMemory(addr, value, vp); + } + + // Constructors, move constructors, assigment operators........ + scoped_write() = default; + scoped_write(const scoped_write&) = delete; + scoped_write(scoped_write&& rhs) : scoped_basic(std::move(rhs)) {} + scoped_write& operator=(const scoped_write& rhs) = delete; + scoped_write& operator=(scoped_write&& rhs) + { scoped_basic::operator=(std::move(rhs)); return *this; } + }; + + /* + * RAII wrapper for filling + */ + template + class scoped_fill : public scoped_basic + { + public: + // Fills memory at @addr with value @value and size @size and virtual protect @vp + void fill(memory_pointer_tr addr, uint8_t value, size_t size, bool vp) + { + this->save(addr, size, vp); + return MemoryFill(addr, value, size, vp); + } + + // Constructors, move constructors, assigment operators........ + scoped_fill() = default; + scoped_fill(const scoped_fill&) = delete; + scoped_fill(scoped_fill&& rhs) : scoped_basic(std::move(rhs)) {} + scoped_fill& operator=(const scoped_fill& rhs) = delete; + scoped_fill& operator=(scoped_fill&& rhs) + { scoped_basic::operator=(std::move(rhs)); return *this; } + + scoped_fill(memory_pointer_tr addr, uint8_t value, size_t size, bool vp) + { fill(addr, value, vp); } + }; + + /* + * RAII wrapper for nopping + */ + template + class scoped_nop : public scoped_basic + { + public: + // Makes NOP at @addr with value @value and size @size and virtual protect @vp + void make_nop(memory_pointer_tr addr, size_t size = 1, bool vp = true) + { + this->save(addr, size, vp); + return MakeNOP(addr, size, vp); + } + + // Constructors, move constructors, assigment operators........ + scoped_nop() = default; + scoped_nop(const scoped_nop&) = delete; + scoped_nop(scoped_nop&& rhs) : scoped_basic(std::move(rhs)) {} + scoped_nop& operator=(const scoped_nop& rhs) = delete; + scoped_nop& operator=(scoped_nop&& rhs) + { scoped_basic::operator=(std::move(rhs)); return *this; } + + scoped_nop(memory_pointer_tr addr, size_t size = 1, bool vp = true) + { make_nop(addr, size, vp); } + }; + + /* + * RAII wrapper for MakeJMP + */ + class scoped_jmp : public scoped_basic<5> + { + public: + // Makes NOP at @addr with value @value and size @size and virtual protect @vp + memory_pointer_raw make_jmp(memory_pointer_tr at, memory_pointer_raw dest, bool vp = true) + { + this->save(at, 5, vp); + return MakeJMP(at, dest, vp); + } + + // Constructors, move constructors, assigment operators........ + scoped_jmp() = default; + scoped_jmp(const scoped_jmp&) = delete; + scoped_jmp(scoped_jmp&& rhs) : scoped_basic<5>(std::move(rhs)) {} + scoped_jmp& operator=(const scoped_jmp& rhs) = delete; + scoped_jmp& operator=(scoped_jmp&& rhs) + { scoped_basic<5>::operator=(std::move(rhs)); return *this; } + + scoped_jmp(memory_pointer_tr at, memory_pointer_raw dest, bool vp = true) + { make_jmp(at, dest, vp); } + }; + + /* + * RAII wrapper for MakeCALL + */ + class scoped_call : public scoped_basic<5> + { + public: + // Makes NOP at @addr with value @value and size @size and virtual protect @vp + memory_pointer_raw make_call(memory_pointer_tr at, memory_pointer_raw dest, bool vp = true) + { + this->save(at, 5, vp); + return MakeCALL(at, dest, vp); + } + + // Constructors, move constructors, assigment operators........ + scoped_call() = default; + scoped_call(const scoped_call&) = delete; + scoped_call(scoped_call&& rhs) : scoped_basic<5>(std::move(rhs)) {} + scoped_call& operator=(const scoped_call& rhs) = delete; + scoped_call& operator=(scoped_call&& rhs) + { scoped_basic<5>::operator=(std::move(rhs)); return *this; } + + scoped_call(memory_pointer_tr at, memory_pointer_raw dest, bool vp = true) + { make_call(at, dest, vp); } + }; + + +#if __cplusplus >= 201103L || _MSC_VER >= 1800 // C++11 or MSVC 2013 required for variadic templates + + /* + * function_hooker_manager + * Manages many function_hookers that points to the same address + * The need for this function arises because otherwise we would only be able to allow one hook per address using function_hookers + * This manager takes care of the amount of hooks placed in a particular address, calls the hooks and unhooks when necessary. + */ + template + class function_hooker_manager : protected scoped_call + { + private: + using func_type_raw = typename ToManage::func_type_raw; + using func_type = typename ToManage::func_type; + using functor_type = typename ToManage::functor_type; + using assoc_type = std::list>; + + // Only construction is allowed... by myself ofcourse... + function_hooker_manager() = default; + function_hooker_manager(const function_hooker_manager&) = delete; + function_hooker_manager(function_hooker_manager&&) = delete; + + // + func_type_raw original; // Pointer to the original function we've replaced + assoc_type assoc; // Association between owners of a hook and the hook (map) + bool has_hooked = false; // Is the hook already in place? + + // Find assoc iterator for the content owned by 'owned' + typename assoc_type::iterator find_assoc(const ToManage& owner) + { + for(auto it = assoc.begin(); it != assoc.end(); ++it) + if(it->first == &owner) return it; + return assoc.end(); + } + + // Adds a new item to the association map (or override if already in the map) + void add(const ToManage& hooker, functor_type functor) + { + auto it = find_assoc(hooker); + if(it != assoc.end()) + it->second = std::move(functor); + else + assoc.emplace_back(&hooker, std::move(functor)); + } + + public: + // Forwards the call to all the installed hooks + static Ret call_hooks(Args&... args) + { + auto& manager = *instance(); + + if(manager.assoc.size() == 0) // This may be uncommon but may happen (?), no hook installed + return manager.original(args...); + + // Functor for the original call + func_type original = [&manager](Args... args) -> Ret { + return manager.original(args...); + }; + + if(manager.assoc.size() == 1) + { + // We have only one hook, just use it directly no need to go further in complexity + auto& functor = manager.assoc.begin()->second; + return functor(std::move(original), args...); + } + else + { + // Build a serie of functors which captures the previous functor sending it to the next functor, + // that's what would happen if the hooks took place independent of the template staticness (AAAAAAA) + func_type next = std::move(original); + for(auto it = manager.assoc.begin(); it != manager.assoc.end(); ++it) + { + auto& functor = it->second; + next = [functor, next](Args... args) -> Ret + { + return functor(next, args...); + }; + } + return next(args...); + } + } + + public: + + // Installs a hook associated with the function_hooker 'hooker' which would call the specified 'functor' + // We need an auxiliar function pointer 'ptr' (to abstract calling conventions) which should forward itself to ^call_hooks + void make_call(const ToManage& hooker, functor_type functor, memory_pointer_raw ptr) + { + this->add(hooker, std::move(functor)); + + // Make sure we only hook this address for the manager once + if(!this->has_hooked) + { + // (the following cast is needed for __thiscall functions) + this->original = (func_type_raw) (void*) scoped_call::make_call(hooker.addr, ptr).get(); + this->has_hooked = true; + } + } + + // Restores the state of the call we've replaced in the game code + // All installed hooks gets uninstalled after this + void restore() + { + if(this->has_hooked) + { + this->has_hooked = false; + this->assoc.clear(); + return scoped_call::restore(); + } + } + + // Replaces the hook associated with 'from' to be associated with 'to' + // After this call the 'from' object has no association in this manager + void replace(const ToManage& from, const ToManage& to) + { + auto it = find_assoc(from); + if(it != assoc.end()) + { + auto functor = std::move(it->second); + assoc.erase(it); + this->add(to, std::move(functor)); + } + } + + // Removes the hook associated with the specified 'hooker' + // If the number of hooks reaches zero after the remotion, a restore will take place + void remove(const ToManage& hooker) + { + auto it = find_assoc(hooker); + if(it != assoc.end()) + { + assoc.erase(it); + if(assoc.size() == 0) this->restore(); + } + } + + // The instance of this specific manager + // This work as a shared pointer to avoid the static destruction of the manager even when a static function_hooker + // still wants to use it. + static std::shared_ptr instance() + { + static auto fm_ptr = std::shared_ptr(new function_hooker_manager()); + return fm_ptr; + } + + }; + + + /* + * function_hooker_base + * Base for any function_hooker, this class manages the relationship with the function hooker manager + */ + template + class function_hooker_base : public scoped_base + { + public: + static const uintptr_t addr = addr1; + + using func_type_raw = FuncType; + using func_type = std::function; + using functor_type = std::function; + using manager_type = function_hooker_manager; + + public: + // Constructors, move constructors, assigment operators........ + function_hooker_base(const function_hooker_base&) = delete; + function_hooker_base& operator=(const function_hooker_base& rhs) = delete; + + function_hooker_base() : manager(manager_type::instance()) + {} + + // + virtual ~function_hooker_base() + { + this->restore(); + } + + // The move constructor should do a replace in the manager + function_hooker_base(function_hooker_base&& rhs) + : scoped_base(std::move(rhs)), has_call(rhs.has_call), + manager(rhs.manager) // (don't move the manager!, every function_hooker should own one) + { + manager->replace(rhs, *this); + } + + // The move assignment operator should also do a replace in the manager + function_hooker_base& operator=(function_hooker_base&& rhs) + { + scoped_base::operator=(std::move(rhs)); + manager->replace(rhs, *this); + this->has_call = rhs.has_call; + this->manager = rhs.manager; // (don't move the manager! every function_hooker should own one) + return *this; + } + + // Deriveds should implement a proper make_call (yeah it's virtual so derived-deriveds can do some fest) + virtual void make_call(functor_type functor) = 0; + + // Restores the state of the call we've replaced in the game code + virtual void restore() + { + this->has_call = false; + manager->remove(*this); + } + + // Checkers whether a hook is installed + bool has_hooked() + { + return this->has_call; + } + + private: + bool has_call = false; // Has a hook installed? + std::shared_ptr manager; // **EVERY** function_hooker should have a ownership over it's manager_type + // this prevents the static destruction of the manager_type while it may be still needed. + + protected: // Forwarders to the function hooker manager + + void make_call(functor_type functor, memory_pointer_raw ptr) + { + this->has_call = true; + manager->make_call(*this, std::move(functor), ptr); + } + + static Ret call_hooks(Args&... a) + { + return manager_type::call_hooks(a...); + } + }; + + + + + /* + * function_hooker + * For standard conventions (usually __cdecl) + */ + template + struct function_hooker; + + template + class function_hooker + : public function_hooker_base + { + private: + using base = function_hooker_base; + + // The hook caller + static Ret call(Args... a) + { + return base::call_hooks(a...); + } + + public: + // Constructors, move constructors, assigment operators........ + function_hooker() = default; + function_hooker(const function_hooker&) = delete; + function_hooker(function_hooker&& rhs) : base(std::move(rhs)) {} + function_hooker& operator=(const function_hooker& rhs) = delete; + function_hooker& operator=(function_hooker&& rhs) + { base::operator=(std::move(rhs)); return *this; } + + // Makes the hook + void make_call(typename base::functor_type functor) + { + return base::make_call(std::move(functor), raw_ptr(call)); + } + }; + + + /* + * function_hooker_stdcall + * For stdcall conventions (__stdcall) + */ + template + struct function_hooker_stdcall; + + template + struct function_hooker_stdcall + : public function_hooker_base + { + private: + using base = function_hooker_base; + + // The hook caller + static Ret __stdcall call(Args... a) + { + return base::call_hooks(a...); + } + + public: + // Constructors, move constructors, assigment operators........ + function_hooker_stdcall() = default; + function_hooker_stdcall(const function_hooker_stdcall&) = delete; + function_hooker_stdcall(function_hooker_stdcall&& rhs) : base(std::move(rhs)) {} + function_hooker_stdcall& operator=(const function_hooker_stdcall& rhs) = delete; + function_hooker_stdcall& operator=(function_hooker_stdcall&& rhs) + { base::operator=(std::move(rhs)); return *this; } + + // Makes the hook + void make_call(typename base::functor_type functor) + { + return base::make_call(std::move(functor), raw_ptr(call)); + } + }; + + + /* + * function_hooker_fastcall + * For fastcall conventions (__fastcall) + */ + template + struct function_hooker_fastcall; + + template + struct function_hooker_fastcall + : public function_hooker_base + { + private: + using base = function_hooker_base; + + // The hook caller + static Ret __fastcall call(Args... a) + { + return base::call_hooks(a...); + } + + public: + // Constructors, move constructors, assigment operators........ + function_hooker_fastcall() = default; + function_hooker_fastcall(const function_hooker_fastcall&) = delete; + function_hooker_fastcall(function_hooker_fastcall&& rhs) : base(std::move(rhs)) {} + function_hooker_fastcall& operator=(const function_hooker_fastcall& rhs) = delete; + function_hooker_fastcall& operator=(function_hooker_fastcall&& rhs) + { base::operator=(std::move(rhs)); return *this; } + + // Makes the hook + void make_call(typename base::functor_type functor) + { + return base::make_call(std::move(functor), raw_ptr(call)); + } + }; + + + /* + * function_hooker_thiscall + * For thiscall conventions (__thiscall, class methods) + */ + template + struct function_hooker_thiscall; + + template + struct function_hooker_thiscall + : public function_hooker_base + { + private: + using base = function_hooker_base; + + // The hook caller + static Ret __thiscall call(Args... a) + { + return base::call_hooks(a...); + } + + public: + // Constructors, move constructors, assigment operators........ + function_hooker_thiscall() = default; + function_hooker_thiscall(const function_hooker_thiscall&) = delete; + function_hooker_thiscall(function_hooker_thiscall&& rhs) : base(std::move(rhs)) {} + function_hooker_thiscall& operator=(const function_hooker_thiscall& rhs) = delete; + function_hooker_thiscall& operator=(function_hooker_thiscall&& rhs) + { base::operator=(std::move(rhs)); return *this; } + + // Makes the hook + void make_call(typename base::functor_type functor) + { + return base::make_call(std::move(functor), raw_ptr(call)); + } + }; + + + + /******************* HELPERS ******************/ + + /* + * Adds a hook to be alive for the entire program lifetime + * That means the hook received will be alive until the program dies. + * Note: Parameter must be a rvalue + */ + template inline + T& add_static_hook(T&& hooker) + { + static std::list a; + return *a.emplace(a.end(), std::move(hooker)); + } + + /* + * Makes a hook which is alive until it gets out of scope + * 'T' must be any function_hooker object + */ + template inline + T make_function_hook(F functor) + { + T a; + a.make_call(std::move(functor)); + return a; + } + + /* + * Makes a hook which is alive for the entire lifetime of this program + * 'T' must be any function_hooker object + */ + template inline + T& make_static_hook(F functor) + { + return add_static_hook(make_function_hook(std::move(functor))); + } + + + // TODO when we have access to C++14 add a make_function_hook, make_stdcall_function_hook, and so on + // the problem behind implement it with C++11 is that lambdas cannot be generic and the first param of a hook is a functor pointing + // to the previous call pointer + +#endif + +} diff --git a/includes/injector/injector.hpp b/includes/injector/injector.hpp new file mode 100644 index 0000000..ffa3a02 --- /dev/null +++ b/includes/injector/injector.hpp @@ -0,0 +1,750 @@ +/* + * Injectors - Base Header + * + * Copyright (C) 2012-2014 LINK/2012 + * Copyright (C) 2014 Deji + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + */ +#pragma once +#define INJECTOR_HAS_INJECTOR_HPP +#include +#include +#include +#include "gvm/gvm.hpp" +/* + The following macros (#define) are relevant on this header: + + INJECTOR_GVM_HAS_TRANSLATOR + If defined, the user should provide their own address_manager::translator function. + That function is responssible for translating a void pointer (that mayn't be an actual pointer) into an actual address. + The meaning of that void pointer will be made by YOU when you send it to the functions that receive pointers on this library. + The default translator does nothing but returns that void pointer as the address. + + INJECTOR_GVM_OWN_DETECT + If defined, the user should provide it's own game detection function thought game_version_manager::Detect + By default it provide an good detection for the Grand Theft Auto series. + + INJECTOR_GVM_PLUGIN_NAME + If this is defined, it will be used as the plugin name used at error messages. + By default it will use ""Unknown Plugin Name" + + INJECTOR_GVM_DUMMY + If defined, the game_version_manager will be a dummy object + By default it provides a nice gvm for Grand Theft Auto series + + INJECTOR_OWN_GVM + If defined, the game_version_manager should be implemented by the user before including this library. + By default it provides a nice gvm for Grand Theft Auto series +*/ +#include "gvm/gvm.hpp" + + + +namespace injector +{ + + +/* + * auto_pointer + * Casts itself to another pointer type in the lhs + */ +union auto_pointer +{ + protected: + friend union memory_pointer_tr; + template friend union basic_memory_pointer; + + void* p; + uintptr_t a; + + public: + auto_pointer() : p(0) {} + auto_pointer(const auto_pointer& x) : p(x.p) {} + explicit auto_pointer(void* x) : p(x) {} + explicit auto_pointer(uint32_t x) : a(x) {} + + bool is_null() const { return this->p != nullptr; } + + #if __cplusplus >= 201103L || _MSC_VER >= 1800 + explicit operator bool() const { return is_null(); } + #endif + + auto_pointer get() const { return *this; } + template T* get() const { return (T*) this->p; } + template T* get_raw() const { return (T*) this->p; } + + template + operator T*() const { return reinterpret_cast(p); } +}; + +/* + * basic_memory_pointer + * A memory pointer class that is capable of many operations, including address translation + * MemTranslator is the translator functor + */ +template +union basic_memory_pointer +{ + protected: + void* p; + uintptr_t a; + + // Translates address p to the running executable pointer + static auto_pointer memory_translate(void* p) + { + return auto_pointer(MemTranslator()(p)); + } + + public: + basic_memory_pointer() : p(nullptr) {} + basic_memory_pointer(std::nullptr_t) : p(nullptr) {} + basic_memory_pointer(uintptr_t x) : a(x) {} + basic_memory_pointer(const auto_pointer& x) : p(x.p) {} + basic_memory_pointer(const basic_memory_pointer& rhs) : p(rhs.p) {} + + template + basic_memory_pointer(T* x) : p((void*)x) {} + + + + + // Gets the translated pointer (plus automatic casting to lhs) + auto_pointer get() const { return memory_translate(p); } + + // Gets the translated pointer (casted to T*) + template T* get() const { return get(); } + + // Gets the raw pointer, without translation (casted to T*) + template T* get_raw() const { return auto_pointer(p); } + + // This type can get assigned from void* and uintptr_t + basic_memory_pointer& operator=(void* x) { return p = x, *this; } + basic_memory_pointer& operator=(uintptr_t x) { return a = x, *this; } + + /* Arithmetic */ + basic_memory_pointer operator+(const basic_memory_pointer& rhs) const + { return basic_memory_pointer(this->a + rhs.a); } + + basic_memory_pointer operator-(const basic_memory_pointer& rhs) const + { return basic_memory_pointer(this->a - rhs.a); } + + basic_memory_pointer operator*(const basic_memory_pointer& rhs) const + { return basic_memory_pointer(this->a * rhs.a); } + + basic_memory_pointer operator/(const basic_memory_pointer& rhs) const + { return basic_memory_pointer(this->a / rhs.a); } + + + /* Comparision */ + bool operator==(const basic_memory_pointer& rhs) const + { return this->a == rhs.a; } + + bool operator!=(const basic_memory_pointer& rhs) const + { return this->a != rhs.a; } + + bool operator<(const basic_memory_pointer& rhs) const + { return this->a < rhs.a; } + + bool operator<=(const basic_memory_pointer& rhs) const + { return this->a <= rhs.a; } + + bool operator>(const basic_memory_pointer& rhs) const + { return this->a > rhs.a; } + + bool operator>=(const basic_memory_pointer& rhs) const + { return this->a >=rhs.a; } + + bool is_null() const { return this->p == nullptr; } + uintptr_t as_int() const { return this->a; } // does not perform translation + + + +#if __cplusplus >= 201103L || _MSC_VER >= 1800 // MSVC 2013 + /* Conversion to other types */ + explicit operator uintptr_t() const + { return this->a; } // does not perform translation + explicit operator bool() const + { return this->p != nullptr; } +#else + //operator bool() -------------- Causes casting problems because of implicitness, use !is_null() + //{ return this->p != nullptr; } +#endif + +}; + + // Typedefs including memory translator for the above type +typedef basic_memory_pointer memory_pointer; +typedef basic_memory_pointer memory_pointer_raw; +typedef basic_memory_pointer memory_pointer_aslr; + + + +/* + * memory_pointer_tr + * Stores a basic_memory_pointer as a raw pointer from translated pointer + */ +union memory_pointer_tr +{ + protected: + void* p; + uintptr_t a; + + public: + template + memory_pointer_tr(const basic_memory_pointer& ptr) + : p(ptr.get()) + {} // Constructs from a basic_memory_pointer + + memory_pointer_tr(const auto_pointer& ptr) + : p(ptr.p) + {} // Constructs from a auto_pointer, probably comming from basic_memory_pointer::get + + memory_pointer_tr(const memory_pointer_tr& rhs) + : p(rhs.p) + {} // Constructs from my own type, copy constructor + + memory_pointer_tr(uintptr_t x) + : p(memory_pointer(x).get()) + {} // Constructs from a integer, translating the address + + memory_pointer_tr(void* x) + : p(memory_pointer(x).get()) + {} // Constructs from a void pointer, translating the address + + // Just to be method-compatible with basic_memory_pointer ... + auto_pointer get() { return auto_pointer(p); } + template T* get() { return get(); } + template T* get_raw() { return get(); } + + memory_pointer_tr operator+(const uintptr_t& rhs) const + { return memory_pointer_raw(this->a + rhs); } + + memory_pointer_tr operator-(const uintptr_t& rhs) const + { return memory_pointer_raw(this->a - rhs); } + + memory_pointer_tr operator*(const uintptr_t& rhs) const + { return memory_pointer_raw(this->a * rhs); } + + memory_pointer_tr operator/(const uintptr_t& rhs) const + { return memory_pointer_raw(this->a / rhs); } + + bool is_null() const { return this->p == nullptr; } + uintptr_t as_int() const { return this->a; } + +#if __cplusplus >= 201103L + explicit operator uintptr_t() const + { return this->a; } +#else +#endif + +}; + + + + + + + +/* + * ProtectMemory + * Makes the address @addr have a protection of @protection + */ +inline bool ProtectMemory(memory_pointer_tr addr, size_t size, DWORD protection) +{ + return VirtualProtect(addr.get(), size, protection, &protection) != 0; +} + +/* + * UnprotectMemory + * Unprotect the memory at @addr with size @size so it have all accesses (execute, read and write) + * Returns the old protection to out_oldprotect + */ +inline bool UnprotectMemory(memory_pointer_tr addr, size_t size, DWORD& out_oldprotect) +{ + return VirtualProtect(addr.get(), size, PAGE_EXECUTE_READWRITE, &out_oldprotect) != 0; +} + +/* + * scoped_unprotect + * RAII wrapper for UnprotectMemory + * On construction unprotects the memory, on destruction reprotects the memory + */ +struct scoped_unprotect +{ + memory_pointer_raw addr; + size_t size; + DWORD dwOldProtect; + bool bUnprotected; + + scoped_unprotect(memory_pointer_tr addr, size_t size) + { + if(size == 0) bUnprotected = false; + else bUnprotected = UnprotectMemory(this->addr = addr.get(), this->size = size, dwOldProtect); + } + + ~scoped_unprotect() + { + if(bUnprotected) ProtectMemory(this->addr.get(), this->size, this->dwOldProtect); + } +}; + + + + + + + + +/* + * WriteMemoryRaw + * Writes into memory @addr the content of @value with a sizeof @size + * Does memory unprotection if @vp is true + */ +inline void WriteMemoryRaw(memory_pointer_tr addr, void* value, size_t size, bool vp) +{ + scoped_unprotect xprotect(addr, vp? size : 0); + memcpy(addr.get(), value, size); +} + +/* + * ReadMemoryRaw + * Reads the memory at @addr with a sizeof @size into address @ret + * Does memory unprotection if @vp is true + */ +inline void ReadMemoryRaw(memory_pointer_tr addr, void* ret, size_t size, bool vp) +{ + scoped_unprotect xprotect(addr, vp? size : 0); + memcpy(ret, addr.get(), size); +} + +/* + * MemoryFill + * Fills the memory at @addr with the byte @value doing it @size times + * Does memory unprotection if @vp is true + */ +inline void MemoryFill(memory_pointer_tr addr, uint8_t value, size_t size, bool vp) +{ + scoped_unprotect xprotect(addr, vp? size : 0); + memset(addr.get(), value, size); +} + +/* + * WriteObject + * Assigns the object @value into the same object type at @addr + * Does memory unprotection if @vp is true + */ +template +inline T& WriteObject(memory_pointer_tr addr, const T& value, bool vp = false) +{ + scoped_unprotect xprotect(addr, vp? sizeof(value) : 0); + return (*addr.get() = value); +} + +/* + * ReadObject + * Assigns the object @value with the value of the same object type at @addr + * Does memory unprotection if @vp is true + */ +template +inline T& ReadObject(memory_pointer_tr addr, T& value, bool vp = false) +{ + scoped_unprotect xprotect(addr, vp? sizeof(value) : 0); + return (value = *addr.get()); +} + + +/* + * WriteMemory + * Writes the object of type T into the address @addr + * Does memory unprotection if @vp is true + */ +template +inline void WriteMemory(memory_pointer_tr addr, T value, bool vp = false) +{ + WriteObject(addr, value, vp); +} + +/* + * ReadMemory + * Reads the object type T at address @addr + * Does memory unprotection if @vp is true + */ +template +inline T ReadMemory(memory_pointer_tr addr, bool vp = false) +{ + T value; + return ReadObject(addr, value, vp); +} + +/* + * AdjustPointer + * Searches in the range [@addr, @addr + @max_search] for a pointer in the range [@default_base, @default_end] and replaces + * it with the proper offset in the pointer @replacement_base. + * Does memory unprotection if @vp is true. + */ + inline memory_pointer_raw AdjustPointer(memory_pointer_tr addr, + memory_pointer_raw replacement_base, memory_pointer_tr default_base, memory_pointer_tr default_end, + size_t max_search = 8, bool vp = true) + { + scoped_unprotect xprotect(addr, vp? max_search + sizeof(void*) : 0); + for(size_t i = 0; i < max_search; ++i) + { + memory_pointer_raw ptr = ReadMemory(addr + i); + if(ptr >= default_base.get() && ptr <= default_end.get()) + { + auto result = replacement_base + (ptr - default_base.get()); + WriteMemory(addr + i, result.get()); + return result; + } + } + return nullptr; + } + + + + + + +/* + * GetAbsoluteOffset + * Gets absolute address based on relative offset @rel_value from instruction that ends at @end_of_instruction + */ +inline memory_pointer_raw GetAbsoluteOffset(int rel_value, memory_pointer_tr end_of_instruction) +{ + return end_of_instruction.get() + rel_value; +} + +/* + * GetRelativeOffset + * Gets relative offset based on absolute address @abs_value for instruction that ends at @end_of_instruction + */ +inline int GetRelativeOffset(memory_pointer_tr abs_value, memory_pointer_tr end_of_instruction) +{ + return uintptr_t(abs_value.get() - end_of_instruction.get()); +} + +/* + * ReadRelativeOffset + * Reads relative offset from address @at + */ +inline memory_pointer_raw ReadRelativeOffset(memory_pointer_tr at, size_t sizeof_addr = 4, bool vp = true) +{ + switch(sizeof_addr) + { + case 1: return (GetAbsoluteOffset(ReadMemory (at, vp), at+sizeof_addr)); + case 2: return (GetAbsoluteOffset(ReadMemory(at, vp), at+sizeof_addr)); + case 4: return (GetAbsoluteOffset(ReadMemory(at, vp), at+sizeof_addr)); + } + return nullptr; +} + +/* + * MakeRelativeOffset + * Writes relative offset into @at based on absolute destination @dest + */ +inline void MakeRelativeOffset(memory_pointer_tr at, memory_pointer_tr dest, size_t sizeof_addr = 4, bool vp = true) +{ + switch(sizeof_addr) + { + case 1: WriteMemory (at, static_cast (GetRelativeOffset(dest, at+sizeof_addr)), vp); + case 2: WriteMemory(at, static_cast(GetRelativeOffset(dest, at+sizeof_addr)), vp); + case 4: WriteMemory(at, static_cast(GetRelativeOffset(dest, at+sizeof_addr)), vp); + } +} + +/* + * GetBranchDestination + * Gets the destination of a branch instruction at address @at + * *** Works only with JMP and CALL for now *** + */ +inline memory_pointer_raw GetBranchDestination(memory_pointer_tr at, bool vp = true) +{ + switch(ReadMemory(at, vp)) + { + // We need to handle other instructions (and prefixes) later... + case 0xE8: // call rel + case 0xE9: // jmp rel + return ReadRelativeOffset(at + 1, 4, vp); + + case 0xFF: + switch(ReadMemory(at + 1, vp)) + { + case 0x15: // call dword ptr [addr] + case 0x25: // jmp dword ptr [addr] + return *(ReadMemory(at + 2, vp)); + } + break; + } + return nullptr; +} + +/* + * MakeJMP + * Creates a JMP instruction at address @at that jumps into address @dest + * If there was already a branch instruction there, returns the previosly destination of the branch + */ +inline memory_pointer_raw MakeJMP(memory_pointer_tr at, memory_pointer_raw dest, bool vp = true) +{ + auto p = GetBranchDestination(at, vp); + WriteMemory(at, 0xE9, vp); + MakeRelativeOffset(at+1, dest, 4, vp); + return p; +} + +/* + * MakeCALL + * Creates a CALL instruction at address @at that jumps into address @dest + * If there was already a branch instruction there, returns the previosly destination of the branch + */ +inline memory_pointer_raw MakeCALL(memory_pointer_tr at, memory_pointer_raw dest, bool vp = true) +{ + auto p = GetBranchDestination(at, vp); + WriteMemory(at, 0xE8, vp); + MakeRelativeOffset(at+1, dest, 4, vp); + return p; +} + +/* + * MakeJA + * Creates a JA instruction at address @at that jumps if above into address @dest + * If there was already a branch instruction there, returns the previosly destination of the branch + */ +inline void MakeJA(memory_pointer_tr at, memory_pointer_raw dest, bool vp = true) +{ + WriteMemory(at, 0x87F0, vp); + MakeRelativeOffset(at+2, dest, 4, vp); +} + +/* + * MakeNOP + * Creates a bunch of NOP instructions at address @at + */ +inline void MakeNOP(memory_pointer_tr at, size_t count = 1, bool vp = true) +{ + MemoryFill(at, 0x90, count, vp); +} + +/* + * MakeRangedNOP + * Creates a bunch of NOP instructions at address @at until address @until + */ +inline void MakeRangedNOP(memory_pointer_tr at, memory_pointer_tr until, bool vp = true) +{ + return MakeNOP(at, size_t(until.get_raw() - at.get_raw()), vp); +} + + +/* + * MakeRET + * Creates a RET instruction at address @at popping @pop values from the stack + * If @pop is equal to 0 it will use the 1 byte form of the instruction + */ +inline void MakeRET(memory_pointer_tr at, uint16_t pop = 0, bool vp = true) +{ + WriteMemory(at, pop? 0xC2 : 0xC3, vp); + if(pop) WriteMemory(at+1, pop, vp); +} + + + + + +/* + * lazy_pointer + * Lazy pointer, where it's final value will get evaluated only once when finally needed. + */ + template + struct lazy_pointer + { + public: + // Returns the final raw pointer + static auto_pointer get() + { + return xget().get(); + } + + template + static T* get() + { + return get().get(); + } + + private: + // Returns the final pointer + static memory_pointer_raw xget() + { + static void* ptr = nullptr; + if(!ptr) ptr = memory_pointer(addr).get(); + return memory_pointer_raw(ptr); + } +}; + + /* + * lazy_object + * Lazy object, where it's final object will get evaluated only once when finally needed. + */ + template + struct lazy_object + { + static T& get() + { + static T data; + static bool has_data = false; + if(!has_data) + { + ReadObject(addr, data, true); + has_data = true; + } + return data; + } + }; + + + /* + Helpers + */ + + template +inline memory_pointer mem_ptr(T p) +{ + return memory_pointer(p); +} + +template +inline memory_pointer_raw raw_ptr(T p) +{ + return memory_pointer_raw(p); +} + +template +inline memory_pointer_raw raw_ptr(basic_memory_pointer p) +{ + return raw_ptr(p.get()); +} + +template +inline memory_pointer_raw lazy_ptr() +{ + return lazy_pointer::get(); +} + +template +inline memory_pointer_aslr aslr_ptr(T p) +{ + return memory_pointer_aslr(p); +} + + + + + + +#ifndef INJECTOR_GVM_OWN_DETECT // Should we implement our detection method? + +// Detects game, region and version; returns false if could not detect it +inline bool game_version_manager::Detect() +{ + // Cleanup data + this->Clear(); + + // Find NT header + uintptr_t base = (uintptr_t) GetModuleHandleA(NULL); + IMAGE_DOS_HEADER* dos = (IMAGE_DOS_HEADER*)(base); + IMAGE_NT_HEADERS* nt = (IMAGE_NT_HEADERS*)(base + dos->e_lfanew); + + // Look for game and version thought the entry-point + // Thanks to Silent for many of the entry point offsets + switch (base + nt->OptionalHeader.AddressOfEntryPoint + 0x400000 - (DWORD)GetModuleHandle(NULL)) + { + case 0x5C1E70: // GTA III 1.0 + game = '3', major = 1, minor = 0, region = 0, steam = false; + return true; + + case 0x5C2130: // GTA III 1.1 + game = '3', major = 1, minor = 1, region = 0, steam = false; + return true; + + case 0x5C6FD0: // GTA III 1.1 (Cracked Steam Version) + case 0x9912ED: // GTA III 1.1 (Encrypted Steam Version) + game = '3', major = 1, minor = 1, region = 0, steam = true; + return true; + + case 0x667BF0: // GTA VC 1.0 + game = 'V', major = 1, minor = 0, region = 0, steam = false; + return true; + + case 0x667C40: // GTA VC 1.1 + game = 'V', major = 1, minor = 1, region = 0, steam = false; + return true; + + case 0x666BA0: // GTA VC 1.1 (Cracked Steam Version) + case 0xA402ED: // GTA VC 1.1 (Encrypted Steam Version) + game = 'V', major = 1, minor = 1, region = 0, steam = true; + return true; + + case 0x82457C: // GTA SA 1.0 US Cracked + case 0x824570: // GTA SA 1.0 US Compact + game = 'S', major = 1, minor = 0, region = 'U', steam = false; + cracker = injector::ReadMemory(raw_ptr(0x406A20), true) == 0xE9? 'H' : 0; + return true; + + case 0x8245BC: // GTA SA 1.0 EU Cracked (??????) + case 0x8245B0: // GTA SA 1.0 EU Cracked + game = 'S', major = 1, minor = 0, region = 'E', steam = false; + cracker = injector::ReadMemory(raw_ptr(0x406A20), true) == 0xE9? 'H' : 0; // just to say 'securom' + return true; + + case 0x8252FC: // GTA SA 1.1 US Cracked + game = 'S', major = 1, minor = 1, region = 'U', steam = false; + return true; + + case 0x82533C: // GTA SA 1.1 EU Cracked + game = 'S', major = 1, minor = 1, region = 'E', steam = false; + return true; + + case 0x85EC4A: // GTA SA 3.0 (Cracked Steam Version) + case 0xD3C3DB: // GTA SA 3.0 (Encrypted Steam Version) + game = 'S', major = 3, minor = 0, region = 0, steam = true; + return true; + + case 0xC965AD: // GTA IV 1.0.0.4 US + game = 'I', major = 1, minor = 0, majorRevision = 0, minorRevision = 4, region = 'U', steam = false; + return true; + + case 0xD0D011: // GTA IV 1.0.0.7 US + game = 'I', major = 1, minor = 0, majorRevision = 0, minorRevision = 7, region = 'U', steam = false; + return true; + + case 0xD0AF06: // GTA EFLC 1.1.2.0 US + game = 'E', major = 1, minor = 1, majorRevision = 2, minorRevision = 0, region = 'U', steam = false; + return true; + + default: + return false; + } +} + +#endif + + +} // namespace + diff --git a/includes/injector/utility.hpp b/includes/injector/utility.hpp new file mode 100644 index 0000000..0a5eafc --- /dev/null +++ b/includes/injector/utility.hpp @@ -0,0 +1,56 @@ +/* + * Injectors - Utility / Helpers + * + * Copyright (C) 2014 LINK/2012 + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + */ +#pragma once + +namespace injector +{ + template + T return_value() + { + return value; + } + + template + void* force_ptr(const T& fun) + { + auto ptr = fun; + return *(void**)&ptr; + } + + + // Helper structure to help calling back what was there before a hook + // e.g. hb.fun = MakeCALL(0x0, raw_ptr(my_hook)); + template + struct hook_back + { + typedef FuncType func_type; + + func_type fun; + + hook_back() : fun(nullptr) + {} + }; +}; diff --git a/includes/stdafx.h b/includes/stdafx.h new file mode 100644 index 0000000..f3a0737 --- /dev/null +++ b/includes/stdafx.h @@ -0,0 +1,16 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files: +#include + + + +// TODO: reference additional headers your program requires here diff --git a/ini/UbiTMNTHax.ini b/ini/UbiTMNTHax.ini new file mode 100644 index 0000000..a499ebb --- /dev/null +++ b/ini/UbiTMNTHax.ini @@ -0,0 +1,10 @@ +[TMNTHax] +BorderlessWindowed = 0 // Make sure to first set Fullscreen=0 in Hardware.ini! (found in AppData\Roaming\TMNT, if not there - generate one with TMNT.exe launcher first!) +EnableWindowResize = 0 // If you resize the window, the mouse position readout *will break* +EnableConsole = 1 +AllowMultipleInstances = 0 +RelocateWindow = 0 // Use WindowLocation below to configure + +[WindowLocation] +X = 0 +Y = 0 diff --git a/stdafx.cpp b/stdafx.cpp new file mode 100644 index 0000000..fd4f341 --- /dev/null +++ b/stdafx.cpp @@ -0,0 +1 @@ +#include "stdafx.h" diff --git a/stdafx.h b/stdafx.h new file mode 100644 index 0000000..f3a0737 --- /dev/null +++ b/stdafx.h @@ -0,0 +1,16 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files: +#include + + + +// TODO: reference additional headers your program requires here diff --git a/targetver.h b/targetver.h new file mode 100644 index 0000000..87c0086 --- /dev/null +++ b/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include