├── .gitignore ├── DirectXHook.sln ├── DirectXHook.vcxproj ├── DirectXHook.vcxproj.filters ├── DirectXHook.vcxproj.user ├── README.md ├── assets ├── hook_fonts │ └── OpenSans-22.spritefont ├── hook_textures │ └── blank.jpg └── repo_pictures │ ├── box_struct.png │ ├── create_files.png │ ├── dllmain.png │ ├── er_first_person_souls.png │ ├── er_pause_the_game.png │ ├── example_header.png │ ├── example_source.png │ ├── rgb_boxes.png │ ├── rgb_boxes_code.png │ ├── text.png │ ├── text_code.png │ ├── textures.png │ └── textures_code.png ├── include ├── DirectXHook.h ├── ID3DRenderer.h ├── IRenderCallback.h ├── Logger.h ├── MemoryUtils.h ├── OverlayFramework.h ├── Renderer.h ├── UniversalProxyDLL.h └── nmd_assembly.h ├── overlays ├── Example │ ├── Example.cpp │ └── Example.h ├── PauseTheGame │ ├── PauseTheGame.cpp │ └── PauseTheGame.h └── RiseDpsMeter │ ├── RiseDpsMeter.cpp │ └── RiseDpsMeter.h ├── packages.config └── src ├── DirectXHook.cpp ├── DllMain.cpp ├── Renderer.cpp └── Shaders.hlsl /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | packages/ 3 | x64/ 4 | x86/ 5 | Debug/ 6 | Release/ 7 | -------------------------------------------------------------------------------- /DirectXHook.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31205.134 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DirectXHook", "DirectXHook.vcxproj", "{5EB93C8A-65BB-403D-A8B5-A39FCF98F17F}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {5EB93C8A-65BB-403D-A8B5-A39FCF98F17F}.Debug|x64.ActiveCfg = Debug|x64 17 | {5EB93C8A-65BB-403D-A8B5-A39FCF98F17F}.Debug|x64.Build.0 = Debug|x64 18 | {5EB93C8A-65BB-403D-A8B5-A39FCF98F17F}.Debug|x86.ActiveCfg = Debug|Win32 19 | {5EB93C8A-65BB-403D-A8B5-A39FCF98F17F}.Debug|x86.Build.0 = Debug|Win32 20 | {5EB93C8A-65BB-403D-A8B5-A39FCF98F17F}.Release|x64.ActiveCfg = Release|x64 21 | {5EB93C8A-65BB-403D-A8B5-A39FCF98F17F}.Release|x64.Build.0 = Release|x64 22 | {5EB93C8A-65BB-403D-A8B5-A39FCF98F17F}.Release|x86.ActiveCfg = Release|Win32 23 | {5EB93C8A-65BB-403D-A8B5-A39FCF98F17F}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {B1B72F8D-E60B-4269-AC0B-EAC732492566} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /DirectXHook.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | Win32Proj 24 | {5eb93c8a-65bb-403d-a8b5-a39fcf98f17f} 25 | DirectXHook 26 | 10.0 27 | 28 | 29 | 30 | DynamicLibrary 31 | true 32 | MultiByte 33 | v143 34 | 35 | 36 | DynamicLibrary 37 | false 38 | true 39 | MultiByte 40 | v143 41 | 42 | 43 | DynamicLibrary 44 | true 45 | MultiByte 46 | v143 47 | 48 | 49 | DynamicLibrary 50 | false 51 | true 52 | MultiByte 53 | v143 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | false 74 | dinput8 75 | $(SolutionDir)overlays;$(SolutionDir)include;$(IncludePath) 76 | $(SolutionDir)overlays;$(SolutionDir)src 77 | 78 | 79 | false 80 | dinput8 81 | $(SolutionDir)overlays;$(SolutionDir)include;$(IncludePath) 82 | $(SolutionDir)overlays;$(SolutionDir)src 83 | 84 | 85 | false 86 | $(SolutionDir)overlays;$(SolutionDir)include;$(IncludePath) 87 | $(SolutionDir)overlays;$(SolutionDir)src 88 | dinput8 89 | 90 | 91 | false 92 | $(SolutionDir)overlays;$(SolutionDir)include;$(IncludePath) 93 | $(SolutionDir)overlays;$(SolutionDir)src 94 | .dll 95 | dinput8 96 | true 97 | 98 | 99 | 100 | Level3 101 | true 102 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions);_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING 103 | true 104 | MultiThreadedDLL 105 | stdcpp17 106 | 107 | 108 | Windows 109 | true 110 | 111 | 112 | d3d11.lib;d3d12.lib;d3dcompiler.lib;%(AdditionalDependencies) 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | Level3 122 | true 123 | true 124 | true 125 | _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING;NDEBUG;_CONSOLE 126 | true 127 | MultiThreadedDLL 128 | stdcpp17 129 | 130 | 131 | Windows 132 | true 133 | true 134 | false 135 | 136 | 137 | d3d11.lib;d3d12.lib;d3dcompiler.lib;%(AdditionalDependencies) 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | Level3 147 | true 148 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions);_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING 149 | true 150 | MultiThreadedDebug 151 | stdcpp17 152 | 153 | 154 | Windows 155 | true 156 | 157 | 158 | d3d11.lib;d3d12.lib;d3dcompiler.lib;%(AdditionalDependencies) 159 | 160 | 161 | COPY "C:\Programming\Github repositories\DirectXHook\x64\Release\dinput8.dll" "G:\SteamLibrary\steamapps\common\MonsterHunterRise\reframework\plugins\RiseDpsMeter.dll" 162 | 163 | 164 | 165 | 166 | Level3 167 | true 168 | true 169 | true 170 | _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING;NDEBUG;_CONSOLE 171 | true 172 | MultiThreadedDLL 173 | stdcpp17 174 | 175 | 176 | Windows 177 | true 178 | true 179 | false 180 | 181 | 182 | d3d11.lib;d3d12.lib;d3dcompiler.lib;%(AdditionalDependencies) 183 | 184 | 185 | COPY "C:\Programming\Github repositories\DirectXHook\x64\Release\dinput8.dll" "F:\SteamLibrary\steamapps\common\ELDEN RING\Game\dinput8.dll" 186 | XCOPY /y "C:\Programming\Github repositories\DirectXHook\assets\hook_textures" "F:\SteamLibrary\steamapps\common\ELDEN RING\Game\hook_textures\" 187 | XCOPY /y "C:\Programming\Github repositories\DirectXHook\assets\hook_fonts" "F:\SteamLibrary\steamapps\common\ELDEN RING\Game\hook_fonts\" 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | false 196 | 197 | 198 | false 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | true 208 | true 209 | true 210 | true 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | false 224 | 225 | 226 | false 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 237 | 238 | 239 | 240 | 241 | -------------------------------------------------------------------------------- /DirectXHook.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {4e722490-89b1-476c-8f11-f855725e5074} 14 | 15 | 16 | {8a12ed8f-5e32-450f-8ec9-985d3e5cde32} 17 | 18 | 19 | {f65c6413-9861-46d0-ac93-cc3206db7958} 20 | 21 | 22 | {49480136-b55a-468a-b6ab-1720b97d7141} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Source Files 31 | 32 | 33 | Source Files 34 | 35 | 36 | Source Files 37 | 38 | 39 | Overlays\Example 40 | 41 | 42 | Overlays\PauseTheGame 43 | 44 | 45 | Overlays\RiseDpsMeter 46 | 47 | 48 | 49 | 50 | Source Files 51 | 52 | 53 | 54 | 55 | Header Files 56 | 57 | 58 | Header Files 59 | 60 | 61 | Header Files 62 | 63 | 64 | Header Files 65 | 66 | 67 | Header Files 68 | 69 | 70 | Header Files 71 | 72 | 73 | Overlays\Example 74 | 75 | 76 | Overlays\RiseDpsMeter 77 | 78 | 79 | Overlays\PauseTheGame 80 | 81 | 82 | Header Files 83 | 84 | 85 | Header Files 86 | 87 | 88 | -------------------------------------------------------------------------------- /DirectXHook.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | G:\SteamLibrary\steamapps\common\ELDEN RING\Game\eldenring.exe 5 | true 6 | WindowsLocalDebugger 7 | 8 | 9 | G:\SteamLibrary\steamapps\common\ELDEN RING\Game\eldenring.exe 10 | true 11 | WindowsLocalDebugger 12 | 13 | 14 | G:\SteamLibrary\steamapps\common\ELDEN RING\Game\eldenring.exe 15 | WindowsLocalDebugger 16 | true 17 | 18 | 19 | G:\SteamLibrary\steamapps\common\ELDEN RING\Game\eldenring.exe 20 | WindowsLocalDebugger 21 | true 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## DirectXHook + Overlay Framework 2 | A DirectX hook that works with DirectX11 and DirectX12 in 32-bit and 64-bit modes. 3 | 4 | Basically this library lets you render your own things inside the game window, as an integrated part of the game rather than as an external overlay. A straightforward but primitive overlay framework is included so you can quickly and easily start creating overlays. Tutorial below. 5 | 6 | ### This library is used in... 7 | #### A mod for Elden Ring, ["First Person Souls - Full Game Conversion found on NexusMods"](https://www.nexusmods.com/eldenring/mods/3266) 8 | 9 | [![First Person Souls](https://github.com/techiew/DirectXHook/blob/master/assets/repo_pictures/er_first_person_souls.png)](https://www.youtube.com/watch?v=nuau_lZ0Imc) 10 | 11 | #### A mod for Elden Ring, ["Pause the game" found on NexusMods](https://www.nexusmods.com/eldenring/mods/43) 12 | 13 | [![Pause the Game](https://github.com/techiew/DirectXHook/blob/master/assets/repo_pictures/er_pause_the_game.png)](https://www.youtube.com/watch?v=xvK1ti_hHh4) 14 | 15 | #### A mod for Monster Hunter Rise, ["Rise DPS Meter" found on NexusMods](https://www.nexusmods.com/monsterhunterrise/mods/289) 16 | 17 | -video to be added- 18 | 19 | #### Example triangle 20 | 21 | -video to be added- 22 | 23 | ## How to create an overlay 24 | First, check the [wiki page](https://github.com/techiew/DirectXHook/wiki/How-to-set-up-the-Visual-Studio-solution) on how to quickly set up the Visual Studio solution. 25 | 26 | When the project is built, "dinput8.dll" will be generated in the project folder. This must be copied next to a game executable which uses DirectX 11 or 12. The game will then load the .dll automatically on startup and will render what you told it to. 27 | 28 | Also note that the "hook_textures" folder containing "blank.jpg" must be present next to dxgi.dll in order for anything to render. 29 | 30 | ### Create files 31 | Create a .cpp and .h file in the Overlays folder (optionally put these inside a parent folder): 32 | 33 | ![create_files](https://github.com/techiew/DirectXHook/blob/master/assets/repo_pictures/create_files.png) 34 | 35 | Create a class that inherits from the IRenderCallback interface and includes "OverlayFramework.h": 36 | 37 | ![example_header](https://github.com/techiew/DirectXHook/blob/master/assets/repo_pictures/example_header.png) 38 | 39 | Define the Setup() and Render() functions in the .cpp file: 40 | 41 | ![example_source](https://github.com/techiew/DirectXHook/blob/master/assets/repo_pictures/example_source.png) 42 | 43 | **Note: Setup() is called once and Render() is called every frame. InitFramework() must be called on the very first line in Setup().** 44 | 45 | Make the hook render your stuff by adding these lines in DllMain.cpp: 46 | 47 | ![dllmain](https://github.com/techiew/DirectXHook/blob/master/assets/repo_pictures/dllmain.png) 48 | 49 | But we have yet to define anything to render... 50 | 51 | ### Boxes 52 | All rendering with the overlay framework is done using Boxes: 53 | 54 | ![box_struct](https://github.com/techiew/DirectXHook/blob/master/assets/repo_pictures/box_struct.png) 55 | 56 | Boxes are a simple struct with data that the framework manages. 57 | 58 | - **pressed** = if the mouse is currently being pressed on this box 59 | - **clicked** = if the mouse was previously pressed and then released on the box this frame 60 | - **hover** = if the mouse is hovering over the box 61 | 62 | The rest are self-explanatory. Do not modify **visible** or **z**. 63 | 64 | Create some boxes and render them: 65 | 66 | ![rgb_boxes_code](https://github.com/techiew/DirectXHook/blob/master/assets/repo_pictures/rgb_boxes_code.png) 67 | 68 | Result: 69 | 70 | ![rgb_boxes](https://github.com/techiew/DirectXHook/blob/master/assets/repo_pictures/rgb_boxes.png) 71 | 72 | Boxes can be rendered with either textures or colors: 73 | 74 | ![textures_code](https://github.com/techiew/DirectXHook/blob/master/assets/repo_pictures/textures_code.png) 75 | 76 | **Note: textures should be loaded in Setup().** 77 | 78 | Result: 79 | 80 | ![textures](https://github.com/techiew/DirectXHook/blob/master/assets/repo_pictures/textures.png) 81 | 82 | Text can be rendered inside Boxes: 83 | 84 | ![text_code](https://github.com/techiew/DirectXHook/blob/master/assets/repo_pictures/text_code.png) 85 | 86 | **Note: a font must be set before rendering text.** 87 | 88 | Result: 89 | 90 | ![text](https://github.com/techiew/DirectXHook/blob/master/assets/repo_pictures/text.png) 91 | 92 | ### Contributions 93 | Feel free to create issues or contribute code to the repo. 94 | 95 | ### License 96 | Feel free to use this code for anything and however you like, but if you create something with it then it would be cool if you could show me what you made :) 97 | 98 | -------------------------------------------------------------------------------- /assets/hook_fonts/OpenSans-22.spritefont: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techiew/DirectXHook/2fcd7487298d772f0e3bbf3ead4441422f58d6ed/assets/hook_fonts/OpenSans-22.spritefont -------------------------------------------------------------------------------- /assets/hook_textures/blank.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techiew/DirectXHook/2fcd7487298d772f0e3bbf3ead4441422f58d6ed/assets/hook_textures/blank.jpg -------------------------------------------------------------------------------- /assets/repo_pictures/box_struct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techiew/DirectXHook/2fcd7487298d772f0e3bbf3ead4441422f58d6ed/assets/repo_pictures/box_struct.png -------------------------------------------------------------------------------- /assets/repo_pictures/create_files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techiew/DirectXHook/2fcd7487298d772f0e3bbf3ead4441422f58d6ed/assets/repo_pictures/create_files.png -------------------------------------------------------------------------------- /assets/repo_pictures/dllmain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techiew/DirectXHook/2fcd7487298d772f0e3bbf3ead4441422f58d6ed/assets/repo_pictures/dllmain.png -------------------------------------------------------------------------------- /assets/repo_pictures/er_first_person_souls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techiew/DirectXHook/2fcd7487298d772f0e3bbf3ead4441422f58d6ed/assets/repo_pictures/er_first_person_souls.png -------------------------------------------------------------------------------- /assets/repo_pictures/er_pause_the_game.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techiew/DirectXHook/2fcd7487298d772f0e3bbf3ead4441422f58d6ed/assets/repo_pictures/er_pause_the_game.png -------------------------------------------------------------------------------- /assets/repo_pictures/example_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techiew/DirectXHook/2fcd7487298d772f0e3bbf3ead4441422f58d6ed/assets/repo_pictures/example_header.png -------------------------------------------------------------------------------- /assets/repo_pictures/example_source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techiew/DirectXHook/2fcd7487298d772f0e3bbf3ead4441422f58d6ed/assets/repo_pictures/example_source.png -------------------------------------------------------------------------------- /assets/repo_pictures/rgb_boxes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techiew/DirectXHook/2fcd7487298d772f0e3bbf3ead4441422f58d6ed/assets/repo_pictures/rgb_boxes.png -------------------------------------------------------------------------------- /assets/repo_pictures/rgb_boxes_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techiew/DirectXHook/2fcd7487298d772f0e3bbf3ead4441422f58d6ed/assets/repo_pictures/rgb_boxes_code.png -------------------------------------------------------------------------------- /assets/repo_pictures/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techiew/DirectXHook/2fcd7487298d772f0e3bbf3ead4441422f58d6ed/assets/repo_pictures/text.png -------------------------------------------------------------------------------- /assets/repo_pictures/text_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techiew/DirectXHook/2fcd7487298d772f0e3bbf3ead4441422f58d6ed/assets/repo_pictures/text_code.png -------------------------------------------------------------------------------- /assets/repo_pictures/textures.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techiew/DirectXHook/2fcd7487298d772f0e3bbf3ead4441422f58d6ed/assets/repo_pictures/textures.png -------------------------------------------------------------------------------- /assets/repo_pictures/textures_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techiew/DirectXHook/2fcd7487298d772f0e3bbf3ead4441422f58d6ed/assets/repo_pictures/textures_code.png -------------------------------------------------------------------------------- /include/DirectXHook.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "Renderer.h" 8 | #include "ID3DRenderer.h" 9 | #include "IRenderCallback.h" 10 | #include "Logger.h" 11 | #include "MemoryUtils.h" 12 | 13 | /* 14 | * Here we have typedefs of the functions we want to hook. 15 | * They are defined so we can call the respective functions through pointers to their memory addresses. 16 | * 17 | * Setting the proper calling convention is important (__stdcall). 18 | * It makes it so the function arguments are read/written to/from memory in the correct way. 19 | * 64-bit functions actually use the __fastcall calling convention, but the compiler changes 20 | * __stdcall to __fastcall automatically for 64-bit compilation. 21 | */ 22 | typedef HRESULT(__stdcall* Present)(IDXGISwapChain* This, UINT SyncInterval, UINT Flags); 23 | typedef HRESULT(__stdcall* ResizeBuffers)(IDXGISwapChain* This, UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT NewFormat, UINT SwapChainFlags); 24 | typedef void(__stdcall* ExecuteCommandLists)(ID3D12CommandQueue* This, UINT NumCommandLists, const ID3D12CommandList** ppCommandLists); 25 | 26 | // Hooks DirectX 11 and DirectX 12 27 | class DirectXHook 28 | { 29 | public: 30 | ID3DRenderer* renderer; 31 | uintptr_t executeCommandListsAddress = 0; 32 | uintptr_t presentReturnAddress = 0; 33 | uintptr_t resizeBuffersReturnAddress = 0; 34 | uintptr_t executeCommandListsReturnAddress = 0; 35 | 36 | DirectXHook(ID3DRenderer* renderer); 37 | void Hook(); 38 | void SetDrawExampleTriangle(bool doDraw); 39 | void AddRenderCallback(IRenderCallback* object); 40 | ID3D12CommandQueue* CreateDummyCommandQueue(); 41 | void HookCommandQueue(ID3D12CommandQueue* dummyCommandQueue, uintptr_t executeCommandListsDetourFunction, uintptr_t* executeCommandListsReturnAddress); 42 | void UnhookCommandQueue(); 43 | 44 | private: 45 | Logger logger{ "DirectXHook" }; 46 | 47 | IDXGISwapChain* CreateDummySwapChain(); 48 | void HookSwapChain(IDXGISwapChain* dummySwapChain, uintptr_t presentDetourFunction, uintptr_t resizeBuffersDetourFunction, uintptr_t* presentReturnAddress, uintptr_t* resizeBuffersReturnAddress); 49 | }; -------------------------------------------------------------------------------- /include/ID3DRenderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "IRenderCallback.h" 6 | 7 | class ID3DRenderer 8 | { 9 | public: 10 | virtual void OnPresent(IDXGISwapChain* pThis, UINT syncInterval, UINT flags) = 0; 11 | virtual void OnResizeBuffers(IDXGISwapChain* pThis, UINT bufferCount, UINT width, UINT height, DXGI_FORMAT newFormat, UINT swapChainFlags) {}; 12 | virtual void AddRenderCallback(IRenderCallback* object) {}; 13 | virtual void SetCommandQueue(ID3D12CommandQueue* commandQueue) {}; 14 | virtual void SetGetCommandQueueCallback(void (*callback)()) {}; 15 | }; -------------------------------------------------------------------------------- /include/IRenderCallback.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class IRenderCallback 9 | { 10 | public: 11 | virtual void Setup() { }; 12 | virtual void Render() = 0; 13 | void Init( 14 | Microsoft::WRL::ComPtr device, 15 | Microsoft::WRL::ComPtr context, 16 | std::shared_ptr spriteBatch, 17 | HWND window) 18 | { 19 | this->device = device; 20 | this->context = context; 21 | this->spriteBatch = spriteBatch; 22 | this->window = window; 23 | } 24 | 25 | protected: 26 | Microsoft::WRL::ComPtr device = nullptr; 27 | Microsoft::WRL::ComPtr context = nullptr; 28 | std::shared_ptr spriteBatch = nullptr; 29 | HWND window = NULL; 30 | }; -------------------------------------------------------------------------------- /include/Logger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class Logger 7 | { 8 | public: 9 | Logger(const char* prefix) 10 | { 11 | printPrefix = prefix; 12 | 13 | FILE* logFile = GetLogFile(); 14 | if (logFile == nullptr) 15 | { 16 | fopen_s(&logFile, "hook_log.txt", "w"); 17 | GetLogFile(logFile); 18 | } 19 | } 20 | 21 | void Log(std::string msg, ...) 22 | { 23 | va_list args; 24 | va_start(args, msg); 25 | vprintf(std::string(printPrefix + " > " + msg + "\n").c_str(), args); 26 | if (GetLogFile() != nullptr) 27 | { 28 | vfprintf(GetLogFile(), std::string(printPrefix + " > " + msg + "\n").c_str(), args); 29 | fflush(GetLogFile()); 30 | } 31 | va_end(args); 32 | } 33 | 34 | private: 35 | std::string printPrefix = ""; 36 | 37 | static FILE* GetLogFile(FILE* newLogFile = nullptr) 38 | { 39 | static FILE* logFile = nullptr; 40 | if (newLogFile != nullptr) 41 | { 42 | logFile = newLogFile; 43 | } 44 | return logFile; 45 | } 46 | }; -------------------------------------------------------------------------------- /include/MemoryUtils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define NMD_ASSEMBLY_IMPLEMENTATION 13 | #define NMD_ASSEMBLY_PRIVATE 14 | #include "nmd_assembly.h" 15 | 16 | #include "Logger.h" 17 | 18 | // Contains various memory manipulation functions related to hooking or modding 19 | namespace MemoryUtils 20 | { 21 | static Logger logger{ "MemoryUtils" }; 22 | static constexpr int maskBytes = 0xffff; 23 | 24 | struct HookInformation 25 | { 26 | std::vector originalBytes = { 0 }; 27 | uintptr_t trampolineInstructionsAddress = 0; 28 | }; 29 | static std::unordered_map InfoBufferForHookedAddresses; 30 | 31 | // Disables or enables the memory protection in a given region. 32 | // Remembers and restores the original memory protection type of the given addresses. 33 | static void ToggleMemoryProtection(bool enableProtection, uintptr_t address, size_t size) 34 | { 35 | static std::map protectionHistory; 36 | if (enableProtection && protectionHistory.find(address) != protectionHistory.end()) 37 | { 38 | VirtualProtect((void*)address, size, protectionHistory[address], &protectionHistory[address]); 39 | protectionHistory.erase(address); 40 | } 41 | else if (!enableProtection && protectionHistory.find(address) == protectionHistory.end()) 42 | { 43 | DWORD oldProtection = 0; 44 | VirtualProtect((void*)address, size, PAGE_EXECUTE_READWRITE, &oldProtection); 45 | protectionHistory[address] = oldProtection; 46 | } 47 | } 48 | 49 | // Copies memory after changing the permissions at both the source and destination so we don't get an access violation. 50 | static void MemCopy(uintptr_t destination, uintptr_t source, size_t numBytes) 51 | { 52 | ToggleMemoryProtection(false, destination, numBytes); 53 | ToggleMemoryProtection(false, source, numBytes); 54 | memcpy((void*)destination, (void*)source, numBytes); 55 | ToggleMemoryProtection(true, source, numBytes); 56 | ToggleMemoryProtection(true, destination, numBytes); 57 | } 58 | 59 | // Simple wrapper around memset 60 | static void MemSet(uintptr_t address, unsigned char byte, size_t numBytes) 61 | { 62 | ToggleMemoryProtection(false, address, numBytes); 63 | memset((void*)address, byte, numBytes); 64 | ToggleMemoryProtection(true, address, numBytes); 65 | } 66 | 67 | // Gets the base address of the game's memory. 68 | static uintptr_t GetProcessBaseAddress(DWORD processId) 69 | { 70 | DWORD_PTR baseAddress = 0; 71 | HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId); 72 | 73 | if (processHandle) 74 | { 75 | DWORD bytesRequired = 0; 76 | if (EnumProcessModules(processHandle, NULL, 0, &bytesRequired)) 77 | { 78 | if (bytesRequired) 79 | { 80 | LPBYTE moduleArrayBytes = (LPBYTE)LocalAlloc(LPTR, bytesRequired); 81 | if (moduleArrayBytes) 82 | { 83 | HMODULE* moduleArray = (HMODULE*)moduleArrayBytes; 84 | if (EnumProcessModules(processHandle, moduleArray, bytesRequired, &bytesRequired)) 85 | { 86 | baseAddress = (DWORD_PTR)moduleArray[0]; 87 | } 88 | 89 | LocalFree(moduleArrayBytes); 90 | } 91 | } 92 | } 93 | 94 | CloseHandle(processHandle); 95 | } 96 | 97 | return baseAddress; 98 | } 99 | 100 | static std::string GetCurrentProcessName() 101 | { 102 | char lpFilename[MAX_PATH]; 103 | GetModuleFileNameA(NULL, lpFilename, sizeof(lpFilename)); 104 | std::string moduleName = strrchr(lpFilename, '\\'); 105 | moduleName = moduleName.substr(1, moduleName.length()); 106 | return moduleName; 107 | } 108 | 109 | static std::string GetCurrentModuleName() 110 | { 111 | HMODULE module = NULL; 112 | 113 | static char dummyStaticVariableToGetModuleName = 'x'; 114 | GetModuleHandleExA( 115 | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, 116 | &dummyStaticVariableToGetModuleName, 117 | &module); 118 | 119 | char lpFilename[MAX_PATH]; 120 | GetModuleFileNameA(module, lpFilename, sizeof(lpFilename)); 121 | char* lastSlash = strrchr(lpFilename, '\\'); 122 | std::string moduleName = ""; 123 | if (lastSlash != nullptr) 124 | { 125 | moduleName = lastSlash; 126 | moduleName = moduleName.substr(1, moduleName.length()); 127 | moduleName.erase(moduleName.find(".dll"), moduleName.length()); 128 | } 129 | return moduleName; 130 | } 131 | 132 | static void ShowErrorPopup(std::string error) 133 | { 134 | logger.Log("Raised error: %s", error.c_str()); 135 | MessageBox(NULL, error.c_str(), GetCurrentModuleName().c_str(), MB_OK | MB_ICONERROR | MB_SYSTEMMODAL); 136 | } 137 | 138 | static void PrintPattern(std::vector pattern) 139 | { 140 | std::string patternString = ""; 141 | for (auto bytes : pattern) 142 | { 143 | std::stringstream stream; 144 | std::string byte = ""; 145 | if (bytes == maskBytes) 146 | { 147 | byte = "?"; 148 | } 149 | else 150 | { 151 | stream << "0x" << std::hex << bytes; 152 | byte = stream.str(); 153 | } 154 | patternString.append(byte + " "); 155 | } 156 | logger.Log("Pattern: %s", patternString.c_str()); 157 | } 158 | 159 | // Scans the memory of the main process module for the given signature. 160 | static uintptr_t SigScan(std::vector pattern) 161 | { 162 | DWORD processId = GetCurrentProcessId(); 163 | uintptr_t regionStart = GetProcessBaseAddress(processId); 164 | logger.Log("Process name: %s", GetCurrentProcessName().c_str()); 165 | logger.Log("Process ID: %i", processId); 166 | logger.Log("Process base address: 0x%llX", regionStart); 167 | PrintPattern(pattern); 168 | 169 | size_t numRegionsChecked = 0; 170 | size_t maxNumberOfRegionsToCheck = 10000; 171 | uintptr_t currentAddress = 0; 172 | while (numRegionsChecked < maxNumberOfRegionsToCheck) 173 | { 174 | MEMORY_BASIC_INFORMATION memoryInfo = { 0 }; 175 | if (VirtualQuery((void*)regionStart, &memoryInfo, sizeof(MEMORY_BASIC_INFORMATION)) == 0) 176 | { 177 | DWORD error = GetLastError(); 178 | if (error == ERROR_INVALID_PARAMETER) 179 | { 180 | logger.Log("Reached end of scannable memory."); 181 | } 182 | else 183 | { 184 | logger.Log("VirtualQuery failed, error code: %i.", error); 185 | } 186 | break; 187 | } 188 | 189 | regionStart = (uintptr_t)memoryInfo.BaseAddress; 190 | size_t regionSize = memoryInfo.RegionSize; 191 | uintptr_t regionEnd = regionStart + regionSize; 192 | DWORD protection = memoryInfo.Protect; 193 | DWORD state = memoryInfo.State; 194 | 195 | bool isMemoryReadable = ( 196 | protection == PAGE_EXECUTE_READWRITE 197 | || protection == PAGE_READWRITE 198 | || protection == PAGE_READONLY 199 | || protection == PAGE_WRITECOPY 200 | || protection == PAGE_EXECUTE_WRITECOPY) 201 | && state == MEM_COMMIT; 202 | 203 | if (isMemoryReadable) 204 | { 205 | logger.Log("Checking region: %p", regionStart); 206 | currentAddress = regionStart; 207 | while (currentAddress < regionEnd - pattern.size()) 208 | { 209 | for (size_t i = 0; i < pattern.size(); i++) 210 | { 211 | if (pattern[i] == maskBytes) 212 | { 213 | currentAddress++; 214 | continue; 215 | } 216 | else if (*(unsigned char*)currentAddress != (unsigned char)pattern[i]) 217 | { 218 | currentAddress++; 219 | break; 220 | } 221 | else if (i == pattern.size() - 1) 222 | { 223 | uintptr_t signature = currentAddress - pattern.size() + 1; 224 | logger.Log("Found signature at %p", signature); 225 | return signature; 226 | } 227 | currentAddress++; 228 | } 229 | } 230 | } 231 | else 232 | { 233 | logger.Log("Skipped region: %p", regionStart); 234 | } 235 | 236 | numRegionsChecked++; 237 | regionStart += regionSize; 238 | } 239 | 240 | logger.Log("Stopped at: %p, num regions checked: %i", currentAddress, numRegionsChecked); 241 | ShowErrorPopup("Could not find signature!"); 242 | return 0; 243 | } 244 | 245 | static uintptr_t AllocateMemory(size_t numBytes) 246 | { 247 | uintptr_t memoryAddress = NULL; 248 | memoryAddress = (uintptr_t)VirtualAlloc(NULL, numBytes, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); 249 | 250 | if (memoryAddress == NULL) 251 | { 252 | logger.Log("Failed to allocate %i bytes of memory", numBytes); 253 | } 254 | else 255 | { 256 | logger.Log("Allocated %i bytes of memory at %p", numBytes, memoryAddress); 257 | MemSet(memoryAddress, 0x90, numBytes); 258 | } 259 | 260 | return memoryAddress; 261 | } 262 | 263 | static uintptr_t AllocateMemoryWithin32BitRange(size_t numBytes, uintptr_t origin) 264 | { 265 | uintptr_t memoryAddress = NULL; 266 | size_t unidirectionalRange = 0x7fffffff; 267 | uintptr_t lowerBound = origin - unidirectionalRange; 268 | uintptr_t higherBound = origin + unidirectionalRange; 269 | size_t numAttempts = 0; 270 | for (size_t i = lowerBound; i < higherBound;) 271 | { 272 | numAttempts++; 273 | memoryAddress = (uintptr_t)VirtualAlloc((void*)i, numBytes, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); 274 | 275 | if (memoryAddress != NULL) 276 | { 277 | bool memoryAddressIsAcceptable = memoryAddress >= lowerBound && memoryAddress <= higherBound; 278 | if (memoryAddressIsAcceptable) 279 | { 280 | break; 281 | } 282 | else 283 | { 284 | MEMORY_BASIC_INFORMATION info; 285 | VirtualQuery((void*)memoryAddress, &info, sizeof(MEMORY_BASIC_INFORMATION)); 286 | i += info.RegionSize; 287 | VirtualFree((void*)memoryAddress, 0, MEM_RELEASE); 288 | } 289 | } 290 | else 291 | { 292 | size_t arbitraryIncrement = 10000; 293 | i += arbitraryIncrement; 294 | } 295 | } 296 | 297 | if (memoryAddress == NULL) 298 | { 299 | logger.Log( 300 | "Failed to allocate %i bytes of memory. Origin: %p, lower: %p, higher: %p, attempts: %i", 301 | numBytes, 302 | origin, 303 | lowerBound, 304 | higherBound, 305 | numAttempts); 306 | } 307 | else 308 | { 309 | logger.Log("Allocated %i bytes of memory at %p", numBytes, memoryAddress); 310 | MemSet(memoryAddress, 0x90, numBytes); 311 | } 312 | 313 | return memoryAddress; 314 | } 315 | 316 | static bool IsRelativeNearJumpPresentAtAddress(uintptr_t address) 317 | { 318 | std::vector buffer(1, 0x90); 319 | std::vector assemblyRelativeNearJumpByte = { 0xe9 }; 320 | MemCopy((uintptr_t)&buffer[0], address, 1); 321 | if (buffer == assemblyRelativeNearJumpByte) 322 | { 323 | return true; 324 | }; 325 | return false; 326 | } 327 | 328 | static bool IsAbsoluteIndirectNearJumpPresentAtAddress(uintptr_t address) 329 | { 330 | std::vector buffer(3, 0x90); 331 | std::vector absoluteIndirectNearJumpBytes = { 0x48, 0xff, 0x25 }; 332 | MemCopy((uintptr_t)&buffer[0], address, 3); 333 | if (buffer == absoluteIndirectNearJumpBytes) 334 | { 335 | return true; 336 | } 337 | return false; 338 | } 339 | 340 | static bool IsAbsoluteDirectFarJumpPresentAtAddress(uintptr_t address) 341 | { 342 | std::vector absoluteDirectFarJump = { 0xff, 0x25, 0x00, 0x00, 0x00, 0x00 }; 343 | std::vector buffer(absoluteDirectFarJump.size(), 0x90); 344 | MemCopy((uintptr_t)&buffer[0], address, buffer.size()); 345 | if (buffer == absoluteDirectFarJump) 346 | { 347 | return true; 348 | } 349 | return false; 350 | } 351 | 352 | static bool IsAddressHooked(uintptr_t address) 353 | { 354 | if ( 355 | IsRelativeNearJumpPresentAtAddress(address) 356 | || IsAbsoluteIndirectNearJumpPresentAtAddress(address) 357 | || IsAbsoluteDirectFarJumpPresentAtAddress(address)) 358 | { 359 | return true; 360 | } 361 | return false; 362 | } 363 | 364 | static size_t CalculateRequiredAsmClearance(uintptr_t address, size_t minimumClearance) 365 | { 366 | size_t maximumAmountOfBytesToCheck = 30; 367 | std::vector bytesBuffer(maximumAmountOfBytesToCheck, 0x90); 368 | MemCopy((uintptr_t)&bytesBuffer[0], address, maximumAmountOfBytesToCheck); 369 | 370 | if (IsAbsoluteDirectFarJumpPresentAtAddress(address)) 371 | { 372 | return 14; 373 | } 374 | 375 | size_t requiredClearance = 0; 376 | for (size_t byteCount = 0; byteCount < maximumAmountOfBytesToCheck;) 377 | { 378 | size_t instructionSize = nmd_x86_ldisasm( 379 | &bytesBuffer[byteCount], 380 | maximumAmountOfBytesToCheck - byteCount, 381 | NMD_X86_MODE_64); 382 | 383 | if (instructionSize <= 0) 384 | { 385 | logger.Log("Instruction invalid, could not check length!"); 386 | return minimumClearance; 387 | } 388 | 389 | if (byteCount >= minimumClearance) 390 | { 391 | requiredClearance = byteCount; 392 | break; 393 | } 394 | 395 | byteCount += instructionSize; 396 | } 397 | 398 | return requiredClearance; 399 | } 400 | 401 | static uintptr_t CalculateAbsoluteDestinationFromRelativeNearJumpAtAddress(uintptr_t relativeNearJumpMemoryLocation) 402 | { 403 | int32_t offset = 0; 404 | MemCopy((uintptr_t)&offset, relativeNearJumpMemoryLocation + 1, 4); 405 | uintptr_t absoluteAddress = relativeNearJumpMemoryLocation + 5 + offset; 406 | return absoluteAddress; 407 | } 408 | 409 | static uintptr_t CalculateAbsoluteDestinationFromAbsoluteIndirectNearJumpAtAddress(uintptr_t absoluteIndirectNearJumpMemoryLocation) 410 | { 411 | int32_t offset = 0; 412 | MemCopy((uintptr_t)&offset, absoluteIndirectNearJumpMemoryLocation + 3, 4); 413 | uintptr_t memoryContainingAbsoluteAddress = absoluteIndirectNearJumpMemoryLocation + 7 + offset; 414 | uintptr_t absoluteAddress = *(uintptr_t*)memoryContainingAbsoluteAddress; 415 | return absoluteAddress; 416 | } 417 | 418 | static uintptr_t CalculateAbsoluteDestinationFromAbsoluteDirectFarJumpAtAddress(uintptr_t absoluteDirectFarJumpMemoryLocation) 419 | { 420 | uintptr_t absoluteAddress = 0; 421 | MemCopy((uintptr_t)&absoluteAddress, absoluteDirectFarJumpMemoryLocation + 6, 8); 422 | return absoluteAddress; 423 | } 424 | 425 | static int32_t CalculateRelativeDisplacementForRelativeJump(uintptr_t relativeJumpAddress, uintptr_t destinationAddress) 426 | { 427 | return -int32_t(relativeJumpAddress + 5 - destinationAddress); 428 | } 429 | 430 | // Places a 14-byte absolutely addressed jump from A to B. 431 | // Add extra clearance when the jump doesn't fit cleanly. 432 | static void PlaceAbsoluteJump(uintptr_t address, uintptr_t destinationAddress, size_t clearance = 14) 433 | { 434 | MemSet(address, 0x90, clearance); 435 | unsigned char absoluteJumpBytes[6] = { 0xff, 0x25, 0x00, 0x00, 0x00, 0x00}; 436 | MemCopy(address, (uintptr_t)&absoluteJumpBytes[0], 6); 437 | MemCopy(address + 6, (uintptr_t)&destinationAddress, 8); 438 | logger.Log("Created absolute jump from %p to %p with a clearance of %i", address, destinationAddress, clearance); 439 | } 440 | 441 | // Places a 5-byte relatively addressed jump from A to B. 442 | // Add extra clearance when the jump doesn't fit cleanly. 443 | static void PlaceRelativeJump(uintptr_t address, uintptr_t destinationAddress, size_t clearance = 5) 444 | { 445 | MemSet(address, 0x90, clearance); 446 | unsigned char relativeJumpBytes[5] = { 0xe9, 0x00, 0x00, 0x00, 0x00 }; 447 | MemCopy(address, (uintptr_t)&relativeJumpBytes[0], 5); 448 | int32_t relativeAddress = CalculateRelativeDisplacementForRelativeJump(address, destinationAddress); 449 | MemCopy((address + 1), (uintptr_t)&relativeAddress, 4); 450 | logger.Log("Created relative jump from %p to %p with a clearance of %i", address, destinationAddress, clearance); 451 | } 452 | 453 | static std::string ConvertVectorOfBytesToStringOfHex(std::vector bytes) 454 | { 455 | std::string hexString = ""; 456 | for (auto byte : bytes) 457 | { 458 | std::stringstream stream; 459 | std::string byteAsHex = ""; 460 | stream << std::hex << std::setfill('0') << std::setw(2) << (int)byte; 461 | byteAsHex = stream.str(); 462 | hexString.append("0x" + byteAsHex + " "); 463 | } 464 | return hexString; 465 | } 466 | 467 | static void PrintBytesAtAddress(uintptr_t address, size_t numBytes) 468 | { 469 | std::vector bytesBuffer(numBytes, 0x90); 470 | MemCopy((uintptr_t)&bytesBuffer[0], address, bytesBuffer.size()); 471 | std::string hexString = ConvertVectorOfBytesToStringOfHex(bytesBuffer); 472 | logger.Log("Existing bytes: %s", hexString.c_str()); 473 | } 474 | 475 | // Place a trampoline hook from A to B while taking third-party hooks into consideration. 476 | // Add extra clearance when the jump doesn't fit cleanly. 477 | static void PlaceHook(uintptr_t addressToHook, uintptr_t destinationAddress, uintptr_t* returnAddress) 478 | { 479 | logger.Log("Hooking..."); 480 | 481 | // Most overlays don't care if we overwrite the 0xE9 jump and place it somewhere else, but MSI Afterburner does. 482 | // So instead of overwriting jumps we follow them and place our jump at the final destination. 483 | int maxFollowAttempts = 50; 484 | int countFollowAttempts = 0; 485 | while (IsAddressHooked(addressToHook)) 486 | { 487 | if (IsRelativeNearJumpPresentAtAddress(addressToHook)) 488 | { 489 | addressToHook = CalculateAbsoluteDestinationFromRelativeNearJumpAtAddress(addressToHook); 490 | } 491 | else if (IsAbsoluteIndirectNearJumpPresentAtAddress(addressToHook)) 492 | { 493 | addressToHook = CalculateAbsoluteDestinationFromAbsoluteIndirectNearJumpAtAddress(addressToHook); 494 | } 495 | else if (IsAbsoluteDirectFarJumpPresentAtAddress(addressToHook)) 496 | { 497 | //addressToHook = CalculateAbsoluteDestinationFromAbsoluteDirectFarJumpAtAddress(addressToHook); 498 | } 499 | 500 | countFollowAttempts++; 501 | if (countFollowAttempts >= maxFollowAttempts) 502 | { 503 | break; 504 | } 505 | } 506 | 507 | PrintBytesAtAddress(addressToHook, 20); 508 | 509 | const size_t assemblyShortJumpSize = 5; 510 | const size_t assemblyFarJumpSize = 14; 511 | size_t trampolineSize = 0; 512 | uintptr_t trampolineAddress = 0; 513 | uintptr_t trampolineReturnAddress = 0; 514 | size_t thirdPartyHookProtectionBuffer = assemblyFarJumpSize; 515 | 516 | size_t clearance = CalculateRequiredAsmClearance(addressToHook, assemblyShortJumpSize); 517 | 518 | trampolineSize = assemblyFarJumpSize * 3 + clearance + thirdPartyHookProtectionBuffer; 519 | 520 | #ifdef _WIN64 521 | trampolineAddress = AllocateMemoryWithin32BitRange(trampolineSize, addressToHook + assemblyShortJumpSize); 522 | #else 523 | trampolineAddress = AllocateMemory(trampolineSize); 524 | #endif 525 | 526 | trampolineReturnAddress = addressToHook + clearance; 527 | MemCopy(trampolineAddress + assemblyFarJumpSize + thirdPartyHookProtectionBuffer, addressToHook, clearance); 528 | 529 | HookInformation hookInfo; 530 | hookInfo.originalBytes = std::vector(clearance); 531 | hookInfo.trampolineInstructionsAddress = trampolineAddress + assemblyFarJumpSize + thirdPartyHookProtectionBuffer; 532 | InfoBufferForHookedAddresses[addressToHook] = hookInfo; 533 | MemCopy( 534 | (uintptr_t)&InfoBufferForHookedAddresses[addressToHook].originalBytes[0], 535 | trampolineAddress + assemblyFarJumpSize + thirdPartyHookProtectionBuffer, 536 | InfoBufferForHookedAddresses[addressToHook].originalBytes.size()); 537 | #ifdef _WIN64 538 | PlaceAbsoluteJump(trampolineAddress + thirdPartyHookProtectionBuffer, destinationAddress); 539 | PlaceAbsoluteJump(trampolineAddress + trampolineSize - assemblyFarJumpSize, trampolineReturnAddress); 540 | #else 541 | PlaceRelativeJump(trampolineAddress + thirdPartyHookProtectionBuffer, destinationAddress); 542 | PlaceRelativeJump(trampolineAddress + trampolineSize - assemblyFarJumpSize, trampolineReturnAddress); 543 | #endif 544 | *returnAddress = trampolineAddress + assemblyFarJumpSize + thirdPartyHookProtectionBuffer; 545 | PlaceRelativeJump(addressToHook, trampolineAddress, clearance); 546 | } 547 | 548 | static void Unhook(uintptr_t hookedAddress) 549 | { 550 | auto search = InfoBufferForHookedAddresses.find(hookedAddress); 551 | if (search != InfoBufferForHookedAddresses.end()) 552 | { 553 | MemSet( 554 | InfoBufferForHookedAddresses[hookedAddress].trampolineInstructionsAddress, 555 | 0x90, 556 | InfoBufferForHookedAddresses[hookedAddress].originalBytes.size()); 557 | MemCopy( 558 | hookedAddress, 559 | (uintptr_t)&InfoBufferForHookedAddresses[hookedAddress].originalBytes[0], 560 | InfoBufferForHookedAddresses[hookedAddress].originalBytes.size()); 561 | logger.Log("Removed hook from %p", hookedAddress); 562 | } 563 | } 564 | 565 | static uintptr_t ReadPointerChain(std::vector pointerOffsets) 566 | { 567 | DWORD processId = GetCurrentProcessId(); 568 | uintptr_t baseAddress = GetProcessBaseAddress(processId); 569 | uintptr_t pointer = baseAddress; 570 | for (size_t i = 0; i < pointerOffsets.size(); i++) 571 | { 572 | pointer += pointerOffsets[i]; 573 | if (pointerOffsets[i] != pointerOffsets.back()) 574 | { 575 | MemCopy((uintptr_t)&pointer, pointer, sizeof(uintptr_t)); 576 | } 577 | if (pointer == 0) 578 | { 579 | return 0; 580 | } 581 | } 582 | return pointer; 583 | } 584 | } -------------------------------------------------------------------------------- /include/OverlayFramework.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "Logger.h" 15 | 16 | #undef DrawText 17 | 18 | namespace OF 19 | { 20 | static struct Box 21 | { 22 | int x = 0; 23 | int y = 0; 24 | float z = 0.0f; 25 | int width = 0; 26 | int height = 0; 27 | bool pressed = false; 28 | bool clicked = false; 29 | bool hover = false; 30 | bool draggable = true; 31 | bool hasBeenRendered = false; 32 | Box* parentBox = nullptr; 33 | }; 34 | 35 | static Logger logger{ "OverlayFramework" }; 36 | static HWND ofWindow = 0; 37 | static int ofWindowWidth = 0; 38 | static int ofWindowHeight = 0; 39 | static std::vector ofBoxes = std::vector(); 40 | static constexpr unsigned char UNBOUND = 0x07; 41 | 42 | static Microsoft::WRL::ComPtr ofDevice = nullptr; 43 | static std::shared_ptr ofSpriteBatch = nullptr; 44 | static std::vector> ofTextures = std::vector>(); 45 | static std::vector> ofFonts = std::vector>(); 46 | static std::shared_ptr ofActiveFont = nullptr; 47 | 48 | // Gives the framework the required DirectX objects to draw 49 | static void InitFramework( 50 | Microsoft::WRL::ComPtr device, 51 | std::shared_ptr spriteBatch, 52 | HWND window) 53 | { 54 | logger.Log("Initialized"); 55 | ofDevice = device; 56 | ofSpriteBatch = spriteBatch; 57 | ofWindow = window; 58 | 59 | RECT hwndRect; 60 | GetClientRect(ofWindow, &hwndRect); 61 | ofWindowWidth = hwndRect.right - hwndRect.left; 62 | ofWindowHeight = hwndRect.bottom - hwndRect.top; 63 | } 64 | 65 | static int MapIntToRange( 66 | int number, 67 | int inputStart, 68 | int inputEnd, 69 | int outputStart, 70 | int outputEnd) 71 | { 72 | return outputStart + (outputEnd - outputStart) * (number - inputStart) / (inputEnd - inputStart); 73 | } 74 | 75 | static float MapFloatToRange( 76 | float number, 77 | float inputStart, 78 | float inputEnd, 79 | float outputStart, 80 | float outputEnd) 81 | { 82 | return outputStart + (outputEnd - outputStart) * (number - inputStart) / (inputEnd - inputStart); 83 | } 84 | 85 | static int LoadTexture(std::string filepath) 86 | { 87 | if (ofDevice.Get() == nullptr) 88 | { 89 | logger.Log("Could not load texture, ofDevice is nullptr! Run InitFramework before attempting to load textures!"); 90 | return -1; 91 | } 92 | 93 | if (ofTextures.size() == 0 && filepath != "blank") { 94 | if (LoadTexture("blank") != 0) 95 | { 96 | return -1; 97 | } 98 | } 99 | else if (filepath == "blank") 100 | { 101 | filepath = "hook_textures\\blank.jpg"; 102 | } 103 | 104 | logger.Log("Loading texture: %s", filepath.c_str()); 105 | 106 | std::wstring wideString(filepath.length(), ' '); 107 | std::copy(filepath.begin(), filepath.end(), wideString.begin()); 108 | std::fstream file = std::fstream(filepath); 109 | if (file.fail()) 110 | { 111 | logger.Log("Texture loading failed, file not found: %s", filepath.c_str()); 112 | file.close(); 113 | return -1; 114 | } 115 | file.close(); 116 | 117 | HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); 118 | if (FAILED(hr)) 119 | { 120 | logger.Log("Error %#010x when initializing the COM library", hr); 121 | } 122 | else 123 | { 124 | logger.Log("Successfully initialized the COM library"); 125 | } 126 | 127 | Microsoft::WRL::ComPtr texture = nullptr; 128 | HRESULT texResult = DirectX::CreateWICTextureFromFile(ofDevice.Get(), wideString.c_str(), nullptr, texture.GetAddressOf()); 129 | 130 | _com_error texErr(texResult); 131 | logger.Log("Texture HRESULT: %s", texErr.ErrorMessage()); 132 | if (FAILED(texResult)) 133 | { 134 | logger.Log("Texture loading failed: %s", filepath.c_str()); 135 | return -1; 136 | } 137 | 138 | ofTextures.push_back(texture); 139 | return ofTextures.size() - 1; 140 | } 141 | 142 | static int LoadFont(std::string filepath) 143 | { 144 | if (ofDevice.Get() == nullptr) 145 | { 146 | logger.Log("Could not load font, ofDevice is nullptr! Run InitFramework before attempting to load fonts!"); 147 | return -1; 148 | } 149 | 150 | logger.Log("Loading font: %s", filepath.c_str()); 151 | 152 | std::fstream file = std::fstream(filepath); 153 | std::wstring wideString(filepath.length(), ' '); 154 | std::copy(filepath.begin(), filepath.end(), wideString.begin()); 155 | if (file.fail()) 156 | { 157 | logger.Log("Font loading failed: %s", filepath.c_str()); 158 | file.close(); 159 | return -1; 160 | } 161 | 162 | file.close(); 163 | 164 | logger.Log("Font was loaded successfully"); 165 | ofFonts.push_back(std::make_shared(ofDevice.Get(), wideString.c_str())); 166 | 167 | return ofFonts.size() - 1; 168 | } 169 | 170 | static void SetFont(int font) 171 | { 172 | if (font > ofFonts.size() - 1 || font < 0) 173 | { 174 | logger.Log("Attempted to set invalid font!"); 175 | return; 176 | } 177 | 178 | ofActiveFont = ofFonts[font]; 179 | } 180 | 181 | static void PlaceOnTop(Box* box) 182 | { 183 | static std::vector ofBoxOrder = std::vector(); 184 | size_t boxIndex = 0; 185 | for (size_t i = 0; i < ofBoxes.size(); i++) 186 | { 187 | if (ofBoxes[i] == box) 188 | { 189 | boxIndex = i; 190 | break; 191 | } 192 | } 193 | 194 | ofBoxOrder.push_back(boxIndex); 195 | for (size_t i = 0; i < ofBoxOrder.size() - 1; i++) 196 | { 197 | if (ofBoxes[ofBoxOrder[i]] == ofBoxes[ofBoxOrder.back()]) 198 | { 199 | ofBoxOrder.erase(ofBoxOrder.begin() + i); 200 | } 201 | } 202 | 203 | for (float i = 0; i < ofBoxOrder.size(); i++) 204 | { 205 | ofBoxes[ofBoxOrder[i]]->z = 1.0f / (1 + (i / 1000)); 206 | } 207 | } 208 | 209 | static POINT GetAbsolutePosition(Box* box) 210 | { 211 | if (box == nullptr) 212 | { 213 | return { 0, 0 }; 214 | } 215 | 216 | POINT absolutePosition = { box->x, box->y }; 217 | Box* parentBox = box->parentBox; 218 | while (parentBox != nullptr) 219 | { 220 | if (parentBox->parentBox == box) 221 | { 222 | break; 223 | } 224 | 225 | absolutePosition.x += parentBox->x; 226 | absolutePosition.y += parentBox->y; 227 | 228 | parentBox = parentBox->parentBox; 229 | } 230 | 231 | return absolutePosition; 232 | } 233 | 234 | static Box* CreateBox(Box* parentBox, int x, int y, int width, int height) 235 | { 236 | Box* box = new Box; 237 | box->x = x; 238 | box->y = y; 239 | box->width = width; 240 | box->height = height; 241 | box->parentBox = parentBox; 242 | 243 | if (parentBox != nullptr) 244 | { 245 | box->draggable = false; 246 | } 247 | 248 | ofBoxes.push_back(box); 249 | PlaceOnTop(box); 250 | return ofBoxes.back(); 251 | } 252 | 253 | static Box* CreateBox(int x, int y, int width, int height) 254 | { 255 | return CreateBox(nullptr, x, y, width, height); 256 | } 257 | 258 | static void _DrawBox(Box* box, DirectX::XMVECTOR color, int textureID) 259 | { 260 | static bool ofFailedToLoadBlank = false; 261 | 262 | if (box == nullptr) 263 | { 264 | logger.Log("Attempted to render a nullptr Box!"); 265 | return; 266 | } 267 | 268 | if (ofSpriteBatch == nullptr) 269 | { 270 | logger.Log("Attempted to render with ofSpriteBatch as nullptr! Run InitFramework before attempting to draw!"); 271 | return; 272 | } 273 | 274 | if (ofTextures.size() < 1) 275 | { 276 | if (ofFailedToLoadBlank == false) 277 | { 278 | if (LoadTexture("blank") != 0) 279 | { 280 | ofFailedToLoadBlank = true; 281 | return; 282 | } 283 | } 284 | else 285 | { 286 | return; 287 | } 288 | } 289 | 290 | if (textureID < 0 || textureID > ofTextures.size() - 1) 291 | { 292 | logger.Log("'%i' is an invalid texture ID!", textureID); 293 | return; 294 | } 295 | 296 | POINT position = GetAbsolutePosition(box); 297 | 298 | RECT rect; 299 | rect.top = position.y; 300 | rect.left = position.x; 301 | rect.bottom = position.y + box->height; 302 | rect.right = position.x + box->width; 303 | 304 | box->hasBeenRendered = true; 305 | ofSpriteBatch->Draw(ofTextures[textureID].Get(), rect, nullptr, color, 0.0f, DirectX::XMFLOAT2(0.0f, 0.0f), DirectX::SpriteEffects_None, box->z); 306 | } 307 | 308 | static void DrawBox(Box* box, int textureID) 309 | { 310 | _DrawBox(box, { 1.0f, 1.0f, 1.0f, 1.0f }, textureID); 311 | } 312 | 313 | static void DrawBox(Box* box, int r, int g, int b, int a = 255) 314 | { 315 | float _r = MapFloatToRange((float)r, 0.0f, 255.0f, 0.0f, 1.0f); 316 | float _g = MapFloatToRange((float)g, 0.0f, 255.0f, 0.0f, 1.0f); 317 | float _b = MapFloatToRange((float)b, 0.0f, 255.0f, 0.0f, 1.0f); 318 | float _a = MapFloatToRange((float)a, 0.0f, 255.0f, 0.0f, 1.0f); 319 | _DrawBox(box, { _r, _g, _b, _a }, 0); 320 | } 321 | 322 | static void DrawText( 323 | Box* box, 324 | std::string text, 325 | int offsetX = 0, 326 | int offsetY = 0, 327 | float scale = 1.0f, 328 | int r = 255, 329 | int g = 255, 330 | int b = 255, 331 | int a = 255, 332 | float rotation = 0.0f) 333 | { 334 | if (ofActiveFont == nullptr) 335 | { 336 | logger.Log("Attempted to render text with an invalid font, make sure to run SetFont first!"); 337 | return; 338 | } 339 | 340 | POINT position = GetAbsolutePosition(box); 341 | 342 | DirectX::XMFLOAT2 textPos = DirectX::XMFLOAT2 343 | ( 344 | position.x + offsetX, 345 | position.y + offsetY 346 | ); 347 | 348 | float _r = MapFloatToRange((float)r, 0.0f, 255.0f, 0.0f, 1.0f); 349 | float _g = MapFloatToRange((float)g, 0.0f, 255.0f, 0.0f, 1.0f); 350 | float _b = MapFloatToRange((float)b, 0.0f, 255.0f, 0.0f, 1.0f); 351 | float _a = MapFloatToRange((float)a, 0.0f, 255.0f, 0.0f, 1.0f); 352 | 353 | ofActiveFont->DrawString( 354 | ofSpriteBatch.get(), 355 | text.c_str(), 356 | textPos, 357 | { _r, _g, _b, _a }, 358 | rotation, 359 | { 0.0f, 0.0f }, 360 | scale, 361 | DirectX::SpriteEffects_None, 362 | box->z); 363 | } 364 | 365 | static bool IsCursorInsideBox(POINT cursorPos, Box* box) 366 | { 367 | POINT position = GetAbsolutePosition(box); 368 | POINT boxSize = { box->width, box->height }; 369 | 370 | if (cursorPos.x < (position.x + boxSize.x) && cursorPos.x > position.x) 371 | { 372 | if (cursorPos.y < (position.y + boxSize.y) && cursorPos.y > position.y) 373 | { 374 | return true; 375 | } 376 | } 377 | 378 | return false; 379 | } 380 | 381 | static bool CheckHotkey(unsigned char key, unsigned char modifier = UNBOUND) 382 | { 383 | static std::vector notReleasedKeys; 384 | 385 | if (ofWindow != GetForegroundWindow()) 386 | { 387 | return false; 388 | } 389 | 390 | bool keyPressed = GetAsyncKeyState(key) & 0x8000; 391 | bool modifierPressed = GetAsyncKeyState(modifier) & 0x8000; 392 | 393 | if (key == UNBOUND) 394 | { 395 | return modifierPressed; 396 | } 397 | 398 | auto iterator = std::find(notReleasedKeys.begin(), notReleasedKeys.end(), key); 399 | bool keyNotReleased = iterator != notReleasedKeys.end(); 400 | 401 | if (keyPressed && keyNotReleased) 402 | { 403 | return false; 404 | } 405 | 406 | if(!keyPressed) 407 | { 408 | if (keyNotReleased) 409 | { 410 | notReleasedKeys.erase(iterator); 411 | } 412 | return false; 413 | } 414 | 415 | if (modifier != UNBOUND && !modifierPressed) 416 | { 417 | return false; 418 | } 419 | 420 | notReleasedKeys.push_back(key); 421 | return true; 422 | } 423 | 424 | static void CheckMouseEvents() 425 | { 426 | static int ofMouseX = 0, ofMouseY = 0; 427 | static int ofDeltaMouseX = 0, ofDeltaMouseY = 0; 428 | static bool ofMousePressed = false; 429 | static Box* ofClickedBox = nullptr; 430 | 431 | if (ofWindow == GetForegroundWindow()) 432 | { 433 | POINT cursorPos; 434 | GetCursorPos(&cursorPos); 435 | ScreenToClient(ofWindow, &cursorPos); 436 | 437 | ofDeltaMouseX = ofMouseX; 438 | ofDeltaMouseY = ofMouseY; 439 | ofMouseX = cursorPos.x; 440 | ofMouseY = cursorPos.y; 441 | ofDeltaMouseX = ofDeltaMouseX - ofMouseX; 442 | ofDeltaMouseY = ofDeltaMouseY - ofMouseY; 443 | 444 | if (ofClickedBox != nullptr) 445 | { 446 | if (ofClickedBox->clicked) 447 | { 448 | ofClickedBox->clicked = false; 449 | ofClickedBox = nullptr; 450 | } 451 | } 452 | 453 | Box* topMostBox = nullptr; 454 | for (size_t i = 0; i < ofBoxes.size(); i++) 455 | { 456 | Box* box = ofBoxes[i]; 457 | box->hover = false; 458 | 459 | if (!box->hasBeenRendered) 460 | { 461 | continue; 462 | } 463 | 464 | if (IsCursorInsideBox(cursorPos, box)) 465 | { 466 | if (topMostBox == nullptr || box->z < topMostBox->z) 467 | { 468 | topMostBox = box; 469 | } 470 | } 471 | } 472 | 473 | if (topMostBox != nullptr) 474 | { 475 | topMostBox->hover = true; 476 | } 477 | 478 | if (GetAsyncKeyState(VK_LBUTTON) & 0x8000) 479 | { 480 | ofMousePressed = true; 481 | 482 | if (topMostBox != nullptr) 483 | { 484 | ofClickedBox = topMostBox; 485 | ofClickedBox->pressed = true; 486 | } 487 | 488 | if (ofClickedBox != nullptr && ofClickedBox->draggable) 489 | { 490 | ofClickedBox->x -= ofDeltaMouseX; 491 | ofClickedBox->y -= ofDeltaMouseY; 492 | } 493 | } 494 | else 495 | { 496 | ofMousePressed = false; 497 | 498 | if (ofClickedBox != nullptr && IsCursorInsideBox(cursorPos, ofClickedBox)) 499 | { 500 | if (ofClickedBox->parentBox != nullptr) 501 | { 502 | PlaceOnTop(ofClickedBox->parentBox); 503 | 504 | for (size_t i = 0; i < ofBoxes.size(); i++) 505 | { 506 | if (ofClickedBox->parentBox == ofBoxes[i]->parentBox) 507 | { 508 | PlaceOnTop(ofBoxes[i]); 509 | } 510 | } 511 | } 512 | 513 | PlaceOnTop(ofClickedBox); 514 | 515 | for (size_t i = 0; i < ofBoxes.size(); i++) 516 | { 517 | if (ofBoxes[i]->parentBox == ofClickedBox) 518 | { 519 | PlaceOnTop(ofBoxes[i]); 520 | } 521 | } 522 | 523 | ofClickedBox->pressed = false; 524 | ofClickedBox->clicked = true; 525 | } 526 | } 527 | } 528 | 529 | for (auto box : ofBoxes) 530 | { 531 | box->hasBeenRendered = false; 532 | } 533 | } 534 | }; -------------------------------------------------------------------------------- /include/Renderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "ID3DRenderer.h" 18 | #include "IRenderCallback.h" 19 | #include "Logger.h" 20 | 21 | // D3D11 renderer with support for D3D12 using D3D11On12 22 | class Renderer : public ID3DRenderer 23 | { 24 | public: 25 | void OnPresent(IDXGISwapChain* pThis, UINT syncInterval, UINT flags); 26 | void OnResizeBuffers(IDXGISwapChain* pThis, UINT bufferCount, UINT width, UINT height, DXGI_FORMAT newFormat, UINT swapChainFlags); 27 | void SetDrawExampleTriangle(bool doDraw); 28 | void AddRenderCallback(IRenderCallback* object); 29 | void SetCommandQueue(ID3D12CommandQueue* commandQueue); 30 | void SetGetCommandQueueCallback(void (*callback)()); 31 | 32 | private: 33 | Logger logger{"Renderer"}; 34 | HWND window = 0; 35 | 36 | IRenderCallback* callbackObject = nullptr; 37 | void (*callbackGetCommandQueue)(); 38 | bool mustInitializeD3DResources = true; 39 | bool firstTimeInitPerformed = false; 40 | bool isDeviceRetrieved = false; 41 | bool isRunningD3D12 = false; 42 | bool getCommandQueueCalled = false; 43 | bool drawExamples = false; 44 | bool examplesLoaded = false; 45 | bool callbackInitialized = false; 46 | int windowWidth = 0; 47 | int windowHeight = 0; 48 | UINT bufferIndex = 0; 49 | UINT bufferCount = 0; 50 | 51 | Microsoft::WRL::ComPtr d3d12Device = nullptr; 52 | Microsoft::WRL::ComPtr d3d11Context = nullptr; 53 | Microsoft::WRL::ComPtr d3d11Device = nullptr; 54 | Microsoft::WRL::ComPtr commandQueue = nullptr; 55 | Microsoft::WRL::ComPtr d3d11On12Device = nullptr; 56 | std::vector> d3d12RenderTargets; 57 | std::vector> d3d11WrappedBackBuffers; 58 | std::vector> d3d11RenderTargetViews; 59 | Microsoft::WRL::ComPtr swapChain = nullptr; 60 | Microsoft::WRL::ComPtr swapChain3 = nullptr; 61 | std::shared_ptr spriteBatch = nullptr; 62 | std::shared_ptr exampleFont = nullptr; 63 | DXGI_SWAP_CHAIN_DESC swapChainDesc; 64 | D3D11_VIEWPORT viewport; 65 | 66 | // Load the shaders from disk at compile time into a string. 67 | const char* shaderData = { 68 | #include "Shaders.hlsl" 69 | }; 70 | 71 | Microsoft::WRL::ComPtr vertexBuffer = nullptr; 72 | Microsoft::WRL::ComPtr indexBuffer = nullptr; 73 | Microsoft::WRL::ComPtr vertexShader = nullptr; 74 | Microsoft::WRL::ComPtr pixelShaderTextures = nullptr; 75 | Microsoft::WRL::ComPtr pixelShader = nullptr; 76 | Microsoft::WRL::ComPtr inputLayout = nullptr; 77 | Microsoft::WRL::ComPtr samplerState = nullptr; 78 | Microsoft::WRL::ComPtr constantBuffer = nullptr; 79 | Microsoft::WRL::ComPtr rasterizerState = nullptr; 80 | Microsoft::WRL::ComPtr depthStencilState = nullptr; 81 | Microsoft::WRL::ComPtr depthStencilBuffer = nullptr; 82 | Microsoft::WRL::ComPtr depthStencilView = nullptr; 83 | 84 | DirectX::XMVECTOR trianglePos = { 0.0f, 0.0f, -5.0f }; 85 | DirectX::XMFLOAT3 triangleScale = DirectX::XMFLOAT3(0.7f, 0.7f, 0.7f); 86 | DirectX::XMFLOAT3 triangleNdc = DirectX::XMFLOAT3(0.0f, 0.0f, 0.0f); 87 | unsigned int triangleNumIndices = 0; 88 | float triangleSpeed = 0.003f; 89 | float triangleVelX = triangleSpeed; 90 | float triangleVelY = -triangleSpeed; 91 | float triangleRotX = 0; 92 | float triangleRotY = 0; 93 | float triangleRotZ = 0; 94 | float triangleCounter = 0; 95 | 96 | struct Vertex 97 | { 98 | DirectX::XMFLOAT3 pos; 99 | DirectX::XMFLOAT4 color; 100 | DirectX::XMFLOAT2 texCoord; 101 | }; 102 | 103 | struct ConstantBufferData 104 | { 105 | DirectX::XMMATRIX wvp = DirectX::XMMatrixIdentity(); 106 | } 107 | constantBufferData; 108 | 109 | bool InitD3DResources(IDXGISwapChain* swapChain); 110 | bool RetrieveD3DDeviceFromSwapChain(); 111 | void GetSwapChainDescription(); 112 | void GetBufferCount(); 113 | void GetSwapchainWindowInfo(); 114 | void CreateViewport(); 115 | void InitD3D(); 116 | void InitD3D11(); 117 | void CreateD3D11Context(); 118 | void CreateSpriteBatch(); 119 | void CreateD3D11RenderTargetView(); 120 | void InitD3D12(); 121 | void CreateD3D11On12Device(); 122 | void CreateD3D12Buffers(); 123 | Microsoft::WRL::ComPtr CreateD3D12RtvHeap(); 124 | void CreateD3D12RenderTargetView(UINT bufferIndex, D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle); 125 | void CreateD3D11WrappedBackBuffer(UINT bufferIndex); 126 | void CreateD3D11RenderTargetViewWithWrappedBackBuffer(UINT bufferIndex); 127 | bool WaitForCommandQueueIfRunningD3D12(); 128 | void Render(); 129 | void PreRender(); 130 | void RenderCallbacks(); 131 | void PostRender(); 132 | void CreatePipeline(); 133 | Microsoft::WRL::ComPtr LoadShader(const char* shaderData, std::string targetShaderVersion, std::string shaderEntry); 134 | void CreateExampleTriangle(); 135 | void CreateExampleFont(); 136 | void DrawExampleTriangle(); 137 | void DrawExampleText(); 138 | void ReleaseViewsBuffersAndContext(); 139 | bool CheckSuccess(HRESULT hr); 140 | }; -------------------------------------------------------------------------------- /overlays/Example/Example.cpp: -------------------------------------------------------------------------------- 1 | #include "Example.h" 2 | 3 | using namespace OF; 4 | 5 | void Example::Setup() 6 | { 7 | InitFramework(device, spriteBatch, window); 8 | box = CreateBox(100, 100, 100, 100); 9 | } 10 | 11 | void Example::Render() 12 | { 13 | CheckMouseEvents(); 14 | DrawBox(box, 255, 0, 0, 255); 15 | } -------------------------------------------------------------------------------- /overlays/Example/Example.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "IRenderCallback.h" 4 | #include "OverlayFramework.h" 5 | 6 | class Example : public IRenderCallback 7 | { 8 | public: 9 | void Setup(); 10 | void Render(); 11 | 12 | private: 13 | OF::Box* box; 14 | }; -------------------------------------------------------------------------------- /overlays/PauseTheGame/PauseTheGame.cpp: -------------------------------------------------------------------------------- 1 | #include "PauseTheGame.h" 2 | 3 | using namespace OF; 4 | 5 | void PauseTheGame::Setup() 6 | { 7 | InitFramework(device, spriteBatch, window); 8 | ReadConfigFile(&keybind); 9 | pauseWindow = CreateBox(ofWindowWidth / 2 - 200, ofWindowHeight / 2 - 100, 400, 200); 10 | topBar = CreateBox(pauseWindow, 0, 0, pauseWindow->width, 7); 11 | bottomBar = CreateBox(pauseWindow, 0, pauseWindow->height, pauseWindow->width, 7); 12 | font = LoadFont("hook_fonts\\OpenSans-22.spritefont"); 13 | barTexture = LoadTexture("hook_textures\\bar.png"); 14 | rotatedBarTexture = LoadTexture("hook_textures\\bar_rotated.png"); 15 | SetFont(font); 16 | } 17 | 18 | void PauseTheGame::Render() 19 | { 20 | while (gamePaused) 21 | { 22 | if (MsgWaitForMultipleObjects(0, nullptr, FALSE, 100, QS_ALLINPUT) == WAIT_OBJECT_0) 23 | { 24 | if (CheckHotkey(keybind)) 25 | { 26 | gamePaused = false; 27 | return; 28 | } 29 | MSG msg; 30 | if (GetMessage(&msg, NULL, 0, 0) != -1) 31 | { 32 | TranslateMessage(&msg); 33 | DispatchMessage(&msg); 34 | } 35 | } 36 | } 37 | 38 | if (CheckHotkey(keybind)) 39 | { 40 | gamePaused = true; 41 | DrawBox(pauseWindow, 0, 0, 0, 240); 42 | DrawBox(topBar, barTexture); 43 | DrawBox(bottomBar, rotatedBarTexture); 44 | DrawText(pauseWindow, "Game paused.", 100, 80); 45 | } 46 | } 47 | 48 | void PauseTheGame::ReadConfigFile(unsigned int* keybind) 49 | { 50 | configFile.open(configFileName, std::fstream::in); 51 | if (configFile.is_open()) 52 | { 53 | std::string line = ""; 54 | getline(configFile, line); 55 | if (line.length() < 3) 56 | { 57 | *keybind = 'P'; 58 | } 59 | else 60 | { 61 | std::stringstream stringStream(line.substr(2, line.length())); 62 | logger.Log("Read keybind line: %s", line); 63 | stringStream >> std::hex >> *keybind; 64 | logger.Log("Keybind is: 0x%x", *keybind); 65 | } 66 | configFile.close(); 67 | } 68 | else 69 | { 70 | logger.Log("Using default keybind"); 71 | configFile.open(configFileName, std::fstream::out); 72 | if (configFile.is_open()) 73 | { 74 | configFile << "0x50"; 75 | configFile.close(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /overlays/PauseTheGame/PauseTheGame.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "IRenderCallback.h" 6 | #include "OverlayFramework.h" 7 | #include "Logger.h" 8 | 9 | class PauseTheGame : public IRenderCallback 10 | { 11 | public: 12 | void Setup(); 13 | void Render(); 14 | 15 | private: 16 | Logger logger{ "PauseTheGame" }; 17 | std::fstream configFile; 18 | std::string configFileName = "pause_keybind.txt"; 19 | unsigned int keybind = 'P'; 20 | OF::Box* pauseWindow = nullptr; 21 | OF::Box* topBar = nullptr; 22 | OF::Box* bottomBar = nullptr; 23 | int font = 0; 24 | int barTexture = 0; 25 | int rotatedBarTexture = 0; 26 | bool gamePaused = false; 27 | 28 | void ReadConfigFile(unsigned int* keybind); 29 | }; -------------------------------------------------------------------------------- /overlays/RiseDpsMeter/RiseDpsMeter.cpp: -------------------------------------------------------------------------------- 1 | #include "RiseDpsMeter.h" 2 | 3 | using namespace OF; 4 | 5 | void RiseDpsMeter::Setup() 6 | { 7 | InitFramework(device, spriteBatch, window); 8 | 9 | int defaultXPos = ofWindowWidth / 2; 10 | int defaultYPos = ofWindowHeight / 2; 11 | ReadConfigFile(&defaultXPos, &defaultYPos); 12 | 13 | dpsMeterWindow = CreateBox(defaultXPos, defaultYPos, 400, 180); 14 | dpsMeterWindowDivider = CreateBox(dpsMeterWindow, 23, dpsMeterWindow->height - 40, dpsMeterWindow->width - 46, 1); 15 | placeholderWindow = CreateBox(defaultXPos, defaultYPos, dpsMeterWindow->width, dpsMeterWindow->height); 16 | placeholderOkButton = CreateBox(placeholderWindow, placeholderWindow->width / 2 - 30, placeholderWindow->height - 40, 60, 30); 17 | placeholderOkButtonBorder = CreateBox(placeholderWindow, placeholderWindow->width / 2 - 27, placeholderWindow->height - 17, 55, 2); 18 | cornerWindow = CreateBox(0, 0, 50, 50); 19 | 20 | int columnWidth = 1; 21 | int numColumns = floor(((float)dpsMeterWindowDivider->width) / columnWidth) - (columnWidth * 2); 22 | for (int i = 0; i < numColumns; i++) 23 | { 24 | graphColumns.push_back(CreateBox(dpsMeterWindow, i * columnWidth + columnWidth + 25, dpsMeterWindow->height - 41, columnWidth, 0)); 25 | } 26 | 27 | font = LoadFont("hook_fonts\\OpenSans-22.spritefont"); 28 | SetFont(font); 29 | 30 | SetPointerOffsets(); 31 | } 32 | 33 | void RiseDpsMeter::Render() 34 | { 35 | CheckMouseEvents(); 36 | CheckHotkeys(); 37 | ReadMemory(); 38 | 39 | if (playerData[0].totalDamage != 0) 40 | { 41 | if (timerUpdateDps.Check()) 42 | { 43 | UpdateDamageStats(); 44 | UpdateGraph(); 45 | } 46 | 47 | if (!userDisabledDpsMeter) 48 | { 49 | DrawDpsMeter(); 50 | } 51 | } 52 | 53 | if (showPlaceholder) 54 | { 55 | DrawPlaceholder(); 56 | } 57 | 58 | if (showCornerText) 59 | { 60 | if (timerCornerText.Check()) 61 | { 62 | showCornerText = false; 63 | } 64 | DrawCornerText(); 65 | } 66 | } 67 | 68 | void RiseDpsMeter::DrawDpsMeter() 69 | { 70 | std::ostringstream dpsString; 71 | dpsString << std::fixed << std::setprecision(1) << playerData[0].averageDps; 72 | 73 | DrawBox(dpsMeterWindow, 0, 0, 0, 130); 74 | DrawBox(dpsMeterWindowDivider, 255, 255, 255, 255); 75 | 76 | for (auto box : graphColumns) 77 | { 78 | DrawBox(box, 125, 125, 255, 255); 79 | } 80 | 81 | DrawText(dpsMeterWindow, "DPS: " + dpsString.str(), 20, dpsMeterWindow->height - 32, 0.6f); 82 | DrawText(dpsMeterWindow, "High: " + std::to_string(playerData[0].mostDamageInOneSecond), 162, dpsMeterWindow->height - 32, 0.6f); 83 | DrawText(dpsMeterWindow, "Total: " + std::to_string(playerData[0].totalDamage), 287, dpsMeterWindow->height - 32, 0.6f); 84 | } 85 | 86 | void RiseDpsMeter::DrawPlaceholder() 87 | { 88 | DrawBox(placeholderWindow, 0, 0, 0, 170); 89 | DrawText(placeholderWindow, 90 | " (Hold Left Alt and drag to move)\n" 91 | "Move me to a suitable location then click ok.\n " 92 | "The window will auto-hide.", 93 | 23, 45, 0.6f); 94 | 95 | int color = 170; 96 | if (placeholderOkButton->hover) 97 | { 98 | color = 255; 99 | } 100 | 101 | DrawBox(placeholderOkButton, 0, 0, 0, 0); 102 | DrawBox(placeholderOkButtonBorder, color, color, color, 255); 103 | DrawText(placeholderOkButton, "Ok", 14, -3, 0.7f, color, color, color); 104 | 105 | if (placeholderOkButton->clicked) 106 | { 107 | showPlaceholder = false; 108 | dpsMeterWindow->x = placeholderWindow->x; 109 | dpsMeterWindow->y = placeholderWindow->y; 110 | WriteToConfigFile(); 111 | } 112 | } 113 | 114 | void RiseDpsMeter::DrawCornerText() 115 | { 116 | DrawText(cornerWindow, "RiseDpsMeter v2.0 loaded", 0, 0, 0.5f); 117 | } 118 | 119 | void RiseDpsMeter::UpdateDamageStats() 120 | { 121 | for (auto& player : playerData) 122 | { 123 | if (player.dpsHistory.size() > 21600) 124 | { 125 | player.dpsHistory.erase(player.dpsHistory.begin()); 126 | } 127 | 128 | player.dpsHistory.push_back(player.totalDamage - player.previousTotalDamage); 129 | player.previousTotalDamage = player.totalDamage; 130 | 131 | if (player.dpsHistory.back() > player.mostDamageInOneSecond) 132 | { 133 | player.mostDamageInOneSecond = player.dpsHistory.back(); 134 | } 135 | 136 | player.averageDps = 0; 137 | for (auto dps : player.dpsHistory) 138 | { 139 | player.averageDps += dps; 140 | } 141 | player.averageDps /= player.dpsHistory.size(); 142 | } 143 | } 144 | 145 | void RiseDpsMeter::UpdateGraph() 146 | { 147 | for (int i = graphColumns.size() - 1, 148 | j = playerData[0].dpsHistory.size() - 1; 149 | i > -1 && j > -1; 150 | i--, 151 | j--) 152 | { 153 | if (playerData[0].dpsHistory[j] != 0) 154 | { 155 | graphColumns[i]->height = MapIntToRange(playerData[0].dpsHistory[j], 0, playerData[0].mostDamageInOneSecond, 0, 130); 156 | graphColumns[i]->y = dpsMeterWindow->height - 41 - graphColumns[i]->height; 157 | } 158 | 159 | if (i != graphColumns.size() - 1 && j != playerData[0].dpsHistory.size() - 1 && playerData[0].dpsHistory[j + 1] == 0) 160 | { 161 | graphColumns[i + 1]->height = graphColumns[i]->height * 0.8; 162 | graphColumns[i + 1]->y = dpsMeterWindow->height - 41 - graphColumns[i + 1]->height; 163 | } 164 | } 165 | } 166 | 167 | void RiseDpsMeter::ReadMemory() 168 | { 169 | for (auto& player : playerData) 170 | { 171 | ReadPlayerDamage(&player); 172 | } 173 | } 174 | 175 | void RiseDpsMeter::ReadPlayerDamage(PlayerData* playerData) 176 | { 177 | if (playerData->timerVerifyDamageAddress.Check()) 178 | { 179 | FindDamageMemoryAddress(playerData); 180 | } 181 | 182 | if (playerData->damageAddressIsFound) 183 | { 184 | MemoryUtils::MemCopy( 185 | (uintptr_t)&playerData->totalDamage, 186 | playerData->damageMemoryAddress, 187 | sizeof(uint64_t)); 188 | } 189 | else 190 | { 191 | ResetState(); 192 | } 193 | } 194 | 195 | void RiseDpsMeter::FindDamageMemoryAddress(PlayerData* playerData) 196 | { 197 | if (playerData->damagePointerOffsets.size() > 0) 198 | { 199 | playerData->damageMemoryAddress = MemoryUtils::ReadPointerChain(playerData->damagePointerOffsets); 200 | } 201 | 202 | if (playerData->damageMemoryAddress == 0) 203 | { 204 | playerData->damageAddressIsFound = false; 205 | } 206 | else 207 | { 208 | playerData->damageAddressIsFound = true; 209 | } 210 | } 211 | 212 | void RiseDpsMeter::SetPointerOffsets() 213 | { 214 | playerData.resize(1); 215 | playerData[0].damagePointerOffsets = { 216 | 0x0f6e7550, 217 | 0xd8, 218 | 0x110, 219 | 0x20, 220 | 0x20, 221 | 0x138, 222 | 0x5c8, 223 | 0x18 224 | }; 225 | } 226 | 227 | void RiseDpsMeter::CheckHotkeys() 228 | { 229 | if (CheckHotkey('P', VK_LSHIFT)) 230 | { 231 | placeholderWindow->x = ofWindowWidth / 2; 232 | placeholderWindow->y = ofWindowHeight / 2; 233 | dpsMeterWindow->x = ofWindowWidth / 2; 234 | dpsMeterWindow->y = ofWindowHeight / 2; 235 | } 236 | else if (CheckHotkey('P')) 237 | { 238 | if (userDisabledDpsMeter) 239 | { 240 | userDisabledDpsMeter = false; 241 | } 242 | else 243 | { 244 | userDisabledDpsMeter = true; 245 | } 246 | } 247 | 248 | if (CheckHotkey(UNBOUND, VK_LMENU)) 249 | { 250 | dpsMeterWindow->draggable = true; 251 | placeholderWindow->draggable = true; 252 | } 253 | else 254 | { 255 | dpsMeterWindow->draggable = false; 256 | placeholderWindow->draggable = false; 257 | } 258 | } 259 | 260 | void RiseDpsMeter::WriteToConfigFile() 261 | { 262 | dpsMeterConfigFile.open(configFileName, std::fstream::out); 263 | if (dpsMeterConfigFile.is_open() && dpsMeterWindow != nullptr) 264 | { 265 | dpsMeterConfigFile << dpsMeterWindow->x << " " << dpsMeterWindow->y << std::endl; 266 | dpsMeterConfigFile.close(); 267 | } 268 | } 269 | 270 | void RiseDpsMeter::ReadConfigFile(int* x, int* y) 271 | { 272 | dpsMeterConfigFile.open(configFileName, std::fstream::in); 273 | if (dpsMeterConfigFile.is_open()) 274 | { 275 | std::string line = ""; 276 | getline(dpsMeterConfigFile, line); 277 | std::stringstream stringStream(line); 278 | std::istream_iterator begin(stringStream); 279 | std::istream_iterator end; 280 | std::vector values(begin, end); 281 | if (values.size() >= 2) 282 | { 283 | *x = std::stoi(values[0]); 284 | *y = std::stoi(values[1]); 285 | } 286 | dpsMeterConfigFile.close(); 287 | } 288 | else 289 | { 290 | showPlaceholder = true; 291 | } 292 | } 293 | 294 | void RiseDpsMeter::ResetState() 295 | { 296 | for (auto box : graphColumns) 297 | { 298 | box->height = 0; 299 | } 300 | for (auto& player : playerData) 301 | { 302 | player.Reset(); 303 | } 304 | timerUpdateDps.Reset(); 305 | } 306 | 307 | RiseDpsMeter::~RiseDpsMeter() 308 | { 309 | WriteToConfigFile(); 310 | } -------------------------------------------------------------------------------- /overlays/RiseDpsMeter/RiseDpsMeter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "IRenderCallback.h" 12 | #include "OverlayFramework.h" 13 | #include "MemoryUtils.h" 14 | 15 | class Timer 16 | { 17 | public: 18 | Timer(unsigned int millis) 19 | { 20 | interval = millis; 21 | } 22 | 23 | bool Check() 24 | { 25 | auto now = std::chrono::system_clock::now(); 26 | if (resetOnNextCheck) 27 | { 28 | lastExecutionTime = now; 29 | resetOnNextCheck = false; 30 | return false; 31 | } 32 | 33 | auto diff = std::chrono::duration_cast(now - lastExecutionTime); 34 | if (diff.count() >= interval) 35 | { 36 | lastExecutionTime = now; 37 | return true; 38 | } 39 | 40 | return false; 41 | } 42 | 43 | void Reset() 44 | { 45 | resetOnNextCheck = true; 46 | } 47 | private: 48 | unsigned int interval = 0; 49 | bool resetOnNextCheck = true; 50 | std::chrono::system_clock::time_point lastExecutionTime; 51 | }; 52 | 53 | class RiseDpsMeter : public IRenderCallback 54 | { 55 | public: 56 | void Setup(); 57 | void Render(); 58 | ~RiseDpsMeter(); 59 | 60 | private: 61 | struct PlayerData 62 | { 63 | uint64_t totalDamage = 0; 64 | uint64_t previousTotalDamage = 0; 65 | uint64_t mostDamageInOneSecond = 0; 66 | double averageDps = 0.0; 67 | uintptr_t damageMemoryAddress = 0; 68 | std::vector damagePointerOffsets; 69 | bool damageAddressIsFound = false; 70 | Timer timerVerifyDamageAddress{ 10000 }; 71 | std::vector dpsHistory; 72 | void Reset() 73 | { 74 | totalDamage = 0; 75 | previousTotalDamage = 0; 76 | mostDamageInOneSecond = 0; 77 | averageDps = 0; 78 | dpsHistory.clear(); 79 | } 80 | }; 81 | std::vector playerData; 82 | 83 | std::fstream dpsMeterConfigFile; 84 | std::string configFileName = "rise_dps_meter.cfg"; 85 | OF::Box* cornerWindow = nullptr; 86 | OF::Box* dpsMeterWindow = nullptr; 87 | OF::Box* dpsMeterWindowDivider = nullptr; 88 | OF::Box* placeholderWindow = nullptr; 89 | OF::Box* placeholderOkButton = nullptr; 90 | OF::Box* placeholderOkButtonBorder = nullptr; 91 | std::vector graphColumns; 92 | Timer timerUpdateDps{ 1000 }; 93 | Timer timerCornerText{ 3000 }; 94 | bool showCornerText = true; 95 | bool showPlaceholder = false; 96 | bool showDpsMeter = false; 97 | bool userDisabledDpsMeter = false; 98 | int font = -1; 99 | 100 | void DrawDpsMeter(); 101 | void DrawPlaceholder(); 102 | void DrawCornerText(); 103 | void UpdateDamageStats(); 104 | void UpdateGraph(); 105 | void ReadMemory(); 106 | void ReadPlayerDamage(PlayerData* playerStats); 107 | void FindDamageMemoryAddress(PlayerData* playerStats); 108 | void SetPointerOffsets(); 109 | void CheckHotkeys(); 110 | void WriteToConfigFile(); 111 | void ReadConfigFile(int* x, int* y); 112 | void ResetState(); 113 | }; -------------------------------------------------------------------------------- /packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/DirectXHook.cpp: -------------------------------------------------------------------------------- 1 | #include "DirectXHook.h" 2 | #include "RiseDpsMeter/RiseDpsMeter.h" 3 | #include "Example/Example.h" 4 | #include "PauseTheGame/PauseTheGame.h" 5 | 6 | static DirectXHook* hookInstance = nullptr; 7 | 8 | /* 9 | * Note: The non-member functions in this file are defined as such because 10 | * you are not allowed to pass around pointers to member functions. 11 | */ 12 | 13 | /* 14 | * Present will get hooked and detour to this function. 15 | * Present is part of the final rendering stage in DirectX. 16 | * We need to hook this so we can grab the pointer to the game's swapchain and use it to render. 17 | * We also place our own rendering code within this function call. 18 | * https://docs.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgiswapchain-present 19 | */ 20 | HRESULT __stdcall OnPresent(IDXGISwapChain* pThis, UINT syncInterval, UINT flags) 21 | { 22 | hookInstance->renderer->OnPresent(pThis, syncInterval, flags); 23 | return ((Present)hookInstance->presentReturnAddress)(pThis, syncInterval, flags); 24 | } 25 | 26 | /* 27 | * ResizeBuffers will get hooked and detour to this function. 28 | * ResizeBuffers usually gets called by the game when the window resizes. 29 | * We need to hook this so we can release our references to various resources when it's called. 30 | * If we don't do this then the game will most likely crash. 31 | * https://docs.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgiswapchain-resizebuffers 32 | */ 33 | HRESULT __stdcall OnResizeBuffers(IDXGISwapChain* pThis, UINT bufferCount, UINT width, UINT height, DXGI_FORMAT newFormat, UINT swapChainFlags) 34 | { 35 | hookInstance->renderer->OnResizeBuffers(pThis, bufferCount, width, height, newFormat, swapChainFlags); 36 | return ((ResizeBuffers)hookInstance->resizeBuffersReturnAddress)(pThis, bufferCount, width, height, newFormat, swapChainFlags); 37 | } 38 | 39 | /* 40 | * ExecuteCommandLists will get hooked and detour to this function. 41 | * ExecuteCommandLists gets called when work is to be submitted to the GPU. 42 | * We need to hook this so we can grab the command queue and use it to create the D3D11On12 device in DirectX 12 games. 43 | * https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12commandqueue-executecommandlists 44 | */ 45 | void __stdcall OnExecuteCommandLists(ID3D12CommandQueue* pThis, UINT numCommandLists, const ID3D12CommandList** ppCommandLists) 46 | { 47 | if (pThis->GetDesc().Type == D3D12_COMMAND_LIST_TYPE_DIRECT) 48 | { 49 | hookInstance->renderer->SetCommandQueue(pThis); 50 | //hookInstance->UnhookCommandQueue(); 51 | } 52 | 53 | ((ExecuteCommandLists)hookInstance->executeCommandListsReturnAddress)(pThis, numCommandLists, ppCommandLists); 54 | } 55 | 56 | void GetCommandQueue() 57 | { 58 | ID3D12CommandQueue* dummyCommandQueue = hookInstance->CreateDummyCommandQueue(); 59 | hookInstance->HookCommandQueue(dummyCommandQueue, (uintptr_t)&OnExecuteCommandLists, &hookInstance->executeCommandListsReturnAddress); 60 | } 61 | 62 | DirectXHook::DirectXHook(ID3DRenderer* renderer) 63 | { 64 | this->renderer = renderer; 65 | hookInstance = this; 66 | } 67 | 68 | void DirectXHook::Hook() 69 | { 70 | logger.Log("OnPresent: %p", &OnPresent); 71 | logger.Log("OnResizeBuffers: %p", &OnResizeBuffers); 72 | 73 | renderer->SetGetCommandQueueCallback(&GetCommandQueue); 74 | IDXGISwapChain* dummySwapChain = CreateDummySwapChain(); 75 | HookSwapChain(dummySwapChain, (uintptr_t)&OnPresent, (uintptr_t)&OnResizeBuffers, &presentReturnAddress, &resizeBuffersReturnAddress); 76 | } 77 | 78 | void DirectXHook::SetDrawExampleTriangle(bool doDraw) 79 | { 80 | ((Renderer*)renderer)->SetDrawExampleTriangle(doDraw); 81 | } 82 | 83 | void DirectXHook::AddRenderCallback(IRenderCallback* object) 84 | { 85 | renderer->AddRenderCallback(object); 86 | } 87 | 88 | IDXGISwapChain* DirectXHook::CreateDummySwapChain() 89 | { 90 | WNDCLASSEX wc { 0 }; 91 | wc.cbSize = sizeof(wc); 92 | wc.lpfnWndProc = DefWindowProc; 93 | wc.lpszClassName = TEXT("dummy class"); 94 | RegisterClassExA(&wc); 95 | HWND hwnd = CreateWindow(wc.lpszClassName, TEXT(""), WS_DISABLED, 0, 0, 0, 0, NULL, NULL, NULL, nullptr); 96 | 97 | DXGI_SWAP_CHAIN_DESC desc{ 0 }; 98 | desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; 99 | desc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; 100 | desc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; 101 | desc.SampleDesc.Count = 1; 102 | desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; 103 | desc.BufferCount = 1; 104 | desc.OutputWindow = hwnd; 105 | desc.Windowed = TRUE; 106 | desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; 107 | 108 | D3D_FEATURE_LEVEL featureLevels[] = 109 | { 110 | D3D_FEATURE_LEVEL_11_0, 111 | D3D_FEATURE_LEVEL_11_1 112 | }; 113 | 114 | ID3D11Device* dummyDevice = nullptr; 115 | IDXGISwapChain* dummySwapChain = nullptr; 116 | HRESULT result = D3D11CreateDeviceAndSwapChain( 117 | NULL, 118 | D3D_DRIVER_TYPE_HARDWARE, 119 | NULL, 120 | 0, 121 | featureLevels, 122 | 1, 123 | D3D11_SDK_VERSION, 124 | &desc, 125 | &dummySwapChain, 126 | &dummyDevice, 127 | NULL, 128 | NULL); 129 | 130 | DestroyWindow(desc.OutputWindow); 131 | UnregisterClass(wc.lpszClassName, GetModuleHandle(nullptr)); 132 | //dummySwapChain->Release(); 133 | //dummyDevice->Release(); 134 | 135 | if (FAILED(result)) 136 | { 137 | _com_error error(result); 138 | logger.Log("D3D11CreateDeviceAndSwapChain failed: %s", error.ErrorMessage()); 139 | return nullptr; 140 | } 141 | 142 | logger.Log("D3D11CreateDeviceAndSwapChain succeeded"); 143 | return dummySwapChain; 144 | } 145 | 146 | //IDXGISwapChain* DirectXHook::CreateDummySwapChain(ID3D11Device* dummyDevice) 147 | //{ 148 | // IDXGIDevice2* pDXGIDevice; 149 | // CheckSuccess(dummyDevice->QueryInterface(__uuidof(IDXGIDevice2), (void**)&pDXGIDevice)); 150 | // 151 | // IDXGIAdapter* pDXGIAdapter; 152 | // CheckSuccess(pDXGIDevice->GetParent(__uuidof(IDXGIAdapter), (void**)&pDXGIAdapter)); 153 | // 154 | // WNDCLASSEX wc { 0 }; 155 | // wc.cbSize = sizeof(wc); 156 | // wc.lpfnWndProc = DefWindowProc; 157 | // wc.lpszClassName = TEXT("dummy class"); 158 | // RegisterClassExA(&wc); 159 | // HWND hwnd = CreateWindow(wc.lpszClassName, TEXT(""), WS_DISABLED, 0, 0, 0, 0, NULL, NULL, NULL, nullptr); 160 | // 161 | // DXGI_SWAP_CHAIN_DESC1 desc{ 0 }; 162 | // desc.Width = 1; 163 | // desc.Height = 1; 164 | // desc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT; 165 | // desc.Stereo = FALSE; 166 | // desc.SampleDesc = { 1, 0 }; 167 | // desc.BufferUsage = DXGI_USAGE_BACK_BUFFER; 168 | // desc.BufferCount = 2; 169 | // desc.Scaling = DXGI_SCALING_STRETCH; 170 | // desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; 171 | // desc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; 172 | // desc.Flags = DXGI_SWAP_CHAIN_FLAG_DISPLAY_ONLY; 173 | // 174 | // IDXGIFactory2* pIDXGIFactory = nullptr; 175 | // CheckSuccess(pDXGIAdapter->GetParent(__uuidof(IDXGIFactory2), (void**)&pIDXGIFactory)); 176 | // 177 | // IDXGISwapChain* dummySwapChain = nullptr; 178 | // IDXGISwapChain1* dummySwapChain1 = nullptr; 179 | // CheckSuccess(pIDXGIFactory->CreateSwapChainForHwnd( 180 | // dummyDevice, 181 | // hwnd, 182 | // &desc, 183 | // NULL, 184 | // NULL, 185 | // &dummySwapChain1)); 186 | // 187 | // DestroyWindow(hwnd); 188 | // UnregisterClass(wc.lpszClassName, GetModuleHandle(nullptr)); 189 | // 190 | // CheckSuccess(dummySwapChain1->QueryInterface(__uuidof(IDXGISwapChain), (void**)&dummySwapChain)); 191 | // //dummySwapChain1->Release(); 192 | // return dummySwapChain; 193 | //} 194 | 195 | //ID3D11Device* DirectXHook::CreateDummyD3D11Device() 196 | //{ 197 | // D3D_FEATURE_LEVEL featureLevels[] = 198 | // { 199 | // D3D_FEATURE_LEVEL_11_0, 200 | // D3D_FEATURE_LEVEL_11_1 201 | // }; 202 | // 203 | // ID3D11Device* dummyDevice = nullptr; 204 | // CheckSuccess(D3D11CreateDevice( 205 | // NULL, 206 | // D3D_DRIVER_TYPE_HARDWARE, 207 | // NULL, 208 | // 0, 209 | // featureLevels, 210 | // 2, 211 | // D3D11_SDK_VERSION, 212 | // &dummyDevice, 213 | // NULL, 214 | // NULL)); 215 | // 216 | // return dummyDevice; 217 | //} 218 | 219 | ID3D12CommandQueue* DirectXHook::CreateDummyCommandQueue() 220 | { 221 | D3D12_COMMAND_QUEUE_DESC queueDesc {}; 222 | queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; 223 | queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; 224 | 225 | ID3D12Device* d12Device = nullptr; 226 | D3D12CreateDevice(NULL, D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), (void**)&d12Device); 227 | 228 | ID3D12CommandQueue* dummyCommandQueue; 229 | d12Device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&dummyCommandQueue)); 230 | 231 | logger.Log("Command queue: %p", dummyCommandQueue); 232 | 233 | return dummyCommandQueue; 234 | } 235 | 236 | void DirectXHook::HookSwapChain( 237 | IDXGISwapChain* dummySwapChain, 238 | uintptr_t presentDetourFunction, 239 | uintptr_t resizeBuffersDetourFunction, 240 | uintptr_t* presentReturnAddress, 241 | uintptr_t* resizeBuffersReturnAddress) 242 | { 243 | int vmtPresentOffset = 8; 244 | int vmtResizeBuffersOffset = 13; 245 | size_t numBytes = sizeof(size_t); 246 | 247 | uintptr_t vmtBaseAddress = (*(uintptr_t*)dummySwapChain); 248 | uintptr_t vmtPresentIndex = (vmtBaseAddress + (numBytes * vmtPresentOffset)); 249 | uintptr_t vmtResizeBuffersIndex = (vmtBaseAddress + (numBytes * vmtResizeBuffersOffset)); 250 | 251 | logger.Log("SwapChain VMT base address: %p", vmtBaseAddress); 252 | logger.Log("SwapChain VMT Present index: %p", vmtPresentIndex); 253 | logger.Log("SwapChain VMT ResizeBuffers index: %p", vmtResizeBuffersIndex); 254 | 255 | MemoryUtils::ToggleMemoryProtection(false, vmtPresentIndex, numBytes); 256 | MemoryUtils::ToggleMemoryProtection(false, vmtResizeBuffersIndex, numBytes); 257 | 258 | uintptr_t presentAddress = (*(uintptr_t*)vmtPresentIndex); 259 | uintptr_t resizeBuffersAddress = (*(uintptr_t*)vmtResizeBuffersIndex); 260 | 261 | logger.Log("Present address: %p", presentAddress); 262 | logger.Log("ResizeBuffers address: %p", resizeBuffersAddress); 263 | 264 | MemoryUtils::ToggleMemoryProtection(true, vmtPresentIndex, numBytes); 265 | MemoryUtils::ToggleMemoryProtection(true, vmtResizeBuffersIndex, numBytes); 266 | 267 | MemoryUtils::PlaceHook(presentAddress, presentDetourFunction, presentReturnAddress); 268 | MemoryUtils::PlaceHook(resizeBuffersAddress, resizeBuffersDetourFunction, resizeBuffersReturnAddress); 269 | 270 | dummySwapChain->Release(); 271 | } 272 | 273 | void DirectXHook::HookCommandQueue( 274 | ID3D12CommandQueue* dummyCommandQueue, 275 | uintptr_t executeCommandListsDetourFunction, 276 | uintptr_t* executeCommandListsReturnAddress) 277 | { 278 | int vmtExecuteCommandListsOffset = 10; 279 | size_t numBytes = 8; 280 | 281 | uintptr_t vmtBaseAddress = (*(uintptr_t*)dummyCommandQueue); 282 | uintptr_t vmtExecuteCommandListsIndex = (vmtBaseAddress + (numBytes * vmtExecuteCommandListsOffset)); 283 | 284 | logger.Log("CommandQueue VMT base address: %p", vmtBaseAddress); 285 | logger.Log("ExecuteCommandLists index: %p", vmtExecuteCommandListsIndex); 286 | 287 | MemoryUtils::ToggleMemoryProtection(false, vmtExecuteCommandListsIndex, numBytes); 288 | executeCommandListsAddress = (*(uintptr_t*)vmtExecuteCommandListsIndex); 289 | MemoryUtils::ToggleMemoryProtection(true, vmtExecuteCommandListsIndex, numBytes); 290 | 291 | logger.Log("ExecuteCommandLists address: %p", executeCommandListsAddress); 292 | 293 | bool hookIsPresent = MemoryUtils::IsAddressHooked(executeCommandListsAddress); 294 | if (hookIsPresent) 295 | { 296 | logger.Log("Hook already present in ExecuteCommandLists"); 297 | } 298 | 299 | MemoryUtils::PlaceHook(executeCommandListsAddress, executeCommandListsDetourFunction, executeCommandListsReturnAddress); 300 | dummyCommandQueue->Release(); 301 | } 302 | 303 | void DirectXHook::UnhookCommandQueue() 304 | { 305 | MemoryUtils::Unhook(executeCommandListsAddress); 306 | } -------------------------------------------------------------------------------- /src/DllMain.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "DirectXHook.h" 4 | #include "Logger.h" 5 | #include "MemoryUtils.h" 6 | #include "Example/Example.h" 7 | #include "UniversalProxyDLL.h" 8 | 9 | static Logger logger{ "DllMain" }; 10 | 11 | void OpenDebugTerminal() 12 | { 13 | std::fstream terminalEnableFile; 14 | terminalEnableFile.open("hook_enable_terminal.txt", std::fstream::in); 15 | if (terminalEnableFile.is_open()) 16 | { 17 | if (AllocConsole()) 18 | { 19 | freopen_s((FILE**)stdout, "CONOUT$", "w", stdout); 20 | SetWindowText(GetConsoleWindow(), "DirectXHook"); 21 | } 22 | terminalEnableFile.close(); 23 | } 24 | } 25 | 26 | DWORD WINAPI HookThread(LPVOID lpParam) 27 | { 28 | static Renderer renderer; 29 | static DirectXHook dxHook(&renderer); 30 | static Example example; 31 | dxHook.AddRenderCallback(&example); 32 | dxHook.Hook(); 33 | return 0; 34 | } 35 | 36 | BOOL WINAPI DllMain(HMODULE module, DWORD reason, LPVOID) 37 | { 38 | if (reason == DLL_PROCESS_ATTACH) 39 | { 40 | OpenDebugTerminal(); 41 | UPD::MuteLogging(); 42 | UPD::CreateProxy(module); 43 | CreateThread(0, 0, &HookThread, 0, 0, NULL); 44 | } 45 | 46 | return 1; 47 | } -------------------------------------------------------------------------------- /src/Renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "Renderer.h" 2 | 3 | using namespace Microsoft::WRL; 4 | using namespace DirectX; 5 | 6 | void Renderer::OnPresent(IDXGISwapChain* pThis, UINT syncInterval, UINT flags) 7 | { 8 | if (mustInitializeD3DResources) 9 | { 10 | if (!InitD3DResources(pThis)) 11 | { 12 | return; 13 | } 14 | mustInitializeD3DResources = false; 15 | } 16 | 17 | Render(); 18 | } 19 | 20 | void Renderer::OnResizeBuffers(IDXGISwapChain* pThis, UINT bufferCount, UINT width, UINT height, DXGI_FORMAT newFormat, UINT swapChainFlags) 21 | { 22 | logger.Log("ResizeBuffers was called!"); 23 | ReleaseViewsBuffersAndContext(); 24 | mustInitializeD3DResources = true; 25 | } 26 | 27 | void Renderer::SetDrawExampleTriangle(bool doDraw) 28 | { 29 | drawExamples = doDraw; 30 | } 31 | 32 | void Renderer::AddRenderCallback(IRenderCallback* object) 33 | { 34 | callbackObject = object; 35 | callbackInitialized = false; 36 | } 37 | 38 | void Renderer::SetCommandQueue(ID3D12CommandQueue* commandQueue) 39 | { 40 | this->commandQueue = commandQueue; 41 | } 42 | 43 | void Renderer::SetGetCommandQueueCallback(void (*callback)()) 44 | { 45 | callbackGetCommandQueue = callback; 46 | } 47 | 48 | bool Renderer::InitD3DResources(IDXGISwapChain* swapChain) 49 | { 50 | logger.Log("Initializing D3D resources..."); 51 | 52 | try 53 | { 54 | if (!isDeviceRetrieved) 55 | { 56 | this->swapChain = swapChain; 57 | isDeviceRetrieved = RetrieveD3DDeviceFromSwapChain(); 58 | } 59 | 60 | if (WaitForCommandQueueIfRunningD3D12()) 61 | { 62 | return false; 63 | } 64 | 65 | GetSwapChainDescription(); 66 | GetBufferCount(); 67 | GetSwapchainWindowInfo(); 68 | CreateViewport(); 69 | InitD3D(); 70 | } 71 | catch (std::string errorMsg) 72 | { 73 | logger.Log(errorMsg); 74 | return false; 75 | } 76 | 77 | firstTimeInitPerformed = true; 78 | logger.Log("Successfully initialized D3D resources"); 79 | return true; 80 | } 81 | 82 | bool Renderer::RetrieveD3DDeviceFromSwapChain() 83 | { 84 | logger.Log("Retrieving D3D device..."); 85 | 86 | bool d3d11DeviceRetrieved = SUCCEEDED(swapChain->GetDevice(__uuidof(ID3D11Device), (void**)d3d11Device.GetAddressOf())); 87 | if (d3d11DeviceRetrieved) 88 | { 89 | logger.Log("Retrieved D3D11 device"); 90 | return true; 91 | } 92 | 93 | bool d3d12DeviceRetrieved = SUCCEEDED(swapChain->GetDevice(__uuidof(ID3D12Device), (void**)d3d12Device.GetAddressOf())); 94 | if (d3d12DeviceRetrieved) 95 | { 96 | logger.Log("Retrieved D3D12 device"); 97 | isRunningD3D12 = true; 98 | return true; 99 | } 100 | 101 | throw("Failed to retrieve D3D device"); 102 | } 103 | 104 | void Renderer::GetSwapChainDescription() 105 | { 106 | ZeroMemory(&swapChainDesc, sizeof(DXGI_SWAP_CHAIN_DESC)); 107 | swapChain->GetDesc(&swapChainDesc); 108 | } 109 | 110 | void Renderer::GetBufferCount() 111 | { 112 | if (isRunningD3D12) 113 | { 114 | bufferCount = swapChainDesc.BufferCount; 115 | } 116 | else 117 | { 118 | bufferCount = 1; 119 | } 120 | } 121 | 122 | void Renderer::GetSwapchainWindowInfo() 123 | { 124 | RECT hwndRect; 125 | GetClientRect(swapChainDesc.OutputWindow, &hwndRect); 126 | windowWidth = hwndRect.right - hwndRect.left; 127 | windowHeight = hwndRect.bottom - hwndRect.top; 128 | logger.Log("Window width: %i", windowWidth); 129 | logger.Log("Window height: %i", windowHeight); 130 | window = swapChainDesc.OutputWindow; 131 | } 132 | 133 | void Renderer::CreateViewport() 134 | { 135 | ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT)); 136 | viewport.Width = windowWidth; 137 | viewport.Height = windowHeight; 138 | viewport.MinDepth = 0.0f; 139 | viewport.MaxDepth = 1.0f; 140 | viewport.TopLeftX = 0; 141 | viewport.TopLeftY = 0; 142 | } 143 | 144 | void Renderer::InitD3D() 145 | { 146 | if (!isRunningD3D12) 147 | { 148 | InitD3D11(); 149 | } 150 | else 151 | { 152 | InitD3D12(); 153 | } 154 | } 155 | 156 | void Renderer::InitD3D11() 157 | { 158 | logger.Log("Initializing D3D11..."); 159 | 160 | if (!firstTimeInitPerformed) 161 | { 162 | CreateD3D11Context(); 163 | CreateSpriteBatch(); 164 | } 165 | CreateD3D11RenderTargetView(); 166 | 167 | logger.Log("Initialized D3D11"); 168 | } 169 | 170 | void Renderer::CreateD3D11Context() 171 | { 172 | d3d11Device->GetImmediateContext(&d3d11Context); 173 | } 174 | 175 | void Renderer::CreateSpriteBatch() 176 | { 177 | spriteBatch = std::make_shared(d3d11Context.Get()); 178 | } 179 | 180 | void Renderer::CreateD3D11RenderTargetView() 181 | { 182 | ComPtr backbuffer; 183 | swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)backbuffer.GetAddressOf()); 184 | d3d11RenderTargetViews = std::vector>(1, nullptr); 185 | d3d11Device->CreateRenderTargetView(backbuffer.Get(), nullptr, d3d11RenderTargetViews[0].GetAddressOf()); 186 | backbuffer.ReleaseAndGetAddressOf(); 187 | } 188 | 189 | void Renderer::InitD3D12() 190 | { 191 | logger.Log("Initializing D3D12..."); 192 | 193 | if (!firstTimeInitPerformed) 194 | { 195 | CreateD3D11On12Device(); 196 | CheckSuccess(swapChain->QueryInterface(__uuidof(IDXGISwapChain3), &swapChain3)); 197 | CreateSpriteBatch(); 198 | } 199 | CreateD3D12Buffers(); 200 | 201 | logger.Log("Initialized D3D12"); 202 | } 203 | 204 | bool Renderer::WaitForCommandQueueIfRunningD3D12() 205 | { 206 | if (isRunningD3D12) 207 | { 208 | if (commandQueue.Get() == nullptr) 209 | { 210 | logger.Log("Waiting for command queue..."); 211 | if (!getCommandQueueCalled && callbackGetCommandQueue != nullptr) 212 | { 213 | callbackGetCommandQueue(); 214 | getCommandQueueCalled = true; 215 | } 216 | return true; 217 | } 218 | } 219 | return false; 220 | } 221 | 222 | void Renderer::CreateD3D11On12Device() 223 | { 224 | D3D_FEATURE_LEVEL featureLevels = { D3D_FEATURE_LEVEL_11_0 }; 225 | bool d3d11On12DeviceCreated = CheckSuccess( 226 | D3D11On12CreateDevice( 227 | d3d12Device.Get(), 228 | NULL, 229 | &featureLevels, 230 | 1, 231 | reinterpret_cast(commandQueue.GetAddressOf()), 232 | 1, 233 | 0, 234 | d3d11Device.GetAddressOf(), 235 | d3d11Context.GetAddressOf(), 236 | nullptr)); 237 | 238 | bool d3d11On12DeviceChecked = CheckSuccess(d3d11Device.As(&d3d11On12Device)); 239 | 240 | if (!d3d11On12DeviceCreated || !d3d11On12DeviceChecked) 241 | { 242 | throw("Failed to create D3D11On12 device"); 243 | } 244 | } 245 | 246 | void Renderer::CreateD3D12Buffers() 247 | { 248 | d3d12RenderTargets = std::vector>(bufferCount, nullptr); 249 | d3d11WrappedBackBuffers = std::vector>(bufferCount, nullptr); 250 | d3d11RenderTargetViews = std::vector>(bufferCount, nullptr); 251 | 252 | ComPtr rtvHeap = CreateD3D12RtvHeap(); 253 | D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle(rtvHeap->GetCPUDescriptorHandleForHeapStart()); 254 | UINT rtvDescriptorSize = d3d12Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); 255 | for (UINT i = 0; i < bufferCount; i++) 256 | { 257 | CreateD3D12RenderTargetView(i, rtvHandle); 258 | CreateD3D11WrappedBackBuffer(i); 259 | CreateD3D11RenderTargetViewWithWrappedBackBuffer(i); 260 | rtvHandle.ptr = SIZE_T(INT64(rtvHandle.ptr) + INT64(1) * INT64(rtvDescriptorSize)); 261 | } 262 | } 263 | 264 | ComPtr Renderer::CreateD3D12RtvHeap() 265 | { 266 | ComPtr rtvHeap; 267 | D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {}; 268 | rtvHeapDesc.NumDescriptors = bufferCount; 269 | rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; 270 | rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; 271 | CheckSuccess(d3d12Device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(rtvHeap.GetAddressOf()))); 272 | return rtvHeap; 273 | } 274 | 275 | void Renderer::CreateD3D12RenderTargetView(UINT bufferIndex, D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle) 276 | { 277 | if (!CheckSuccess(swapChain->GetBuffer(bufferIndex, IID_PPV_ARGS(&d3d12RenderTargets[bufferIndex])))) 278 | { 279 | throw("Failed to create D3D12 render target view"); 280 | } 281 | d3d12Device->CreateRenderTargetView(d3d12RenderTargets[bufferIndex].Get(), nullptr, rtvHandle); 282 | } 283 | 284 | void Renderer::CreateD3D11WrappedBackBuffer(UINT bufferIndex) 285 | { 286 | D3D11_RESOURCE_FLAGS d3d11Flags = { D3D11_BIND_RENDER_TARGET }; 287 | if (!CheckSuccess( 288 | d3d11On12Device->CreateWrappedResource( 289 | d3d12RenderTargets[bufferIndex].Get(), 290 | &d3d11Flags, 291 | D3D12_RESOURCE_STATE_RENDER_TARGET, 292 | D3D12_RESOURCE_STATE_PRESENT, 293 | IID_PPV_ARGS(&d3d11WrappedBackBuffers[bufferIndex])))) 294 | { 295 | throw "Failed to create D3D11 wrapped backbuffer"; 296 | } 297 | } 298 | 299 | void Renderer::CreateD3D11RenderTargetViewWithWrappedBackBuffer(UINT bufferIndex) 300 | { 301 | if (!CheckSuccess( 302 | d3d11Device->CreateRenderTargetView( 303 | d3d11WrappedBackBuffers[bufferIndex].Get(), 304 | nullptr, 305 | d3d11RenderTargetViews[bufferIndex].GetAddressOf()))) 306 | { 307 | throw "Failed to create D3D11 render target view"; 308 | } 309 | } 310 | 311 | void Renderer::Render() 312 | { 313 | PreRender(); 314 | 315 | if (drawExamples) 316 | { 317 | if (!examplesLoaded) 318 | { 319 | CreatePipeline(); 320 | CreateExampleTriangle(); 321 | CreateExampleFont(); 322 | examplesLoaded = true; 323 | } 324 | 325 | DrawExampleTriangle(); 326 | DrawExampleText(); 327 | } 328 | 329 | RenderCallbacks(); 330 | PostRender(); 331 | } 332 | 333 | void Renderer::PreRender() 334 | { 335 | if (isRunningD3D12) 336 | { 337 | bufferIndex = swapChain3->GetCurrentBackBufferIndex(); 338 | d3d11On12Device->AcquireWrappedResources(d3d11WrappedBackBuffers[bufferIndex].GetAddressOf(), 1); 339 | } 340 | 341 | d3d11Context->OMSetRenderTargets(1, d3d11RenderTargetViews[bufferIndex].GetAddressOf(), 0); 342 | d3d11Context->RSSetViewports(1, &viewport); 343 | } 344 | 345 | void Renderer::RenderCallbacks() 346 | { 347 | if (callbackObject != nullptr) 348 | { 349 | if (!callbackInitialized) 350 | { 351 | callbackObject->Init(d3d11Device, d3d11Context, spriteBatch, window); 352 | callbackObject->Setup(); 353 | callbackInitialized = true; 354 | } 355 | 356 | spriteBatch->Begin(SpriteSortMode_BackToFront); 357 | callbackObject->Render(); 358 | spriteBatch->End(); 359 | } 360 | } 361 | 362 | void Renderer::PostRender() 363 | { 364 | if (isRunningD3D12) 365 | { 366 | d3d11On12Device->ReleaseWrappedResources(d3d11WrappedBackBuffers[bufferIndex].GetAddressOf(), 1); 367 | d3d11Context->Flush(); 368 | } 369 | } 370 | 371 | // Creates the necessary things for rendering the examples 372 | void Renderer::CreatePipeline() 373 | { 374 | ComPtr vertexShaderBlob = LoadShader(shaderData, "vs_5_0", "VS").Get(); 375 | ComPtr pixelShaderTexturesBlob = LoadShader(shaderData, "ps_5_0", "PSTex").Get(); 376 | ComPtr pixelShaderBlob = LoadShader(shaderData, "ps_5_0", "PS").Get(); 377 | 378 | d3d11Device->CreateVertexShader( 379 | vertexShaderBlob->GetBufferPointer(), 380 | vertexShaderBlob->GetBufferSize(), 381 | nullptr, 382 | vertexShader.GetAddressOf()); 383 | 384 | d3d11Device->CreatePixelShader( 385 | pixelShaderTexturesBlob->GetBufferPointer(), 386 | pixelShaderTexturesBlob->GetBufferSize(), 387 | nullptr, 388 | pixelShaderTextures.GetAddressOf()); 389 | 390 | d3d11Device->CreatePixelShader(pixelShaderBlob->GetBufferPointer(), 391 | pixelShaderBlob->GetBufferSize(), nullptr, pixelShader.GetAddressOf()); 392 | 393 | D3D11_INPUT_ELEMENT_DESC inputLayoutDesc[3] = 394 | { 395 | { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, 396 | { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }, 397 | { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 } 398 | }; 399 | 400 | d3d11Device->CreateInputLayout( 401 | inputLayoutDesc, 402 | ARRAYSIZE(inputLayoutDesc), 403 | vertexShaderBlob->GetBufferPointer(), 404 | vertexShaderBlob->GetBufferSize(), 405 | inputLayout.GetAddressOf()); 406 | 407 | D3D11_SAMPLER_DESC samplerDesc; 408 | ZeroMemory(&samplerDesc, sizeof(D3D11_SAMPLER_DESC)); 409 | samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; 410 | samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; 411 | samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; 412 | samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; 413 | samplerDesc.ComparisonFunc = D3D11_COMPARISON_NEVER; 414 | samplerDesc.MinLOD = 0; 415 | samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; 416 | 417 | d3d11Device->CreateSamplerState(&samplerDesc, &samplerState); 418 | 419 | D3D11_TEXTURE2D_DESC dsDesc; 420 | dsDesc.Width = windowWidth; 421 | dsDesc.Height = windowHeight; 422 | dsDesc.MipLevels = 1; 423 | dsDesc.ArraySize = 1; 424 | dsDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; 425 | dsDesc.SampleDesc.Count = 1; 426 | dsDesc.SampleDesc.Quality = 0; 427 | dsDesc.Usage = D3D11_USAGE_DEFAULT; 428 | dsDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL; 429 | dsDesc.CPUAccessFlags = 0; 430 | dsDesc.MiscFlags = 0; 431 | 432 | d3d11Device->CreateTexture2D(&dsDesc, 0, depthStencilBuffer.GetAddressOf()); 433 | d3d11Device->CreateDepthStencilView(depthStencilBuffer.Get(), 0, depthStencilView.GetAddressOf()); 434 | } 435 | 436 | ComPtr Renderer::LoadShader(const char* shader, std::string targetShaderVersion, std::string shaderEntry) 437 | { 438 | logger.Log("Loading shader: %s", shaderEntry.c_str()); 439 | ComPtr errorBlob = nullptr; 440 | ComPtr shaderBlob; 441 | 442 | D3DCompile( 443 | shader, 444 | strlen(shader), 445 | 0, 446 | nullptr, 447 | nullptr, 448 | shaderEntry.c_str(), 449 | targetShaderVersion.c_str(), 450 | D3DCOMPILE_ENABLE_STRICTNESS, 451 | 0, 452 | shaderBlob.GetAddressOf(), 453 | errorBlob.GetAddressOf()); 454 | 455 | if (errorBlob) 456 | { 457 | char error[256]{ 0 }; 458 | memcpy(error, errorBlob->GetBufferPointer(), errorBlob->GetBufferSize()); 459 | logger.Log("Shader error: %s", error); 460 | return nullptr; 461 | } 462 | 463 | return shaderBlob; 464 | } 465 | 466 | void Renderer::CreateExampleTriangle() 467 | { 468 | // Create the vertex buffer 469 | Vertex vertices[] = 470 | { 471 | { XMFLOAT3(0.0f, 0.1f, 0.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f), XMFLOAT2(0.5f, 0.0f) }, 472 | { XMFLOAT3(0.1f, -0.1f, 0.1f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f), XMFLOAT2(1.0f, 0.5f) }, 473 | { XMFLOAT3(-0.1f, -0.1f, 0.1f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f), XMFLOAT2(0.0f, 0.5f) }, 474 | { XMFLOAT3(0.0f, -0.1f, -0.1f), XMFLOAT4(1.0f, 0.0f, 1.0f, 1.0f), XMFLOAT2(1.0f, 0.5f) } 475 | }; 476 | 477 | D3D11_BUFFER_DESC vbDesc = { 0 }; 478 | ZeroMemory(&vbDesc, sizeof(D3D11_BUFFER_DESC)); 479 | vbDesc.ByteWidth = sizeof(Vertex) * ARRAYSIZE(vertices); 480 | vbDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; 481 | vbDesc.Usage = D3D11_USAGE_DEFAULT; 482 | vbDesc.StructureByteStride = sizeof(Vertex); 483 | 484 | D3D11_SUBRESOURCE_DATA vbData = { vertices, 0, 0 }; 485 | 486 | d3d11Device->CreateBuffer(&vbDesc, &vbData, vertexBuffer.GetAddressOf()); 487 | 488 | // Create the index buffer 489 | unsigned int indices[] = 490 | { 491 | 0, 2, 1, 492 | 0, 3, 2, 493 | 0, 1, 3, 494 | 1, 2, 3 495 | }; 496 | 497 | triangleNumIndices = ARRAYSIZE(indices); 498 | 499 | D3D11_BUFFER_DESC ibDesc; 500 | ZeroMemory(&ibDesc, sizeof(ibDesc)); 501 | ibDesc.ByteWidth = sizeof(unsigned int) * ARRAYSIZE(indices); 502 | ibDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; 503 | ibDesc.Usage = D3D11_USAGE_DEFAULT; 504 | ibDesc.CPUAccessFlags = 0; 505 | ibDesc.MiscFlags = 0; 506 | 507 | D3D11_SUBRESOURCE_DATA ibData = { indices, 0, 0 }; 508 | 509 | d3d11Device->CreateBuffer(&ibDesc, &ibData, indexBuffer.GetAddressOf()); 510 | 511 | // Create the constant buffer 512 | // We need to send the world view projection (WVP) matrix to the shader 513 | D3D11_BUFFER_DESC cbDesc = { 0 }; 514 | ZeroMemory(&cbDesc, sizeof(D3D11_BUFFER_DESC)); 515 | cbDesc.ByteWidth = sizeof(ConstantBufferData); 516 | cbDesc.Usage = D3D11_USAGE_DYNAMIC; 517 | cbDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; 518 | cbDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; 519 | cbDesc.MiscFlags = 0; 520 | cbDesc.StructureByteStride = 0; 521 | 522 | D3D11_SUBRESOURCE_DATA cbData = { &constantBufferData, 0, 0 }; 523 | 524 | d3d11Device->CreateBuffer(&cbDesc, &cbData, constantBuffer.GetAddressOf()); 525 | 526 | // Create the rasterizer state. 527 | // We need to control which face of a shape is culled, and we need to know which order to set our indices 528 | D3D11_RASTERIZER_DESC rsDesc; 529 | ZeroMemory(&rsDesc, sizeof(D3D11_RASTERIZER_DESC)); 530 | rsDesc.FillMode = D3D11_FILL_SOLID; 531 | rsDesc.CullMode = D3D11_CULL_BACK; 532 | rsDesc.FrontCounterClockwise = FALSE; 533 | rsDesc.DepthClipEnable = TRUE; 534 | 535 | d3d11Device->CreateRasterizerState(&rsDesc, rasterizerState.GetAddressOf()); 536 | 537 | // Create the depth stencil state 538 | D3D11_DEPTH_STENCIL_DESC dsDesc; 539 | ZeroMemory(&dsDesc, sizeof(D3D11_DEPTH_STENCIL_DESC)); 540 | dsDesc.DepthEnable = true; 541 | dsDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; 542 | dsDesc.DepthFunc = D3D11_COMPARISON_LESS_EQUAL; 543 | 544 | d3d11Device->CreateDepthStencilState(&dsDesc, depthStencilState.GetAddressOf()); 545 | } 546 | 547 | void Renderer::CreateExampleFont() 548 | { 549 | std::fstream file = std::fstream(".\\hook_fonts\\OpenSans-22.spritefont"); 550 | 551 | if (!file.fail()) 552 | { 553 | file.close(); 554 | exampleFont = std::make_shared(d3d11Device.Get(), L".\\hook_fonts\\OpenSans-22.spritefont"); 555 | } 556 | else 557 | { 558 | logger.Log("Failed to load the example font"); 559 | } 560 | } 561 | 562 | void Renderer::DrawExampleTriangle() 563 | { 564 | d3d11Context->OMSetRenderTargets(1, d3d11RenderTargetViews[bufferIndex].GetAddressOf(), depthStencilView.Get()); 565 | d3d11Context->ClearDepthStencilView(depthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0); 566 | 567 | trianglePos = XMVectorSet 568 | ( 569 | XMVectorGetX(trianglePos) + triangleVelX, 570 | XMVectorGetY(trianglePos) + triangleVelY, 571 | XMVectorGetZ(trianglePos), 572 | 1.0f 573 | ); 574 | 575 | int hit = 0; 576 | 577 | // Check if the triangle hits an edge of the screen 578 | if (triangleNdc.x > 0.96f) 579 | { 580 | triangleVelX = -triangleSpeed; 581 | hit++; 582 | } 583 | else if (triangleNdc.x < -0.96f) 584 | { 585 | triangleVelX = triangleSpeed; 586 | hit++; 587 | } 588 | 589 | if (triangleNdc.y > 0.90f) 590 | { 591 | triangleVelY = -triangleSpeed; 592 | hit++; 593 | } 594 | else if (triangleNdc.y < -0.90f) 595 | { 596 | triangleVelY = triangleSpeed; 597 | hit++; 598 | } 599 | 600 | if (hit == 2) 601 | { 602 | logger.Log("Hit the corner!"); 603 | } 604 | 605 | triangleCounter += 0.01f; 606 | triangleRotX = cos(triangleCounter) * 2; 607 | triangleRotY = sin(triangleCounter) * 2; 608 | 609 | XMMATRIX world = XMMatrixIdentity(); 610 | 611 | XMMATRIX translation = XMMatrixTranslation(XMVectorGetX(trianglePos), XMVectorGetY(trianglePos), XMVectorGetZ(trianglePos)); 612 | 613 | XMMATRIX rotationX = XMMatrixRotationX(triangleRotX); 614 | XMMATRIX rotationY = XMMatrixRotationY(triangleRotY); 615 | XMMATRIX rotationZ = XMMatrixRotationZ(triangleRotZ); 616 | XMMATRIX rotation = rotationX * rotationY * rotationZ; 617 | 618 | XMMATRIX scale = XMMatrixScaling(triangleScale.x, triangleScale.y, triangleScale.z); 619 | 620 | world = scale * rotation * translation; 621 | 622 | XMMATRIX view = XMMatrixLookAtLH(XMVECTOR{ 0.0f, 0.0f, -5.5f }, XMVECTOR{ 0.0f, 0.0f, 0.0f }, XMVECTOR{ 0.0f, 1.0f, 0.0f }); 623 | 624 | XMMATRIX projection = XMMatrixPerspectiveFovLH(1.3, ((float)windowWidth / (float)windowHeight), 0.1f, 1000.0f); 625 | 626 | // Get the triangle's screen space (NDC) from its world space, used for collision checking and text positioning 627 | XMVECTOR clipSpacePos = XMVector4Transform((XMVector4Transform(trianglePos, view)), projection); 628 | 629 | XMStoreFloat3 630 | ( 631 | &triangleNdc, 632 | { 633 | XMVectorGetX(clipSpacePos) / XMVectorGetW(clipSpacePos), 634 | XMVectorGetY(clipSpacePos) / XMVectorGetW(clipSpacePos), 635 | XMVectorGetZ(clipSpacePos) / XMVectorGetW(clipSpacePos) 636 | } 637 | ); 638 | 639 | constantBufferData.wvp = XMMatrixTranspose(world * view * projection); 640 | 641 | // Map the constant buffer on the GPU 642 | D3D11_MAPPED_SUBRESOURCE mappedResource; 643 | d3d11Context->Map(constantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); 644 | memcpy(mappedResource.pData, &constantBufferData, sizeof(ConstantBufferData)); 645 | d3d11Context->Unmap(constantBuffer.Get(), 0); 646 | 647 | d3d11Context->VSSetShader(vertexShader.Get(), nullptr, 0); 648 | d3d11Context->PSSetShader(pixelShader.Get(), nullptr, 0); 649 | 650 | d3d11Context->IASetInputLayout(inputLayout.Get()); 651 | d3d11Context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); 652 | 653 | d3d11Context->RSSetState(rasterizerState.Get()); 654 | d3d11Context->OMSetDepthStencilState(depthStencilState.Get(), 0); 655 | 656 | d3d11Context->VSSetConstantBuffers(0, 1, constantBuffer.GetAddressOf()); 657 | 658 | UINT stride = sizeof(Vertex); 659 | UINT offset = 0; 660 | 661 | d3d11Context->IASetVertexBuffers(0, 1, vertexBuffer.GetAddressOf(), &stride, &offset); 662 | d3d11Context->IASetIndexBuffer(indexBuffer.Get(), DXGI_FORMAT_R32_UINT, 0); 663 | d3d11Context->DrawIndexed(triangleNumIndices, 0, 0); 664 | } 665 | 666 | void Renderer::DrawExampleText() 667 | { 668 | if (exampleFont == nullptr) return; 669 | 670 | const char* text = "Hello, World!"; 671 | const char* text2 = "This is a DirectX hook."; 672 | XMFLOAT2 stringSize1, stringSize2; 673 | XMVECTOR textVector = exampleFont->MeasureString(text); 674 | XMVECTOR textVector2 = exampleFont->MeasureString(text2); 675 | XMStoreFloat2(&stringSize1, textVector); 676 | XMStoreFloat2(&stringSize2, textVector2); 677 | 678 | XMFLOAT2 textPos1 = XMFLOAT2 679 | ( 680 | (windowWidth / 2) * (triangleNdc.x + 1) - (stringSize1.x / 2), 681 | (windowHeight - ((windowHeight / 2) * (triangleNdc.y + 1))) - (stringSize1.y / 2) - 150 682 | ); 683 | 684 | XMFLOAT2 textPos2 = XMFLOAT2 685 | ( 686 | (windowWidth / 2) * (triangleNdc.x + 1) - (stringSize2.x / 2), 687 | (windowHeight - ((windowHeight / 2) * (triangleNdc.y + 1))) - (stringSize2.y / 2) + 150 688 | ); 689 | 690 | spriteBatch->Begin(); 691 | exampleFont->DrawString(spriteBatch.get(), text, textPos1); 692 | exampleFont->DrawString(spriteBatch.get(), text2, textPos2); 693 | spriteBatch->End(); 694 | } 695 | 696 | void Renderer::ReleaseViewsBuffersAndContext() 697 | { 698 | for (int i = 0; i < bufferCount; i++) 699 | { 700 | if (d3d12Device.Get() == nullptr) 701 | { 702 | d3d11RenderTargetViews[i].ReleaseAndGetAddressOf(); 703 | } 704 | else 705 | { 706 | d3d11RenderTargetViews[i].ReleaseAndGetAddressOf(); 707 | d3d12RenderTargets[i].ReleaseAndGetAddressOf(); 708 | d3d11WrappedBackBuffers[i].ReleaseAndGetAddressOf(); 709 | } 710 | } 711 | 712 | if (d3d11Context.Get() != nullptr) 713 | { 714 | d3d11Context->Flush(); 715 | } 716 | } 717 | 718 | bool Renderer::CheckSuccess(HRESULT hr) 719 | { 720 | if (SUCCEEDED(hr)) 721 | { 722 | return true; 723 | } 724 | _com_error err(hr); 725 | logger.Log("%s", err.ErrorMessage()); 726 | return false; 727 | } -------------------------------------------------------------------------------- /src/Shaders.hlsl: -------------------------------------------------------------------------------- 1 | // These are some generic shaders that we need to render the example triangle 2 | // Make sure that this file is excluded from the build to avoid compile time errors! 3 | R""( 4 | 5 | Texture2D tex; 6 | SamplerState sampleType; 7 | 8 | cbuffer constantBuffer 9 | { 10 | matrix wvp; 11 | }; 12 | 13 | struct VS_Input 14 | { 15 | float4 pos : POSITION; 16 | float4 color : COLOR; 17 | float2 texcoord : TEXCOORD; 18 | }; 19 | 20 | struct VS_Output 21 | { 22 | float4 pos : SV_POSITION; 23 | float4 color : COLOR; 24 | float2 texcoord : TEXCOORD; 25 | }; 26 | 27 | VS_Output VS(VS_Input input) 28 | { 29 | VS_Output vsout; 30 | vsout.pos = mul(input.pos, wvp); 31 | vsout.color = input.color; 32 | vsout.texcoord = input.texcoord; 33 | return vsout; 34 | } 35 | 36 | float4 PSTex(VS_Output input) : SV_Target 37 | { 38 | float4 textureColor; 39 | textureColor = tex.Sample(sampleType, input.texcoord); 40 | return textureColor; 41 | } 42 | 43 | float4 PS(VS_Output input) : SV_Target 44 | { 45 | return input.color; 46 | } 47 | 48 | )"" --------------------------------------------------------------------------------