├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── create_release.yaml ├── .gitignore ├── .gitmodules ├── DDDAFix.filters ├── DDDAFix.ini ├── DDDAFix.sln ├── DDDAFix.vcxproj ├── LICENSE.md ├── README.md ├── external └── safetyhook │ ├── Zydis.c │ ├── Zydis.h │ ├── safetyhook.cpp │ └── safetyhook.hpp └── src ├── dllmain.cpp ├── helper.hpp └── stdafx.h /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: Wintermance 2 | ko_fi: lyall 3 | -------------------------------------------------------------------------------- /.github/workflows/create_release.yaml: -------------------------------------------------------------------------------- 1 | name: create-release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'Release version number' 8 | required: true 9 | 10 | defaults: 11 | run: 12 | shell: pwsh 13 | 14 | jobs: 15 | build: 16 | runs-on: windows-latest 17 | permissions: 18 | contents: write 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | submodules: 'true' 23 | 24 | - name: setup-msbuild 25 | uses: microsoft/setup-msbuild@v1.1 26 | 27 | - run: msbuild ${{ github.event.repository.name }}.sln -t:rebuild -verbosity:diag -property:Configuration=Release -property:Platform=x86 28 | - run: cp Release\${{ github.event.repository.name }}.asi ${{ github.event.repository.name }}.asi 29 | 30 | - uses: robinraju/release-downloader@v1.8 31 | with: 32 | repository: "ThirteenAG/Ultimate-ASI-Loader" 33 | latest: true 34 | fileName: "Ultimate-ASI-Loader.zip" 35 | 36 | - name: Prepare Ultimate ASI Loader 37 | run: | 38 | unzip Ultimate-ASI-Loader.zip -d .\ 39 | C:\msys64\usr\bin\wget.exe -O .\UltimateASILoader_LICENSE.md https://raw.githubusercontent.com/ThirteenAG/Ultimate-ASI-Loader/master/license 40 | 41 | - name: Create Directory Structure 42 | run: | 43 | mkdir .\zip\ 44 | 45 | # Release build 46 | - name: Prepare Release Files 47 | run: | 48 | cp ${{ github.event.repository.name }}.asi .\zip\ 49 | cp ${{ github.event.repository.name }}.ini .\zip\ 50 | cp dinput8.dll .\zip\winmm.dll 51 | cp UltimateASILoader_LICENSE.md .\zip\ 52 | New-Item -Path ".\zip\EXTRACT_TO_GAME_FOLDER" -ItemType File 53 | 54 | - name: Create Release Zip 55 | run: | 56 | cd .\zip 57 | 7z a -r -tzip ..\${{ github.event.repository.name }}_${{ github.event.inputs.version }}.zip .\* 58 | 59 | - uses: ncipollo/release-action@v1 60 | with: 61 | artifacts: "${{ github.event.repository.name }}_${{ github.event.inputs.version }}.zip" 62 | token: ${{ secrets.GITHUB_TOKEN }} 63 | tag: ${{ github.event.inputs.version }} 64 | name: "${{ github.event.inputs.version }}" 65 | draft: true 66 | generateReleaseNotes: true 67 | artifactErrorsFailBuild: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | 2 | [submodule "external/inipp"] 3 | path = external/inipp 4 | url = https://github.com/mcmtroffaes/inipp 5 | [submodule "external/spdlog"] 6 | path = external/spdlog 7 | url = https://github.com/gabime/spdlog 8 | -------------------------------------------------------------------------------- /DDDAFix.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 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | 26 | 27 | Header Files 28 | 29 | 30 | Header Files 31 | 32 | 33 | -------------------------------------------------------------------------------- /DDDAFix.ini: -------------------------------------------------------------------------------- 1 | [DDDAFix Parameters] 2 | ; Injection delay in milliseconds. 3 | InjectionDelay = 1000 4 | 5 | ;;;;;;;;;; General ;;;;;;;;;; 6 | 7 | [Raise Framerate Cap] 8 | ; Raises "variable" framerate setting from 150fps to 1000fps. 9 | Enabled = false 10 | 11 | [Disable Pause on Focus Loss] 12 | ; Set to true to stop the game from pausing when alt+tabbed. 13 | Enabled = false 14 | 15 | [Borderless Windowed Mode] 16 | ; Set to true to enable borderless fullscreen when the game is set to "windowed" mode. 17 | Enabled = true 18 | 19 | ;;;;;;;;;; Ultrawide/Narrower Fixes ;;;;;;;;;; 20 | 21 | [Fix HUD] 22 | ; Centres HUD to 16:9 and fixes many HUD issues. 23 | Enabled = true 24 | 25 | [Fix FOV] 26 | ; Fixes FOV. 27 | Enabled = true -------------------------------------------------------------------------------- /DDDAFix.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32901.215 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DDDAFix", "DDDAFix.vcxproj", "{C6644269-B721-4F94-BE7F-77BFB2343BA5}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x86 = Debug|x86 11 | Release|x86 = Release|x86 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {C6644269-B721-4F94-BE7F-77BFB2343BA5}.Debug|x86.ActiveCfg = Debug|Win32 15 | {C6644269-B721-4F94-BE7F-77BFB2343BA5}.Debug|x86.Build.0 = Debug|Win32 16 | {C6644269-B721-4F94-BE7F-77BFB2343BA5}.Release|x86.ActiveCfg = Release|Win32 17 | {C6644269-B721-4F94-BE7F-77BFB2343BA5}.Release|x86.Build.0 = Release|Win32 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {BD882CE7-25BA-4751-8EB4-4B14C55192CA} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /DDDAFix.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 16.0 26 | Win32Proj 27 | {c6644269-b721-4f94-be7f-77bfb2343ba5} 28 | DDDAFix 29 | 10.0 30 | 31 | 32 | 33 | DynamicLibrary 34 | true 35 | v143 36 | Unicode 37 | 38 | 39 | DynamicLibrary 40 | false 41 | v143 42 | true 43 | Unicode 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | .asi 59 | external\inipp;external\spdlog\include;external\safetyhook;$(VC_IncludePath);$(WindowsSDK_IncludePath); 60 | $(SolutionDir)$(Configuration)\ 61 | 62 | 63 | $(SolutionDir)$(Configuration)\ 64 | .asi 65 | external\inipp;external\spdlog\include;external\safetyhook;$(VC_IncludePath);$(WindowsSDK_IncludePath); 66 | 67 | 68 | 69 | Level3 70 | true 71 | WIN32;_DEBUG;DDDAFix_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;_CRT_SECURE_NO_WARNINGS 72 | true 73 | NotUsing 74 | pch.h 75 | stdcpplatest 76 | 77 | 78 | Windows 79 | true 80 | false 81 | 82 | 83 | 84 | 85 | Level3 86 | true 87 | true 88 | true 89 | WIN32;NDEBUG;DDDAFix_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);_CRT_SECURE_NO_WARNINGS 90 | true 91 | NotUsing 92 | pch.h 93 | stdcpplatest 94 | Default 95 | 96 | 97 | Windows 98 | true 99 | true 100 | true 101 | false 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Lyall 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dragon's Dogma: Dark Arisen Fix 2 | [![Patreon-Button](https://github.com/Lyall/FISTFix/assets/695941/19c468ac-52af-4790-b4eb-5187c06af949)](https://www.patreon.com/Wintermance) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/W7W01UAI9)
3 | [![Github All Releases](https://img.shields.io/github/downloads/Lyall/DDDAFix/total.svg)](https://github.com/Lyall/DDDAFix/releases) 4 | 5 | This is a fix for Dragon's Dogma: Dark Arisen that attempts to solve various issues related to non-16:9 display support and more.
6 | 7 | ## Ultrawide/Narrower Features 8 | - Fixed broken depth of field. 9 | - 16:9 centred HUD. 10 | - Fixed many broken/misaligned HUD elements. 11 | - Corrected mouse input for interacting with UI. 12 | - Fixed cropped cutscene FOV. 13 | - Spanned backgrounds for various HUD elements like fades etc. 14 | - Correctly scaled FMVs. 15 | 16 | ## Other Features 17 | - Option to uncap 150fps "variable" framerate cap. 18 | - Borderless windowed mode. 19 | - Option to disable pausing when game is alt+tabbed. 20 | 21 | ## Installation 22 | - Grab the latest release of DDDAFix from [here.](https://github.com/Lyall/DDDAFix/releases) 23 | - Extract the contents of the release zip in to the the game folder.
(e.g. "**steamapps\common\DDDA**" for Steam). 24 | 25 | ### Steam Deck/Linux Additional Instructions 26 | 🚩**You do not need to do this if you are using Windows!** 27 | - Open up the game properties in Steam and add `WINEDLLOVERRIDES="winmm=n,b" %command%` to the launch options. 28 | 29 | ## Configuration 30 | - See **DDDAFix.ini** to adjust settings for the fix. 31 | 32 | ## Known Issues 33 | Please report any issues you see. 34 | This list will contain bugs which may or may not be fixed. 35 | - Loaded map area is limited to 16:9. 36 | 37 | ## Screenshots 38 | 39 | | ![ezgif-3-c3dee62d14](https://github.com/Lyall/DDDAFix/assets/695941/be7804e2-e896-47ac-ab1e-d03e9e00cdcd) | 40 | |:--:| 41 | | Gameplay | 42 | 43 | ## Credits 44 | Thanks to [@p1xel8ted](https://github.com/p1xel8ted) for testing!
45 | [Ultimate ASI Loader](https://github.com/ThirteenAG/Ultimate-ASI-Loader) for ASI loading.
46 | [inipp](https://github.com/mcmtroffaes/inipp) for ini reading.
47 | [spdlog](https://github.com/gabime/spdlog) for logging.
48 | [safetyhook](https://github.com/cursey/safetyhook) for hooking. 49 | -------------------------------------------------------------------------------- /external/safetyhook/safetyhook.cpp: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This file is auto-generated by `amalgamate.py`. 2 | 3 | #define NOMINMAX 4 | 5 | #include "safetyhook.hpp" 6 | 7 | 8 | // 9 | // Source file: allocator.cpp 10 | // 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | // 18 | // Header: safetyhook/os.hpp 19 | // 20 | 21 | // This is the OS abstraction layer. 22 | #pragma once 23 | 24 | #ifndef SAFETYHOOK_USE_CXXMODULES 25 | #include 26 | #include 27 | #include 28 | #else 29 | import std.compat; 30 | #endif 31 | 32 | namespace safetyhook { 33 | 34 | enum class OsError { 35 | FAILED_TO_ALLOCATE, 36 | FAILED_TO_PROTECT, 37 | FAILED_TO_QUERY, 38 | FAILED_TO_GET_NEXT_THREAD, 39 | FAILED_TO_GET_THREAD_CONTEXT, 40 | FAILED_TO_SET_THREAD_CONTEXT, 41 | FAILED_TO_FREEZE_THREAD, 42 | FAILED_TO_UNFREEZE_THREAD, 43 | FAILED_TO_GET_THREAD_ID, 44 | }; 45 | 46 | struct VmAccess { 47 | bool read : 1; 48 | bool write : 1; 49 | bool execute : 1; 50 | 51 | constexpr bool operator==(const VmAccess& other) const { 52 | return read == other.read && write == other.write && execute == other.execute; 53 | } 54 | }; 55 | 56 | constexpr VmAccess VM_ACCESS_R{.read = true, .write = false, .execute = false}; 57 | constexpr VmAccess VM_ACCESS_RW{.read = true, .write = true, .execute = false}; 58 | constexpr VmAccess VM_ACCESS_RX{.read = true, .write = false, .execute = true}; 59 | constexpr VmAccess VM_ACCESS_RWX{.read = true, .write = true, .execute = true}; 60 | 61 | struct VmBasicInfo { 62 | uint8_t* address; 63 | size_t size; 64 | VmAccess access; 65 | bool is_free; 66 | }; 67 | 68 | std::expected vm_allocate(uint8_t* address, size_t size, VmAccess access); 69 | void vm_free(uint8_t* address); 70 | std::expected vm_protect(uint8_t* address, size_t size, VmAccess access); 71 | std::expected vm_protect(uint8_t* address, size_t size, uint32_t access); 72 | std::expected vm_query(uint8_t* address); 73 | bool vm_is_readable(uint8_t* address, size_t size); 74 | bool vm_is_writable(uint8_t* address, size_t size); 75 | bool vm_is_executable(uint8_t* address); 76 | 77 | struct SystemInfo { 78 | uint32_t page_size; 79 | uint32_t allocation_granularity; 80 | uint8_t* min_address; 81 | uint8_t* max_address; 82 | }; 83 | 84 | SystemInfo system_info(); 85 | 86 | using ThreadId = uint32_t; 87 | using ThreadHandle = void*; 88 | using ThreadContext = void*; 89 | 90 | /// @brief Executes a function while all other threads are frozen. Also allows for visiting each frozen thread and 91 | /// modifying it's context. 92 | /// @param run_fn The function to run while all other threads are frozen. 93 | /// @param visit_fn The function that will be called for each frozen thread. 94 | /// @note The visit function will be called in the order that the threads were frozen. 95 | /// @note The visit function will be called before the run function. 96 | /// @note Keep the logic inside run_fn and visit_fn as simple as possible to avoid deadlocks. 97 | void execute_while_frozen(const std::function& run_fn, 98 | const std::function& visit_fn = {}); 99 | 100 | /// @brief Will modify the context of a thread's IP to point to a new address if its IP is at the old address. 101 | /// @param ctx The thread context to modify. 102 | /// @param old_ip The old IP address. 103 | /// @param new_ip The new IP address. 104 | void fix_ip(ThreadContext ctx, uint8_t* old_ip, uint8_t* new_ip); 105 | 106 | } // namespace safetyhook 107 | 108 | 109 | namespace safetyhook { 110 | Allocation::Allocation(Allocation&& other) noexcept { 111 | *this = std::move(other); 112 | } 113 | 114 | Allocation& Allocation::operator=(Allocation&& other) noexcept { 115 | if (this != &other) { 116 | free(); 117 | 118 | m_allocator = std::move(other.m_allocator); 119 | m_address = other.m_address; 120 | m_size = other.m_size; 121 | 122 | other.m_address = nullptr; 123 | other.m_size = 0; 124 | } 125 | 126 | return *this; 127 | } 128 | 129 | Allocation::~Allocation() { 130 | free(); 131 | } 132 | 133 | void Allocation::free() { 134 | if (m_allocator && m_address != nullptr && m_size != 0) { 135 | m_allocator->free(m_address, m_size); 136 | m_address = nullptr; 137 | m_size = 0; 138 | m_allocator.reset(); 139 | } 140 | } 141 | 142 | Allocation::Allocation(std::shared_ptr allocator, uint8_t* address, size_t size) noexcept 143 | : m_allocator{std::move(allocator)}, m_address{address}, m_size{size} { 144 | } 145 | 146 | std::shared_ptr Allocator::global() { 147 | static std::weak_ptr global_allocator{}; 148 | static std::mutex global_allocator_mutex{}; 149 | 150 | std::scoped_lock lock{global_allocator_mutex}; 151 | 152 | if (auto allocator = global_allocator.lock()) { 153 | return allocator; 154 | } 155 | 156 | auto allocator = Allocator::create(); 157 | 158 | global_allocator = allocator; 159 | 160 | return allocator; 161 | } 162 | 163 | std::shared_ptr Allocator::create() { 164 | return std::shared_ptr{new Allocator{}}; 165 | } 166 | 167 | std::expected Allocator::allocate(size_t size) { 168 | return allocate_near({}, size, std::numeric_limits::max()); 169 | } 170 | 171 | std::expected Allocator::allocate_near( 172 | const std::vector& desired_addresses, size_t size, size_t max_distance) { 173 | std::scoped_lock lock{m_mutex}; 174 | return internal_allocate_near(desired_addresses, size, max_distance); 175 | } 176 | 177 | void Allocator::free(uint8_t* address, size_t size) { 178 | std::scoped_lock lock{m_mutex}; 179 | return internal_free(address, size); 180 | } 181 | 182 | std::expected Allocator::internal_allocate_near( 183 | const std::vector& desired_addresses, size_t size, size_t max_distance) { 184 | // First search through our list of allocations for a free block that is large 185 | // enough. 186 | for (const auto& allocation : m_memory) { 187 | if (allocation->size < size) { 188 | continue; 189 | } 190 | 191 | for (auto node = allocation->freelist.get(); node != nullptr; node = node->next.get()) { 192 | // Enough room? 193 | if (static_cast(node->end - node->start) < size) { 194 | continue; 195 | } 196 | 197 | const auto address = node->start; 198 | 199 | // Close enough? 200 | if (!in_range(address, desired_addresses, max_distance)) { 201 | continue; 202 | } 203 | 204 | node->start += size; 205 | 206 | return Allocation{shared_from_this(), address, size}; 207 | } 208 | } 209 | 210 | // If we didn't find a free block, we need to allocate a new one. 211 | auto allocation_size = align_up(size, system_info().allocation_granularity); 212 | auto allocation_address = allocate_nearby_memory(desired_addresses, allocation_size, max_distance); 213 | 214 | if (!allocation_address) { 215 | return std::unexpected{allocation_address.error()}; 216 | } 217 | 218 | auto& allocation = m_memory.emplace_back(new Memory); 219 | 220 | allocation->address = *allocation_address; 221 | allocation->size = allocation_size; 222 | allocation->freelist = std::make_unique(); 223 | allocation->freelist->start = *allocation_address + size; 224 | allocation->freelist->end = *allocation_address + allocation_size; 225 | 226 | return Allocation{shared_from_this(), *allocation_address, size}; 227 | } 228 | 229 | void Allocator::internal_free(uint8_t* address, size_t size) { 230 | for (const auto& allocation : m_memory) { 231 | if (allocation->address > address || allocation->address + allocation->size < address) { 232 | continue; 233 | } 234 | 235 | // Find the right place for our new freenode. 236 | FreeNode* prev{}; 237 | 238 | for (auto node = allocation->freelist.get(); node != nullptr; prev = node, node = node->next.get()) { 239 | if (node->start > address) { 240 | break; 241 | } 242 | } 243 | 244 | // Add new freenode. 245 | auto free_node = std::make_unique(); 246 | 247 | free_node->start = address; 248 | free_node->end = address + size; 249 | 250 | if (prev == nullptr) { 251 | free_node->next.swap(allocation->freelist); 252 | allocation->freelist.swap(free_node); 253 | } else { 254 | free_node->next.swap(prev->next); 255 | prev->next.swap(free_node); 256 | } 257 | 258 | combine_adjacent_freenodes(*allocation); 259 | break; 260 | } 261 | } 262 | 263 | void Allocator::combine_adjacent_freenodes(Memory& memory) { 264 | for (auto prev = memory.freelist.get(), node = prev; node != nullptr; node = node->next.get()) { 265 | if (prev->end == node->start) { 266 | prev->end = node->end; 267 | prev->next.swap(node->next); 268 | node->next.reset(); 269 | node = prev; 270 | } else { 271 | prev = node; 272 | } 273 | } 274 | } 275 | 276 | std::expected Allocator::allocate_nearby_memory( 277 | const std::vector& desired_addresses, size_t size, size_t max_distance) { 278 | if (desired_addresses.empty()) { 279 | if (auto result = vm_allocate(nullptr, size, VM_ACCESS_RWX)) { 280 | return result.value(); 281 | } 282 | 283 | return std::unexpected{Error::BAD_VIRTUAL_ALLOC}; 284 | } 285 | 286 | auto attempt_allocation = [&](uint8_t* p) -> uint8_t* { 287 | if (!in_range(p, desired_addresses, max_distance)) { 288 | return nullptr; 289 | } 290 | 291 | if (auto result = vm_allocate(p, size, VM_ACCESS_RWX)) { 292 | return result.value(); 293 | } 294 | 295 | return nullptr; 296 | }; 297 | 298 | auto si = system_info(); 299 | auto desired_address = desired_addresses[0]; 300 | auto search_start = si.min_address; 301 | auto search_end = si.max_address; 302 | 303 | if (static_cast(desired_address - search_start) > max_distance) { 304 | search_start = desired_address - max_distance; 305 | } 306 | 307 | if (static_cast(search_end - desired_address) > max_distance) { 308 | search_end = desired_address + max_distance; 309 | } 310 | 311 | search_start = std::max(search_start, si.min_address); 312 | search_end = std::min(search_end, si.max_address); 313 | desired_address = align_up(desired_address, si.allocation_granularity); 314 | VmBasicInfo mbi{}; 315 | 316 | // Search backwards from the desired_address. 317 | for (auto p = desired_address; p > search_start && in_range(p, desired_addresses, max_distance); 318 | p = align_down(mbi.address - 1, si.allocation_granularity)) { 319 | auto result = vm_query(p); 320 | 321 | if (!result) { 322 | break; 323 | } 324 | 325 | mbi = result.value(); 326 | 327 | if (!mbi.is_free) { 328 | continue; 329 | } 330 | 331 | if (auto allocation_address = attempt_allocation(p); allocation_address != nullptr) { 332 | return allocation_address; 333 | } 334 | } 335 | 336 | // Search forwards from the desired_address. 337 | for (auto p = desired_address; p < search_end && in_range(p, desired_addresses, max_distance); p += mbi.size) { 338 | auto result = vm_query(p); 339 | 340 | if (!result) { 341 | break; 342 | } 343 | 344 | mbi = result.value(); 345 | 346 | if (!mbi.is_free) { 347 | continue; 348 | } 349 | 350 | if (auto allocation_address = attempt_allocation(p); allocation_address != nullptr) { 351 | return allocation_address; 352 | } 353 | } 354 | 355 | return std::unexpected{Error::NO_MEMORY_IN_RANGE}; 356 | } 357 | 358 | bool Allocator::in_range(uint8_t* address, const std::vector& desired_addresses, size_t max_distance) { 359 | return std::ranges::all_of(desired_addresses, [&](const auto& desired_address) { 360 | const size_t delta = (address > desired_address) ? address - desired_address : desired_address - address; 361 | return delta <= max_distance; 362 | }); 363 | } 364 | 365 | Allocator::Memory::~Memory() { 366 | vm_free(address); 367 | } 368 | } // namespace safetyhook 369 | 370 | // 371 | // Source file: easy.cpp 372 | // 373 | 374 | 375 | namespace safetyhook { 376 | InlineHook create_inline(void* target, void* destination) { 377 | if (auto hook = InlineHook::create(target, destination)) { 378 | return std::move(*hook); 379 | } else { 380 | return {}; 381 | } 382 | } 383 | 384 | MidHook create_mid(void* target, MidHookFn destination) { 385 | if (auto hook = MidHook::create(target, destination)) { 386 | return std::move(*hook); 387 | } else { 388 | return {}; 389 | } 390 | } 391 | 392 | VmtHook create_vmt(void* object) { 393 | if (auto hook = VmtHook::create(object)) { 394 | return std::move(*hook); 395 | } else { 396 | return {}; 397 | } 398 | } 399 | } // namespace safetyhook 400 | 401 | // 402 | // Source file: inline_hook.cpp 403 | // 404 | 405 | #include 406 | 407 | #if __has_include("Zydis/Zydis.h") 408 | #include "Zydis/Zydis.h" 409 | #elif __has_include("Zydis.h") 410 | #include "Zydis.h" 411 | #else 412 | #error "Zydis not found" 413 | #endif 414 | 415 | 416 | 417 | namespace safetyhook { 418 | 419 | #pragma pack(push, 1) 420 | struct JmpE9 { 421 | uint8_t opcode{0xE9}; 422 | uint32_t offset{0}; 423 | }; 424 | 425 | #if SAFETYHOOK_ARCH_X86_64 426 | struct JmpFF { 427 | uint8_t opcode0{0xFF}; 428 | uint8_t opcode1{0x25}; 429 | uint32_t offset{0}; 430 | }; 431 | 432 | struct TrampolineEpilogueE9 { 433 | JmpE9 jmp_to_original{}; 434 | JmpFF jmp_to_destination{}; 435 | uint64_t destination_address{}; 436 | }; 437 | 438 | struct TrampolineEpilogueFF { 439 | JmpFF jmp_to_original{}; 440 | uint64_t original_address{}; 441 | }; 442 | #elif SAFETYHOOK_ARCH_X86_32 443 | struct TrampolineEpilogueE9 { 444 | JmpE9 jmp_to_original{}; 445 | JmpE9 jmp_to_destination{}; 446 | }; 447 | #endif 448 | #pragma pack(pop) 449 | 450 | #if SAFETYHOOK_ARCH_X86_64 451 | static auto make_jmp_ff(uint8_t* src, uint8_t* dst, uint8_t* data) { 452 | JmpFF jmp{}; 453 | 454 | jmp.offset = static_cast(data - src - sizeof(jmp)); 455 | store(data, dst); 456 | 457 | return jmp; 458 | } 459 | 460 | [[nodiscard]] static std::expected emit_jmp_ff( 461 | uint8_t* src, uint8_t* dst, uint8_t* data, size_t size = sizeof(JmpFF)) { 462 | if (size < sizeof(JmpFF)) { 463 | return std::unexpected{InlineHook::Error::not_enough_space(dst)}; 464 | } 465 | 466 | auto um = unprotect(src, size); 467 | 468 | if (!um) { 469 | return std::unexpected{InlineHook::Error::failed_to_unprotect(src)}; 470 | } 471 | 472 | if (size > sizeof(JmpFF)) { 473 | std::fill_n(src, size, static_cast(0x90)); 474 | } 475 | 476 | store(src, make_jmp_ff(src, dst, data)); 477 | 478 | return {}; 479 | } 480 | #endif 481 | 482 | constexpr auto make_jmp_e9(uint8_t* src, uint8_t* dst) { 483 | JmpE9 jmp{}; 484 | 485 | jmp.offset = static_cast(dst - src - sizeof(jmp)); 486 | 487 | return jmp; 488 | } 489 | 490 | [[nodiscard]] static std::expected emit_jmp_e9( 491 | uint8_t* src, uint8_t* dst, size_t size = sizeof(JmpE9)) { 492 | if (size < sizeof(JmpE9)) { 493 | return std::unexpected{InlineHook::Error::not_enough_space(dst)}; 494 | } 495 | 496 | auto um = unprotect(src, size); 497 | 498 | if (!um) { 499 | return std::unexpected{InlineHook::Error::failed_to_unprotect(src)}; 500 | } 501 | 502 | if (size > sizeof(JmpE9)) { 503 | std::fill_n(src, size, static_cast(0x90)); 504 | } 505 | 506 | store(src, make_jmp_e9(src, dst)); 507 | 508 | return {}; 509 | } 510 | 511 | static bool decode(ZydisDecodedInstruction* ix, uint8_t* ip) { 512 | ZydisDecoder decoder{}; 513 | ZyanStatus status; 514 | 515 | #if SAFETYHOOK_ARCH_X86_64 516 | status = ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64); 517 | #elif SAFETYHOOK_ARCH_X86_32 518 | status = ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LEGACY_32, ZYDIS_STACK_WIDTH_32); 519 | #endif 520 | 521 | if (!ZYAN_SUCCESS(status)) { 522 | return false; 523 | } 524 | 525 | return ZYAN_SUCCESS(ZydisDecoderDecodeInstruction(&decoder, nullptr, ip, 15, ix)); 526 | } 527 | 528 | std::expected InlineHook::create(void* target, void* destination) { 529 | return create(Allocator::global(), target, destination); 530 | } 531 | 532 | std::expected InlineHook::create( 533 | const std::shared_ptr& allocator, void* target, void* destination) { 534 | InlineHook hook{}; 535 | 536 | if (const auto setup_result = 537 | hook.setup(allocator, reinterpret_cast(target), reinterpret_cast(destination)); 538 | !setup_result) { 539 | return std::unexpected{setup_result.error()}; 540 | } 541 | 542 | return hook; 543 | } 544 | 545 | InlineHook::InlineHook(InlineHook&& other) noexcept { 546 | *this = std::move(other); 547 | } 548 | 549 | InlineHook& InlineHook::operator=(InlineHook&& other) noexcept { 550 | if (this != &other) { 551 | destroy(); 552 | 553 | std::scoped_lock lock{m_mutex, other.m_mutex}; 554 | 555 | m_target = other.m_target; 556 | m_destination = other.m_destination; 557 | m_trampoline = std::move(other.m_trampoline); 558 | m_trampoline_size = other.m_trampoline_size; 559 | m_original_bytes = std::move(other.m_original_bytes); 560 | 561 | other.m_target = nullptr; 562 | other.m_destination = nullptr; 563 | other.m_trampoline_size = 0; 564 | } 565 | 566 | return *this; 567 | } 568 | 569 | InlineHook::~InlineHook() { 570 | destroy(); 571 | } 572 | 573 | void InlineHook::reset() { 574 | *this = {}; 575 | } 576 | 577 | std::expected InlineHook::setup( 578 | const std::shared_ptr& allocator, uint8_t* target, uint8_t* destination) { 579 | m_target = target; 580 | m_destination = destination; 581 | 582 | if (auto e9_result = e9_hook(allocator); !e9_result) { 583 | #if SAFETYHOOK_ARCH_X86_64 584 | if (auto ff_result = ff_hook(allocator); !ff_result) { 585 | return ff_result; 586 | } 587 | #elif SAFETYHOOK_ARCH_X86_32 588 | return e9_result; 589 | #endif 590 | } 591 | 592 | return {}; 593 | } 594 | 595 | std::expected InlineHook::e9_hook(const std::shared_ptr& allocator) { 596 | m_original_bytes.clear(); 597 | m_trampoline_size = sizeof(TrampolineEpilogueE9); 598 | 599 | std::vector desired_addresses{m_target}; 600 | ZydisDecodedInstruction ix{}; 601 | 602 | for (auto ip = m_target; ip < m_target + sizeof(JmpE9); ip += ix.length) { 603 | if (!decode(&ix, ip)) { 604 | return std::unexpected{Error::failed_to_decode_instruction(ip)}; 605 | } 606 | 607 | m_trampoline_size += ix.length; 608 | m_original_bytes.insert(m_original_bytes.end(), ip, ip + ix.length); 609 | 610 | const auto is_relative = (ix.attributes & ZYDIS_ATTRIB_IS_RELATIVE) != 0; 611 | 612 | if (is_relative) { 613 | if (ix.raw.disp.size == 32) { 614 | const auto target_address = ip + ix.length + static_cast(ix.raw.disp.value); 615 | desired_addresses.emplace_back(target_address); 616 | } else if (ix.raw.imm[0].size == 32) { 617 | const auto target_address = ip + ix.length + static_cast(ix.raw.imm[0].value.s); 618 | desired_addresses.emplace_back(target_address); 619 | } else if (ix.meta.category == ZYDIS_CATEGORY_COND_BR && ix.meta.branch_type == ZYDIS_BRANCH_TYPE_SHORT) { 620 | const auto target_address = ip + ix.length + static_cast(ix.raw.imm[0].value.s); 621 | desired_addresses.emplace_back(target_address); 622 | m_trampoline_size += 4; // near conditional branches are 4 bytes larger. 623 | } else if (ix.meta.category == ZYDIS_CATEGORY_UNCOND_BR && ix.meta.branch_type == ZYDIS_BRANCH_TYPE_SHORT) { 624 | const auto target_address = ip + ix.length + static_cast(ix.raw.imm[0].value.s); 625 | desired_addresses.emplace_back(target_address); 626 | m_trampoline_size += 3; // near unconditional branches are 3 bytes larger. 627 | } else { 628 | return std::unexpected{Error::unsupported_instruction_in_trampoline(ip)}; 629 | } 630 | } 631 | } 632 | 633 | auto trampoline_allocation = allocator->allocate_near(desired_addresses, m_trampoline_size); 634 | 635 | if (!trampoline_allocation) { 636 | return std::unexpected{Error::bad_allocation(trampoline_allocation.error())}; 637 | } 638 | 639 | m_trampoline = std::move(*trampoline_allocation); 640 | 641 | for (auto ip = m_target, tramp_ip = m_trampoline.data(); ip < m_target + m_original_bytes.size(); ip += ix.length) { 642 | if (!decode(&ix, ip)) { 643 | m_trampoline.free(); 644 | return std::unexpected{Error::failed_to_decode_instruction(ip)}; 645 | } 646 | 647 | const auto is_relative = (ix.attributes & ZYDIS_ATTRIB_IS_RELATIVE) != 0; 648 | 649 | if (is_relative && ix.raw.disp.size == 32) { 650 | std::copy_n(ip, ix.length, tramp_ip); 651 | const auto target_address = ip + ix.length + ix.raw.disp.value; 652 | const auto new_disp = target_address - (tramp_ip + ix.length); 653 | store(tramp_ip + ix.raw.disp.offset, static_cast(new_disp)); 654 | tramp_ip += ix.length; 655 | } else if (is_relative && ix.raw.imm[0].size == 32) { 656 | std::copy_n(ip, ix.length, tramp_ip); 657 | const auto target_address = ip + ix.length + ix.raw.imm[0].value.s; 658 | const auto new_disp = target_address - (tramp_ip + ix.length); 659 | store(tramp_ip + ix.raw.imm[0].offset, static_cast(new_disp)); 660 | tramp_ip += ix.length; 661 | } else if (ix.meta.category == ZYDIS_CATEGORY_COND_BR && ix.meta.branch_type == ZYDIS_BRANCH_TYPE_SHORT) { 662 | const auto target_address = ip + ix.length + ix.raw.imm[0].value.s; 663 | auto new_disp = target_address - (tramp_ip + 6); 664 | 665 | // Handle the case where the target is now in the trampoline. 666 | if (target_address < m_target + m_original_bytes.size()) { 667 | new_disp = static_cast(ix.raw.imm[0].value.s); 668 | } 669 | 670 | *tramp_ip = 0x0F; 671 | *(tramp_ip + 1) = 0x10 + ix.opcode; 672 | store(tramp_ip + 2, static_cast(new_disp)); 673 | tramp_ip += 6; 674 | } else if (ix.meta.category == ZYDIS_CATEGORY_UNCOND_BR && ix.meta.branch_type == ZYDIS_BRANCH_TYPE_SHORT) { 675 | const auto target_address = ip + ix.length + ix.raw.imm[0].value.s; 676 | auto new_disp = target_address - (tramp_ip + 5); 677 | 678 | // Handle the case where the target is now in the trampoline. 679 | if (target_address < m_target + m_original_bytes.size()) { 680 | new_disp = static_cast(ix.raw.imm[0].value.s); 681 | } 682 | 683 | *tramp_ip = 0xE9; 684 | store(tramp_ip + 1, static_cast(new_disp)); 685 | tramp_ip += 5; 686 | } else { 687 | std::copy_n(ip, ix.length, tramp_ip); 688 | tramp_ip += ix.length; 689 | } 690 | } 691 | 692 | auto trampoline_epilogue = reinterpret_cast( 693 | m_trampoline.address() + m_trampoline_size - sizeof(TrampolineEpilogueE9)); 694 | 695 | // jmp from trampoline to original. 696 | auto src = reinterpret_cast(&trampoline_epilogue->jmp_to_original); 697 | auto dst = m_target + m_original_bytes.size(); 698 | 699 | if (auto result = emit_jmp_e9(src, dst); !result) { 700 | return std::unexpected{result.error()}; 701 | } 702 | 703 | // jmp from trampoline to destination. 704 | src = reinterpret_cast(&trampoline_epilogue->jmp_to_destination); 705 | dst = m_destination; 706 | 707 | #if SAFETYHOOK_ARCH_X86_64 708 | auto data = reinterpret_cast(&trampoline_epilogue->destination_address); 709 | 710 | if (auto result = emit_jmp_ff(src, dst, data); !result) { 711 | return std::unexpected{result.error()}; 712 | } 713 | #elif SAFETYHOOK_ARCH_X86_32 714 | if (auto result = emit_jmp_e9(src, dst); !result) { 715 | return std::unexpected{result.error()}; 716 | } 717 | #endif 718 | 719 | std::optional error; 720 | 721 | // jmp from original to trampoline. 722 | execute_while_frozen( 723 | [this, &trampoline_epilogue, &error] { 724 | if (auto result = emit_jmp_e9(m_target, 725 | reinterpret_cast(&trampoline_epilogue->jmp_to_destination), m_original_bytes.size()); 726 | !result) { 727 | error = result.error(); 728 | } 729 | }, 730 | [this](auto, auto, auto ctx) { 731 | for (size_t i = 0; i < m_original_bytes.size(); ++i) { 732 | fix_ip(ctx, m_target + i, m_trampoline.data() + i); 733 | } 734 | }); 735 | 736 | if (error) { 737 | return std::unexpected{*error}; 738 | } 739 | 740 | return {}; 741 | } 742 | 743 | #if SAFETYHOOK_ARCH_X86_64 744 | std::expected InlineHook::ff_hook(const std::shared_ptr& allocator) { 745 | m_original_bytes.clear(); 746 | m_trampoline_size = sizeof(TrampolineEpilogueFF); 747 | ZydisDecodedInstruction ix{}; 748 | 749 | for (auto ip = m_target; ip < m_target + sizeof(JmpFF) + sizeof(uintptr_t); ip += ix.length) { 750 | if (!decode(&ix, ip)) { 751 | return std::unexpected{Error::failed_to_decode_instruction(ip)}; 752 | } 753 | 754 | // We can't support any instruction that is IP relative here because 755 | // ff_hook should only be called if e9_hook failed indicating that 756 | // we're likely outside the +- 2GB range. 757 | if (ix.attributes & ZYDIS_ATTRIB_IS_RELATIVE) { 758 | return std::unexpected{Error::ip_relative_instruction_out_of_range(ip)}; 759 | } 760 | 761 | m_original_bytes.insert(m_original_bytes.end(), ip, ip + ix.length); 762 | m_trampoline_size += ix.length; 763 | } 764 | 765 | auto trampoline_allocation = allocator->allocate(m_trampoline_size); 766 | 767 | if (!trampoline_allocation) { 768 | return std::unexpected{Error::bad_allocation(trampoline_allocation.error())}; 769 | } 770 | 771 | m_trampoline = std::move(*trampoline_allocation); 772 | 773 | std::copy(m_original_bytes.begin(), m_original_bytes.end(), m_trampoline.data()); 774 | 775 | const auto trampoline_epilogue = 776 | reinterpret_cast(m_trampoline.data() + m_trampoline_size - sizeof(TrampolineEpilogueFF)); 777 | 778 | // jmp from trampoline to original. 779 | auto src = reinterpret_cast(&trampoline_epilogue->jmp_to_original); 780 | auto dst = m_target + m_original_bytes.size(); 781 | auto data = reinterpret_cast(&trampoline_epilogue->original_address); 782 | 783 | if (auto result = emit_jmp_ff(src, dst, data); !result) { 784 | return std::unexpected{result.error()}; 785 | } 786 | 787 | std::optional error; 788 | 789 | // jmp from original to trampoline. 790 | execute_while_frozen( 791 | [this, &error] { 792 | if (auto result = emit_jmp_ff(m_target, m_destination, m_target + sizeof(JmpFF), m_original_bytes.size()); 793 | !result) { 794 | error = result.error(); 795 | } 796 | }, 797 | [this](auto, auto, auto ctx) { 798 | for (size_t i = 0; i < m_original_bytes.size(); ++i) { 799 | fix_ip(ctx, m_target + i, m_trampoline.data() + i); 800 | } 801 | }); 802 | 803 | if (error) { 804 | return std::unexpected{*error}; 805 | } 806 | 807 | return {}; 808 | } 809 | #endif 810 | 811 | void InlineHook::destroy() { 812 | std::scoped_lock lock{m_mutex}; 813 | 814 | if (!m_trampoline) { 815 | return; 816 | } 817 | 818 | execute_while_frozen( 819 | [this] { 820 | if (auto um = unprotect(m_target, m_original_bytes.size())) { 821 | std::copy(m_original_bytes.begin(), m_original_bytes.end(), m_target); 822 | } 823 | }, 824 | [this](auto, auto, auto ctx) { 825 | for (size_t i = 0; i < m_original_bytes.size(); ++i) { 826 | fix_ip(ctx, m_trampoline.data() + i, m_target + i); 827 | } 828 | }); 829 | 830 | m_trampoline.free(); 831 | } 832 | } // namespace safetyhook 833 | 834 | // 835 | // Source file: mid_hook.cpp 836 | // 837 | 838 | #include 839 | #include 840 | 841 | 842 | 843 | namespace safetyhook { 844 | 845 | #if SAFETYHOOK_ARCH_X86_64 846 | #if SAFETYHOOK_OS_WINDOWS 847 | constexpr std::array asm_data = {0xFF, 0x35, 0x79, 0x01, 0x00, 0x00, 0x54, 0x54, 0x55, 0x50, 0x53, 0x51, 848 | 0x52, 0x56, 0x57, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57, 849 | 0x9C, 0x48, 0x81, 0xEC, 0x00, 0x01, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0xBC, 0x24, 0xF0, 0x00, 0x00, 0x00, 0xF3, 850 | 0x44, 0x0F, 0x7F, 0xB4, 0x24, 0xE0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0xAC, 0x24, 0xD0, 0x00, 0x00, 0x00, 851 | 0xF3, 0x44, 0x0F, 0x7F, 0xA4, 0x24, 0xC0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0x9C, 0x24, 0xB0, 0x00, 0x00, 852 | 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0x94, 0x24, 0xA0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0x8C, 0x24, 0x90, 0x00, 853 | 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0x84, 0x24, 0x80, 0x00, 0x00, 0x00, 0xF3, 0x0F, 0x7F, 0x7C, 0x24, 0x70, 0xF3, 854 | 0x0F, 0x7F, 0x74, 0x24, 0x60, 0xF3, 0x0F, 0x7F, 0x6C, 0x24, 0x50, 0xF3, 0x0F, 0x7F, 0x64, 0x24, 0x40, 0xF3, 0x0F, 855 | 0x7F, 0x5C, 0x24, 0x30, 0xF3, 0x0F, 0x7F, 0x54, 0x24, 0x20, 0xF3, 0x0F, 0x7F, 0x4C, 0x24, 0x10, 0xF3, 0x0F, 0x7F, 856 | 0x04, 0x24, 0x48, 0x8B, 0x8C, 0x24, 0x80, 0x01, 0x00, 0x00, 0x48, 0x83, 0xC1, 0x10, 0x48, 0x89, 0x8C, 0x24, 0x80, 857 | 0x01, 0x00, 0x00, 0x48, 0x8D, 0x0C, 0x24, 0x48, 0x89, 0xE3, 0x48, 0x83, 0xEC, 0x30, 0x48, 0x83, 0xE4, 0xF0, 0xFF, 858 | 0x15, 0xA8, 0x00, 0x00, 0x00, 0x48, 0x89, 0xDC, 0xF3, 0x0F, 0x6F, 0x04, 0x24, 0xF3, 0x0F, 0x6F, 0x4C, 0x24, 0x10, 859 | 0xF3, 0x0F, 0x6F, 0x54, 0x24, 0x20, 0xF3, 0x0F, 0x6F, 0x5C, 0x24, 0x30, 0xF3, 0x0F, 0x6F, 0x64, 0x24, 0x40, 0xF3, 860 | 0x0F, 0x6F, 0x6C, 0x24, 0x50, 0xF3, 0x0F, 0x6F, 0x74, 0x24, 0x60, 0xF3, 0x0F, 0x6F, 0x7C, 0x24, 0x70, 0xF3, 0x44, 861 | 0x0F, 0x6F, 0x84, 0x24, 0x80, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0x8C, 0x24, 0x90, 0x00, 0x00, 0x00, 0xF3, 862 | 0x44, 0x0F, 0x6F, 0x94, 0x24, 0xA0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0x9C, 0x24, 0xB0, 0x00, 0x00, 0x00, 863 | 0xF3, 0x44, 0x0F, 0x6F, 0xA4, 0x24, 0xC0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0xAC, 0x24, 0xD0, 0x00, 0x00, 864 | 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0xB4, 0x24, 0xE0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0xBC, 0x24, 0xF0, 0x00, 865 | 0x00, 0x00, 0x48, 0x81, 0xC4, 0x00, 0x01, 0x00, 0x00, 0x9D, 0x41, 0x5F, 0x41, 0x5E, 0x41, 0x5D, 0x41, 0x5C, 0x41, 866 | 0x5B, 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58, 0x5F, 0x5E, 0x5A, 0x59, 0x5B, 0x58, 0x5D, 0x48, 0x8D, 0x64, 0x24, 0x08, 867 | 0x5C, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 868 | #elif SAFETYHOOK_OS_LINUX 869 | constexpr std::array asm_data = {0xFF, 0x35, 0x79, 0x01, 0x00, 0x00, 0x54, 0x54, 0x55, 0x50, 0x53, 0x51, 870 | 0x52, 0x56, 0x57, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57, 871 | 0x9C, 0x48, 0x81, 0xEC, 0x00, 0x01, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0xBC, 0x24, 0xF0, 0x00, 0x00, 0x00, 0xF3, 872 | 0x44, 0x0F, 0x7F, 0xB4, 0x24, 0xE0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0xAC, 0x24, 0xD0, 0x00, 0x00, 0x00, 873 | 0xF3, 0x44, 0x0F, 0x7F, 0xA4, 0x24, 0xC0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0x9C, 0x24, 0xB0, 0x00, 0x00, 874 | 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0x94, 0x24, 0xA0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0x8C, 0x24, 0x90, 0x00, 875 | 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0x84, 0x24, 0x80, 0x00, 0x00, 0x00, 0xF3, 0x0F, 0x7F, 0x7C, 0x24, 0x70, 0xF3, 876 | 0x0F, 0x7F, 0x74, 0x24, 0x60, 0xF3, 0x0F, 0x7F, 0x6C, 0x24, 0x50, 0xF3, 0x0F, 0x7F, 0x64, 0x24, 0x40, 0xF3, 0x0F, 877 | 0x7F, 0x5C, 0x24, 0x30, 0xF3, 0x0F, 0x7F, 0x54, 0x24, 0x20, 0xF3, 0x0F, 0x7F, 0x4C, 0x24, 0x10, 0xF3, 0x0F, 0x7F, 878 | 0x04, 0x24, 0x48, 0x8B, 0xBC, 0x24, 0x80, 0x01, 0x00, 0x00, 0x48, 0x83, 0xC7, 0x10, 0x48, 0x89, 0xBC, 0x24, 0x80, 879 | 0x01, 0x00, 0x00, 0x48, 0x8D, 0x3C, 0x24, 0x48, 0x89, 0xE3, 0x48, 0x83, 0xEC, 0x30, 0x48, 0x83, 0xE4, 0xF0, 0xFF, 880 | 0x15, 0xA8, 0x00, 0x00, 0x00, 0x48, 0x89, 0xDC, 0xF3, 0x0F, 0x6F, 0x04, 0x24, 0xF3, 0x0F, 0x6F, 0x4C, 0x24, 0x10, 881 | 0xF3, 0x0F, 0x6F, 0x54, 0x24, 0x20, 0xF3, 0x0F, 0x6F, 0x5C, 0x24, 0x30, 0xF3, 0x0F, 0x6F, 0x64, 0x24, 0x40, 0xF3, 882 | 0x0F, 0x6F, 0x6C, 0x24, 0x50, 0xF3, 0x0F, 0x6F, 0x74, 0x24, 0x60, 0xF3, 0x0F, 0x6F, 0x7C, 0x24, 0x70, 0xF3, 0x44, 883 | 0x0F, 0x6F, 0x84, 0x24, 0x80, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0x8C, 0x24, 0x90, 0x00, 0x00, 0x00, 0xF3, 884 | 0x44, 0x0F, 0x6F, 0x94, 0x24, 0xA0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0x9C, 0x24, 0xB0, 0x00, 0x00, 0x00, 885 | 0xF3, 0x44, 0x0F, 0x6F, 0xA4, 0x24, 0xC0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0xAC, 0x24, 0xD0, 0x00, 0x00, 886 | 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0xB4, 0x24, 0xE0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0xBC, 0x24, 0xF0, 0x00, 887 | 0x00, 0x00, 0x48, 0x81, 0xC4, 0x00, 0x01, 0x00, 0x00, 0x9D, 0x41, 0x5F, 0x41, 0x5E, 0x41, 0x5D, 0x41, 0x5C, 0x41, 888 | 0x5B, 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58, 0x5F, 0x5E, 0x5A, 0x59, 0x5B, 0x58, 0x5D, 0x48, 0x8D, 0x64, 0x24, 0x08, 889 | 0x5C, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 890 | #endif 891 | #elif SAFETYHOOK_ARCH_X86_32 892 | constexpr std::array asm_data = {0xFF, 0x35, 0xA7, 0x00, 0x00, 0x00, 0x54, 0x54, 0x55, 0x50, 0x53, 0x51, 893 | 0x52, 0x56, 0x57, 0x9C, 0x81, 0xEC, 0x80, 0x00, 0x00, 0x00, 0xF3, 0x0F, 0x7F, 0x7C, 0x24, 0x70, 0xF3, 0x0F, 0x7F, 894 | 0x74, 0x24, 0x60, 0xF3, 0x0F, 0x7F, 0x6C, 0x24, 0x50, 0xF3, 0x0F, 0x7F, 0x64, 0x24, 0x40, 0xF3, 0x0F, 0x7F, 0x5C, 895 | 0x24, 0x30, 0xF3, 0x0F, 0x7F, 0x54, 0x24, 0x20, 0xF3, 0x0F, 0x7F, 0x4C, 0x24, 0x10, 0xF3, 0x0F, 0x7F, 0x04, 0x24, 896 | 0x8B, 0x8C, 0x24, 0xA0, 0x00, 0x00, 0x00, 0x83, 0xC1, 0x08, 0x89, 0x8C, 0x24, 0xA0, 0x00, 0x00, 0x00, 0x54, 0xFF, 897 | 0x15, 0xA3, 0x00, 0x00, 0x00, 0x83, 0xC4, 0x04, 0xF3, 0x0F, 0x6F, 0x04, 0x24, 0xF3, 0x0F, 0x6F, 0x4C, 0x24, 0x10, 898 | 0xF3, 0x0F, 0x6F, 0x54, 0x24, 0x20, 0xF3, 0x0F, 0x6F, 0x5C, 0x24, 0x30, 0xF3, 0x0F, 0x6F, 0x64, 0x24, 0x40, 0xF3, 899 | 0x0F, 0x6F, 0x6C, 0x24, 0x50, 0xF3, 0x0F, 0x6F, 0x74, 0x24, 0x60, 0xF3, 0x0F, 0x6F, 0x7C, 0x24, 0x70, 0x81, 0xC4, 900 | 0x80, 0x00, 0x00, 0x00, 0x9D, 0x5F, 0x5E, 0x5A, 0x59, 0x5B, 0x58, 0x5D, 0x8D, 0x64, 0x24, 0x04, 0x5C, 0xC3, 0x00, 901 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 902 | #endif 903 | 904 | std::expected MidHook::create(void* target, MidHookFn destination) { 905 | return create(Allocator::global(), target, destination); 906 | } 907 | 908 | std::expected MidHook::create( 909 | const std::shared_ptr& allocator, void* target, MidHookFn destination) { 910 | MidHook hook{}; 911 | 912 | if (const auto setup_result = hook.setup(allocator, reinterpret_cast(target), destination); 913 | !setup_result) { 914 | return std::unexpected{setup_result.error()}; 915 | } 916 | 917 | return hook; 918 | } 919 | 920 | MidHook::MidHook(MidHook&& other) noexcept { 921 | *this = std::move(other); 922 | } 923 | 924 | MidHook& MidHook::operator=(MidHook&& other) noexcept { 925 | if (this != &other) { 926 | m_hook = std::move(other.m_hook); 927 | m_target = other.m_target; 928 | m_stub = std::move(other.m_stub); 929 | m_destination = other.m_destination; 930 | 931 | other.m_target = 0; 932 | other.m_destination = nullptr; 933 | } 934 | 935 | return *this; 936 | } 937 | 938 | void MidHook::reset() { 939 | *this = {}; 940 | } 941 | 942 | std::expected MidHook::setup( 943 | const std::shared_ptr& allocator, uint8_t* target, MidHookFn destination_fn) { 944 | m_target = target; 945 | m_destination = destination_fn; 946 | 947 | auto stub_allocation = allocator->allocate(asm_data.size()); 948 | 949 | if (!stub_allocation) { 950 | return std::unexpected{Error::bad_allocation(stub_allocation.error())}; 951 | } 952 | 953 | m_stub = std::move(*stub_allocation); 954 | 955 | std::copy(asm_data.begin(), asm_data.end(), m_stub.data()); 956 | 957 | #if SAFETYHOOK_ARCH_X86_64 958 | store(m_stub.data() + sizeof(asm_data) - 16, m_destination); 959 | #elif SAFETYHOOK_ARCH_X86_32 960 | store(m_stub.data() + sizeof(asm_data) - 8, m_destination); 961 | 962 | // 32-bit has some relocations we need to fix up as well. 963 | store(m_stub.data() + 0x02, m_stub.data() + m_stub.size() - 4); 964 | store(m_stub.data() + 0x59, m_stub.data() + m_stub.size() - 8); 965 | #endif 966 | 967 | auto hook_result = InlineHook::create(allocator, m_target, m_stub.data()); 968 | 969 | if (!hook_result) { 970 | m_stub.free(); 971 | return std::unexpected{Error::bad_inline_hook(hook_result.error())}; 972 | } 973 | 974 | m_hook = std::move(*hook_result); 975 | 976 | #if SAFETYHOOK_ARCH_X86_64 977 | store(m_stub.data() + sizeof(asm_data) - 8, m_hook.trampoline().data()); 978 | #elif SAFETYHOOK_ARCH_X86_32 979 | store(m_stub.data() + sizeof(asm_data) - 4, m_hook.trampoline().data()); 980 | #endif 981 | 982 | return {}; 983 | } 984 | } // namespace safetyhook 985 | 986 | // 987 | // Source file: os.linux.cpp 988 | // 989 | 990 | 991 | #if SAFETYHOOK_OS_LINUX 992 | 993 | #include 994 | 995 | #include 996 | #include 997 | 998 | 999 | 1000 | namespace safetyhook { 1001 | std::expected vm_allocate(uint8_t* address, size_t size, VmAccess access) { 1002 | int prot = 0; 1003 | int flags = MAP_PRIVATE | MAP_ANONYMOUS; 1004 | 1005 | if (access == VM_ACCESS_R) { 1006 | prot = PROT_READ; 1007 | } else if (access == VM_ACCESS_RW) { 1008 | prot = PROT_READ | PROT_WRITE; 1009 | } else if (access == VM_ACCESS_RX) { 1010 | prot = PROT_READ | PROT_EXEC; 1011 | } else if (access == VM_ACCESS_RWX) { 1012 | prot = PROT_READ | PROT_WRITE | PROT_EXEC; 1013 | } else { 1014 | return std::unexpected{OsError::FAILED_TO_ALLOCATE}; 1015 | } 1016 | 1017 | auto* result = mmap(address, size, prot, flags, -1, 0); 1018 | 1019 | if (result == MAP_FAILED) { 1020 | return std::unexpected{OsError::FAILED_TO_ALLOCATE}; 1021 | } 1022 | 1023 | return static_cast(result); 1024 | } 1025 | 1026 | void vm_free(uint8_t* address) { 1027 | munmap(address, 0); 1028 | } 1029 | 1030 | std::expected vm_protect(uint8_t* address, size_t size, VmAccess access) { 1031 | int prot = 0; 1032 | 1033 | if (access == VM_ACCESS_R) { 1034 | prot = PROT_READ; 1035 | } else if (access == VM_ACCESS_RW) { 1036 | prot = PROT_READ | PROT_WRITE; 1037 | } else if (access == VM_ACCESS_RX) { 1038 | prot = PROT_READ | PROT_EXEC; 1039 | } else if (access == VM_ACCESS_RWX) { 1040 | prot = PROT_READ | PROT_WRITE | PROT_EXEC; 1041 | } else { 1042 | return std::unexpected{OsError::FAILED_TO_PROTECT}; 1043 | } 1044 | 1045 | return vm_protect(address, size, prot); 1046 | } 1047 | 1048 | std::expected vm_protect(uint8_t* address, size_t size, uint32_t protect) { 1049 | auto mbi = vm_query(address); 1050 | 1051 | if (!mbi.has_value()) { 1052 | return std::unexpected{OsError::FAILED_TO_PROTECT}; 1053 | } 1054 | 1055 | uint32_t old_protect = 0; 1056 | 1057 | if (mbi->access.read) { 1058 | old_protect |= PROT_READ; 1059 | } 1060 | 1061 | if (mbi->access.write) { 1062 | old_protect |= PROT_WRITE; 1063 | } 1064 | 1065 | if (mbi->access.execute) { 1066 | old_protect |= PROT_EXEC; 1067 | } 1068 | 1069 | auto* addr = align_down(address, static_cast(sysconf(_SC_PAGESIZE))); 1070 | 1071 | if (mprotect(addr, size, static_cast(protect)) == -1) { 1072 | return std::unexpected{OsError::FAILED_TO_PROTECT}; 1073 | } 1074 | 1075 | return old_protect; 1076 | } 1077 | 1078 | std::expected vm_query(uint8_t* address) { 1079 | auto* maps = fopen("/proc/self/maps", "r"); 1080 | 1081 | if (maps == nullptr) { 1082 | return std::unexpected{OsError::FAILED_TO_QUERY}; 1083 | } 1084 | 1085 | char line[512]; 1086 | unsigned long start; 1087 | unsigned long end; 1088 | char perms[5]; 1089 | unsigned long offset; 1090 | int dev_major; 1091 | int dev_minor; 1092 | unsigned long inode; 1093 | char path[256]; 1094 | unsigned long last_end = 1095 | reinterpret_cast(system_info().min_address); // Track the end address of the last mapping. 1096 | auto addr = reinterpret_cast(address); 1097 | std::optional info = std::nullopt; 1098 | 1099 | while (fgets(line, sizeof(line), maps) != nullptr) { 1100 | path[0] = '\0'; 1101 | 1102 | sscanf(line, "%lx-%lx %4s %lx %x:%x %lu %255[^\n]", &start, &end, perms, &offset, &dev_major, &dev_minor, 1103 | &inode, path); 1104 | 1105 | if (last_end < start && addr >= last_end && addr < start) { 1106 | info = { 1107 | .address = reinterpret_cast(last_end), 1108 | .size = start - last_end, 1109 | .access = VmAccess{}, 1110 | .is_free = true, 1111 | }; 1112 | 1113 | break; 1114 | } 1115 | 1116 | last_end = end; 1117 | 1118 | if (addr >= start && addr < end) { 1119 | info = { 1120 | .address = reinterpret_cast(start), 1121 | .size = end - start, 1122 | .access = VmAccess{}, 1123 | .is_free = false, 1124 | }; 1125 | 1126 | if (perms[0] == 'r') { 1127 | info->access.read = true; 1128 | } 1129 | 1130 | if (perms[1] == 'w') { 1131 | info->access.write = true; 1132 | } 1133 | 1134 | if (perms[2] == 'x') { 1135 | info->access.execute = true; 1136 | } 1137 | 1138 | break; 1139 | } 1140 | } 1141 | 1142 | fclose(maps); 1143 | 1144 | if (!info.has_value()) { 1145 | return std::unexpected{OsError::FAILED_TO_QUERY}; 1146 | } 1147 | 1148 | return info.value(); 1149 | } 1150 | 1151 | bool vm_is_readable(uint8_t* address, [[maybe_unused]] size_t size) { 1152 | return vm_query(address).value_or(VmBasicInfo{}).access.read; 1153 | } 1154 | 1155 | bool vm_is_writable(uint8_t* address, [[maybe_unused]] size_t size) { 1156 | return vm_query(address).value_or(VmBasicInfo{}).access.write; 1157 | } 1158 | 1159 | bool vm_is_executable(uint8_t* address) { 1160 | return vm_query(address).value_or(VmBasicInfo{}).access.execute; 1161 | } 1162 | 1163 | SystemInfo system_info() { 1164 | auto page_size = static_cast(sysconf(_SC_PAGESIZE)); 1165 | 1166 | return { 1167 | .page_size = page_size, 1168 | .allocation_granularity = page_size, 1169 | .min_address = reinterpret_cast(0x10000), 1170 | .max_address = reinterpret_cast(1ull << 47), 1171 | }; 1172 | } 1173 | 1174 | void execute_while_frozen(const std::function& run_fn, 1175 | [[maybe_unused]] const std::function& visit_fn) { 1176 | run_fn(); 1177 | } 1178 | 1179 | void fix_ip([[maybe_unused]] ThreadContext ctx, [[maybe_unused]] uint8_t* old_ip, [[maybe_unused]] uint8_t* new_ip) { 1180 | } 1181 | 1182 | } // namespace safetyhook 1183 | 1184 | #endif 1185 | 1186 | // 1187 | // Source file: os.windows.cpp 1188 | // 1189 | 1190 | 1191 | #if SAFETYHOOK_OS_WINDOWS 1192 | 1193 | #define NOMINMAX 1194 | #if __has_include() 1195 | #include 1196 | #elif __has_include() 1197 | #include 1198 | #else 1199 | #error "Windows.h not found" 1200 | #endif 1201 | 1202 | #include 1203 | 1204 | 1205 | #pragma comment(lib, "ntdll") 1206 | 1207 | extern "C" { 1208 | NTSTATUS 1209 | NTAPI 1210 | NtGetNextThread(HANDLE ProcessHandle, HANDLE ThreadHandle, ACCESS_MASK DesiredAccess, ULONG HandleAttributes, 1211 | ULONG Flags, PHANDLE NewThreadHandle); 1212 | } 1213 | 1214 | namespace safetyhook { 1215 | std::expected vm_allocate(uint8_t* address, size_t size, VmAccess access) { 1216 | DWORD protect = 0; 1217 | 1218 | if (access == VM_ACCESS_R) { 1219 | protect = PAGE_READONLY; 1220 | } else if (access == VM_ACCESS_RW) { 1221 | protect = PAGE_READWRITE; 1222 | } else if (access == VM_ACCESS_RX) { 1223 | protect = PAGE_EXECUTE_READ; 1224 | } else if (access == VM_ACCESS_RWX) { 1225 | protect = PAGE_EXECUTE_READWRITE; 1226 | } else { 1227 | return std::unexpected{OsError::FAILED_TO_ALLOCATE}; 1228 | } 1229 | 1230 | auto* result = VirtualAlloc(address, size, MEM_COMMIT | MEM_RESERVE, protect); 1231 | 1232 | if (result == nullptr) { 1233 | return std::unexpected{OsError::FAILED_TO_ALLOCATE}; 1234 | } 1235 | 1236 | return static_cast(result); 1237 | } 1238 | 1239 | void vm_free(uint8_t* address) { 1240 | VirtualFree(address, 0, MEM_RELEASE); 1241 | } 1242 | 1243 | std::expected vm_protect(uint8_t* address, size_t size, VmAccess access) { 1244 | DWORD protect = 0; 1245 | 1246 | if (access == VM_ACCESS_R) { 1247 | protect = PAGE_READONLY; 1248 | } else if (access == VM_ACCESS_RW) { 1249 | protect = PAGE_READWRITE; 1250 | } else if (access == VM_ACCESS_RX) { 1251 | protect = PAGE_EXECUTE_READ; 1252 | } else if (access == VM_ACCESS_RWX) { 1253 | protect = PAGE_EXECUTE_READWRITE; 1254 | } else { 1255 | return std::unexpected{OsError::FAILED_TO_PROTECT}; 1256 | } 1257 | 1258 | return vm_protect(address, size, protect); 1259 | } 1260 | 1261 | std::expected vm_protect(uint8_t* address, size_t size, uint32_t protect) { 1262 | DWORD old_protect = 0; 1263 | 1264 | if (VirtualProtect(address, size, protect, &old_protect) == FALSE) { 1265 | return std::unexpected{OsError::FAILED_TO_PROTECT}; 1266 | } 1267 | 1268 | return old_protect; 1269 | } 1270 | 1271 | std::expected vm_query(uint8_t* address) { 1272 | MEMORY_BASIC_INFORMATION mbi{}; 1273 | auto result = VirtualQuery(address, &mbi, sizeof(mbi)); 1274 | 1275 | if (result == 0) { 1276 | return std::unexpected{OsError::FAILED_TO_QUERY}; 1277 | } 1278 | 1279 | VmAccess access{ 1280 | .read = (mbi.Protect & (PAGE_READONLY | PAGE_READWRITE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE)) != 0, 1281 | .write = (mbi.Protect & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE)) != 0, 1282 | .execute = (mbi.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE)) != 0, 1283 | }; 1284 | 1285 | return VmBasicInfo{ 1286 | .address = static_cast(mbi.AllocationBase), 1287 | .size = mbi.RegionSize, 1288 | .access = access, 1289 | .is_free = mbi.State == MEM_FREE, 1290 | }; 1291 | } 1292 | 1293 | bool vm_is_readable(uint8_t* address, size_t size) { 1294 | return IsBadReadPtr(address, size) == FALSE; 1295 | } 1296 | 1297 | bool vm_is_writable(uint8_t* address, size_t size) { 1298 | return IsBadWritePtr(address, size) == FALSE; 1299 | } 1300 | 1301 | bool vm_is_executable(uint8_t* address) { 1302 | LPVOID image_base_ptr; 1303 | 1304 | if (RtlPcToFileHeader(address, &image_base_ptr) == nullptr) { 1305 | return vm_query(address).value_or(VmBasicInfo{}).access.execute; 1306 | } 1307 | 1308 | // Just check if the section is executable. 1309 | const auto* image_base = reinterpret_cast(image_base_ptr); 1310 | const auto* dos_hdr = reinterpret_cast(image_base); 1311 | 1312 | if (dos_hdr->e_magic != IMAGE_DOS_SIGNATURE) { 1313 | return vm_query(address).value_or(VmBasicInfo{}).access.execute; 1314 | } 1315 | 1316 | const auto* nt_hdr = reinterpret_cast(image_base + dos_hdr->e_lfanew); 1317 | 1318 | if (nt_hdr->Signature != IMAGE_NT_SIGNATURE) { 1319 | return vm_query(address).value_or(VmBasicInfo{}).access.execute; 1320 | } 1321 | 1322 | const auto* section = IMAGE_FIRST_SECTION(nt_hdr); 1323 | 1324 | for (auto i = 0; i < nt_hdr->FileHeader.NumberOfSections; ++i, ++section) { 1325 | if (address >= image_base + section->VirtualAddress && 1326 | address < image_base + section->VirtualAddress + section->Misc.VirtualSize) { 1327 | return (section->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; 1328 | } 1329 | } 1330 | 1331 | return vm_query(address).value_or(VmBasicInfo{}).access.execute; 1332 | } 1333 | 1334 | SystemInfo system_info() { 1335 | SystemInfo info{}; 1336 | 1337 | SYSTEM_INFO si{}; 1338 | GetSystemInfo(&si); 1339 | 1340 | info.page_size = si.dwPageSize; 1341 | info.allocation_granularity = si.dwAllocationGranularity; 1342 | info.min_address = static_cast(si.lpMinimumApplicationAddress); 1343 | info.max_address = static_cast(si.lpMaximumApplicationAddress); 1344 | 1345 | return info; 1346 | } 1347 | 1348 | void execute_while_frozen( 1349 | const std::function& run_fn, const std::function& visit_fn) { 1350 | // Freeze all threads. 1351 | int num_threads_frozen; 1352 | auto first_run = true; 1353 | 1354 | do { 1355 | num_threads_frozen = 0; 1356 | HANDLE thread{}; 1357 | 1358 | while (true) { 1359 | HANDLE next_thread{}; 1360 | const auto status = NtGetNextThread(GetCurrentProcess(), thread, 1361 | THREAD_QUERY_LIMITED_INFORMATION | THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_SET_CONTEXT, 0, 1362 | 0, &next_thread); 1363 | 1364 | if (thread != nullptr) { 1365 | CloseHandle(thread); 1366 | } 1367 | 1368 | if (!NT_SUCCESS(status)) { 1369 | break; 1370 | } 1371 | 1372 | thread = next_thread; 1373 | 1374 | const auto thread_id = GetThreadId(thread); 1375 | 1376 | if (thread_id == 0 || thread_id == GetCurrentThreadId()) { 1377 | continue; 1378 | } 1379 | 1380 | const auto suspend_count = SuspendThread(thread); 1381 | 1382 | if (suspend_count == static_cast(-1)) { 1383 | continue; 1384 | } 1385 | 1386 | // Check if the thread was already frozen. Only resume if the thread was already frozen, and it wasn't the 1387 | // first run of this freeze loop to account for threads that may have already been frozen for other reasons. 1388 | if (suspend_count != 0 && !first_run) { 1389 | ResumeThread(thread); 1390 | continue; 1391 | } 1392 | 1393 | CONTEXT thread_ctx{}; 1394 | 1395 | thread_ctx.ContextFlags = CONTEXT_FULL; 1396 | 1397 | if (GetThreadContext(thread, &thread_ctx) == FALSE) { 1398 | continue; 1399 | } 1400 | 1401 | if (visit_fn) { 1402 | visit_fn(static_cast(thread_id), static_cast(thread), 1403 | static_cast(&thread_ctx)); 1404 | } 1405 | 1406 | SetThreadContext(thread, &thread_ctx); 1407 | 1408 | ++num_threads_frozen; 1409 | } 1410 | 1411 | first_run = false; 1412 | } while (num_threads_frozen != 0); 1413 | 1414 | // Run the function. 1415 | if (run_fn) { 1416 | run_fn(); 1417 | } 1418 | 1419 | // Resume all threads. 1420 | HANDLE thread{}; 1421 | 1422 | while (true) { 1423 | HANDLE next_thread{}; 1424 | const auto status = NtGetNextThread(GetCurrentProcess(), thread, 1425 | THREAD_QUERY_LIMITED_INFORMATION | THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_SET_CONTEXT, 0, 0, 1426 | &next_thread); 1427 | 1428 | if (thread != nullptr) { 1429 | CloseHandle(thread); 1430 | } 1431 | 1432 | if (!NT_SUCCESS(status)) { 1433 | break; 1434 | } 1435 | 1436 | thread = next_thread; 1437 | 1438 | const auto thread_id = GetThreadId(thread); 1439 | 1440 | if (thread_id == 0 || thread_id == GetCurrentThreadId()) { 1441 | continue; 1442 | } 1443 | 1444 | ResumeThread(thread); 1445 | } 1446 | } 1447 | 1448 | void fix_ip(ThreadContext thread_ctx, uint8_t* old_ip, uint8_t* new_ip) { 1449 | auto* ctx = reinterpret_cast(thread_ctx); 1450 | 1451 | #if SAFETYHOOK_ARCH_X86_64 1452 | auto ip = ctx->Rip; 1453 | #elif SAFETYHOOK_ARCH_X86_32 1454 | auto ip = ctx->Eip; 1455 | #endif 1456 | 1457 | if (ip == reinterpret_cast(old_ip)) { 1458 | ip = reinterpret_cast(new_ip); 1459 | } 1460 | 1461 | #if SAFETYHOOK_ARCH_X86_64 1462 | ctx->Rip = ip; 1463 | #elif SAFETYHOOK_ARCH_X86_32 1464 | ctx->Eip = ip; 1465 | #endif 1466 | } 1467 | 1468 | } // namespace safetyhook 1469 | 1470 | #endif 1471 | 1472 | // 1473 | // Source file: utility.cpp 1474 | // 1475 | 1476 | 1477 | 1478 | namespace safetyhook { 1479 | bool is_executable(uint8_t* address) { 1480 | return vm_is_executable(address); 1481 | } 1482 | 1483 | UnprotectMemory::~UnprotectMemory() { 1484 | if (m_address != nullptr) { 1485 | vm_protect(m_address, m_size, m_original_protection); 1486 | } 1487 | } 1488 | 1489 | UnprotectMemory::UnprotectMemory(UnprotectMemory&& other) noexcept { 1490 | *this = std::move(other); 1491 | } 1492 | 1493 | UnprotectMemory& UnprotectMemory::operator=(UnprotectMemory&& other) noexcept { 1494 | if (this != &other) { 1495 | m_address = other.m_address; 1496 | m_size = other.m_size; 1497 | m_original_protection = other.m_original_protection; 1498 | other.m_address = nullptr; 1499 | other.m_size = 0; 1500 | other.m_original_protection = 0; 1501 | } 1502 | 1503 | return *this; 1504 | } 1505 | 1506 | std::optional unprotect(uint8_t* address, size_t size) { 1507 | auto old_protection = vm_protect(address, size, VM_ACCESS_RWX); 1508 | 1509 | if (!old_protection) { 1510 | return std::nullopt; 1511 | } 1512 | 1513 | return UnprotectMemory{address, size, old_protection.value()}; 1514 | } 1515 | 1516 | } // namespace safetyhook 1517 | 1518 | // 1519 | // Source file: vmt_hook.cpp 1520 | // 1521 | 1522 | 1523 | 1524 | namespace safetyhook { 1525 | VmHook::VmHook(VmHook&& other) noexcept { 1526 | *this = std::move(other); 1527 | } 1528 | 1529 | VmHook& VmHook::operator=(VmHook&& other) noexcept { 1530 | destroy(); 1531 | m_original_vm = other.m_original_vm; 1532 | m_new_vm = other.m_new_vm; 1533 | m_vmt_entry = other.m_vmt_entry; 1534 | m_new_vmt_allocation = std::move(other.m_new_vmt_allocation); 1535 | other.m_original_vm = nullptr; 1536 | other.m_new_vm = nullptr; 1537 | other.m_vmt_entry = nullptr; 1538 | return *this; 1539 | } 1540 | 1541 | VmHook::~VmHook() { 1542 | destroy(); 1543 | } 1544 | 1545 | void VmHook::reset() { 1546 | *this = {}; 1547 | } 1548 | 1549 | void VmHook::destroy() { 1550 | if (m_original_vm != nullptr) { 1551 | *m_vmt_entry = m_original_vm; 1552 | m_original_vm = nullptr; 1553 | m_new_vm = nullptr; 1554 | m_vmt_entry = nullptr; 1555 | m_new_vmt_allocation.reset(); 1556 | } 1557 | } 1558 | 1559 | std::expected VmtHook::create(void* object) { 1560 | VmtHook hook{}; 1561 | 1562 | const auto original_vmt = *reinterpret_cast(object); 1563 | hook.m_objects.emplace(object, original_vmt); 1564 | 1565 | // Count the number of virtual method pointers. We start at one to account for the RTTI pointer. 1566 | auto num_vmt_entries = 1; 1567 | 1568 | for (auto vm = original_vmt; is_executable(*vm); ++vm) { 1569 | ++num_vmt_entries; 1570 | } 1571 | 1572 | // Allocate memory for the new VMT. 1573 | auto allocation = Allocator::global()->allocate(num_vmt_entries * sizeof(uint8_t*)); 1574 | 1575 | if (!allocation) { 1576 | return std::unexpected{Error::bad_allocation(allocation.error())}; 1577 | } 1578 | 1579 | hook.m_new_vmt_allocation = std::make_shared(std::move(*allocation)); 1580 | hook.m_new_vmt = reinterpret_cast(hook.m_new_vmt_allocation->data()); 1581 | 1582 | // Copy pointer to RTTI. 1583 | hook.m_new_vmt[0] = original_vmt[-1]; 1584 | 1585 | // Copy virtual method pointers. 1586 | for (auto i = 0; i < num_vmt_entries - 1; ++i) { 1587 | hook.m_new_vmt[i + 1] = original_vmt[i]; 1588 | } 1589 | 1590 | *reinterpret_cast(object) = &hook.m_new_vmt[1]; 1591 | 1592 | return hook; 1593 | } 1594 | 1595 | VmtHook::VmtHook(VmtHook&& other) noexcept { 1596 | *this = std::move(other); 1597 | } 1598 | 1599 | VmtHook& VmtHook::operator=(VmtHook&& other) noexcept { 1600 | destroy(); 1601 | m_objects = std::move(other.m_objects); 1602 | m_new_vmt_allocation = std::move(other.m_new_vmt_allocation); 1603 | m_new_vmt = other.m_new_vmt; 1604 | other.m_new_vmt = nullptr; 1605 | return *this; 1606 | } 1607 | 1608 | VmtHook::~VmtHook() { 1609 | destroy(); 1610 | } 1611 | 1612 | void VmtHook::apply(void* object) { 1613 | m_objects.emplace(object, *reinterpret_cast(object)); 1614 | *reinterpret_cast(object) = &m_new_vmt[1]; 1615 | } 1616 | 1617 | void VmtHook::remove(void* object) { 1618 | const auto search = m_objects.find(object); 1619 | 1620 | if (search == m_objects.end()) { 1621 | return; 1622 | } 1623 | 1624 | const auto original_vmt = search->second; 1625 | 1626 | execute_while_frozen([&] { 1627 | if (!vm_is_writable(reinterpret_cast(object), sizeof(void*))) { 1628 | return; 1629 | } 1630 | 1631 | if (*reinterpret_cast(object) != &m_new_vmt[1]) { 1632 | return; 1633 | } 1634 | 1635 | *reinterpret_cast(object) = original_vmt; 1636 | }); 1637 | 1638 | m_objects.erase(search); 1639 | } 1640 | 1641 | void VmtHook::reset() { 1642 | *this = {}; 1643 | } 1644 | 1645 | void VmtHook::destroy() { 1646 | execute_while_frozen([this] { 1647 | for (const auto [object, original_vmt] : m_objects) { 1648 | if (!vm_is_writable(reinterpret_cast(object), sizeof(void*))) { 1649 | return; 1650 | } 1651 | 1652 | if (*reinterpret_cast(object) != &m_new_vmt[1]) { 1653 | return; 1654 | } 1655 | 1656 | *reinterpret_cast(object) = original_vmt; 1657 | } 1658 | }); 1659 | 1660 | m_objects.clear(); 1661 | m_new_vmt_allocation.reset(); 1662 | m_new_vmt = nullptr; 1663 | } 1664 | } // namespace safetyhook -------------------------------------------------------------------------------- /external/safetyhook/safetyhook.hpp: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This file is auto-generated by `amalgamate.py`. 2 | 3 | 4 | // 5 | // Header: safetyhook.hpp 6 | // 7 | 8 | #pragma once 9 | 10 | 11 | // 12 | // Header: safetyhook/easy.hpp 13 | // 14 | // Include stack: 15 | // - safetyhook.hpp 16 | // 17 | 18 | /// @file safetyhook/easy.hpp 19 | /// @brief Easy to use API for creating hooks. 20 | 21 | #pragma once 22 | 23 | 24 | // 25 | // Header: safetyhook/inline_hook.hpp 26 | // 27 | // Include stack: 28 | // - safetyhook.hpp 29 | // - safetyhook/easy.hpp 30 | // 31 | 32 | /// @file safetyhook/inline_hook.hpp 33 | /// @brief Inline hooking class. 34 | 35 | #pragma once 36 | 37 | #ifndef SAFETYHOOK_USE_CXXMODULES 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #else 45 | import std.compat; 46 | #endif 47 | 48 | 49 | // 50 | // Header: safetyhook/allocator.hpp 51 | // 52 | // Include stack: 53 | // - safetyhook.hpp 54 | // - safetyhook/easy.hpp 55 | // - safetyhook/inline_hook.hpp 56 | // 57 | 58 | /// @file safetyhook/allocator.hpp 59 | /// @brief Allocator for allocating memory near target addresses. 60 | 61 | #pragma once 62 | 63 | #ifndef SAFETYHOOK_USE_CXXMODULES 64 | #include 65 | #include 66 | #include 67 | #include 68 | #include 69 | #else 70 | import std.compat; 71 | #endif 72 | 73 | namespace safetyhook { 74 | class Allocator; 75 | 76 | /// @brief A memory allocation. 77 | class Allocation final { 78 | public: 79 | Allocation() = default; 80 | Allocation(const Allocation&) = delete; 81 | Allocation(Allocation&& other) noexcept; 82 | Allocation& operator=(const Allocation&) = delete; 83 | Allocation& operator=(Allocation&& other) noexcept; 84 | ~Allocation(); 85 | 86 | /// @brief Frees the allocation. 87 | /// @note This is called automatically when the Allocation object is destroyed. 88 | void free(); 89 | 90 | /// @brief Returns a pointer to the data of the allocation. 91 | /// @return Pointer to the data of the allocation. 92 | [[nodiscard]] uint8_t* data() const noexcept { return m_address; } 93 | 94 | /// @brief Returns the address of the allocation. 95 | /// @return The address of the allocation. 96 | [[nodiscard]] uintptr_t address() const noexcept { return (uintptr_t)m_address; } 97 | 98 | /// @brief Returns the size of the allocation. 99 | /// @return The size of the allocation. 100 | [[nodiscard]] size_t size() const noexcept { return m_size; } 101 | 102 | /// @brief Tests if the allocation is valid. 103 | /// @return True if the allocation is valid, false otherwise. 104 | explicit operator bool() const noexcept { return m_address != nullptr && m_size != 0; } 105 | 106 | protected: 107 | friend Allocator; 108 | 109 | Allocation(std::shared_ptr allocator, uint8_t* address, size_t size) noexcept; 110 | 111 | private: 112 | std::shared_ptr m_allocator{}; 113 | uint8_t* m_address{}; 114 | size_t m_size{}; 115 | }; 116 | 117 | /// @brief Allocates memory near target addresses. 118 | class Allocator final : public std::enable_shared_from_this { 119 | public: 120 | /// @brief Returns the global Allocator. 121 | /// @return The global Allocator. 122 | [[nodiscard]] static std::shared_ptr global(); 123 | 124 | /// @brief Creates a new Allocator. 125 | /// @return The new Allocator. 126 | [[nodiscard]] static std::shared_ptr create(); 127 | 128 | Allocator(const Allocator&) = delete; 129 | Allocator(Allocator&&) noexcept = delete; 130 | Allocator& operator=(const Allocator&) = delete; 131 | Allocator& operator=(Allocator&&) noexcept = delete; 132 | ~Allocator() = default; 133 | 134 | /// @brief The error type returned by the allocate functions. 135 | enum class Error : uint8_t { 136 | BAD_VIRTUAL_ALLOC, ///< VirtualAlloc failed. 137 | NO_MEMORY_IN_RANGE, ///< No memory in range. 138 | }; 139 | 140 | /// @brief Allocates memory. 141 | /// @param size The size of the allocation. 142 | /// @return The Allocation or an Allocator::Error if the allocation failed. 143 | [[nodiscard]] std::expected allocate(size_t size); 144 | 145 | /// @brief Allocates memory near a target address. 146 | /// @param desired_addresses The target address. 147 | /// @param size The size of the allocation. 148 | /// @param max_distance The maximum distance from the target address. 149 | /// @return The Allocation or an Allocator::Error if the allocation failed. 150 | [[nodiscard]] std::expected allocate_near( 151 | const std::vector& desired_addresses, size_t size, size_t max_distance = 0x7FFF'FFFF); 152 | 153 | protected: 154 | friend Allocation; 155 | 156 | void free(uint8_t* address, size_t size); 157 | 158 | private: 159 | struct FreeNode { 160 | std::unique_ptr next{}; 161 | uint8_t* start{}; 162 | uint8_t* end{}; 163 | }; 164 | 165 | struct Memory { 166 | uint8_t* address{}; 167 | size_t size{}; 168 | std::unique_ptr freelist{}; 169 | 170 | ~Memory(); 171 | }; 172 | 173 | std::vector> m_memory{}; 174 | std::mutex m_mutex{}; 175 | 176 | Allocator() = default; 177 | 178 | [[nodiscard]] std::expected internal_allocate_near( 179 | const std::vector& desired_addresses, size_t size, size_t max_distance = 0x7FFF'FFFF); 180 | void internal_free(uint8_t* address, size_t size); 181 | 182 | static void combine_adjacent_freenodes(Memory& memory); 183 | [[nodiscard]] static std::expected allocate_nearby_memory( 184 | const std::vector& desired_addresses, size_t size, size_t max_distance); 185 | [[nodiscard]] static bool in_range( 186 | uint8_t* address, const std::vector& desired_addresses, size_t max_distance); 187 | }; 188 | } // namespace safetyhook 189 | 190 | // 191 | // Header: safetyhook/common.hpp 192 | // 193 | // Include stack: 194 | // - safetyhook.hpp 195 | // - safetyhook/easy.hpp 196 | // - safetyhook/inline_hook.hpp 197 | // 198 | 199 | #pragma once 200 | 201 | #if defined(_MSC_VER) 202 | #define SAFETYHOOK_COMPILER_MSVC 1 203 | #define SAFETYHOOK_COMPILER_GCC 0 204 | #define SAFETYHOOK_COMPILER_CLANG 0 205 | #elif defined(__GNUC__) 206 | #define SAFETYHOOK_COMPILER_MSVC 0 207 | #define SAFETYHOOK_COMPILER_GCC 1 208 | #define SAFETYHOOK_COMPILER_CLANG 0 209 | #elif defined(__clang__) 210 | #define SAFETYHOOK_COMPILER_MSVC 0 211 | #define SAFETYHOOK_COMPILER_GCC 0 212 | #define SAFETYHOOK_COMPILER_CLANG 1 213 | #else 214 | #error "Unsupported compiler" 215 | #endif 216 | 217 | #if SAFETYHOOK_COMPILER_MSVC 218 | #if defined(_M_IX86) 219 | #define SAFETYHOOK_ARCH_X86_32 1 220 | #define SAFETYHOOK_ARCH_X86_64 0 221 | #elif defined(_M_X64) 222 | #define SAFETYHOOK_ARCH_X86_32 0 223 | #define SAFETYHOOK_ARCH_X86_64 1 224 | #else 225 | #error "Unsupported architecture" 226 | #endif 227 | #elif SAFETYHOOK_COMPILER_GCC || SAFETYHOOK_COMPILER_CLANG 228 | #if defined(__i386__) 229 | #define SAFETYHOOK_ARCH_X86_32 1 230 | #define SAFETYHOOK_ARCH_X86_64 0 231 | #elif defined(__x86_64__) 232 | #define SAFETYHOOK_ARCH_X86_32 0 233 | #define SAFETYHOOK_ARCH_X86_64 1 234 | #else 235 | #error "Unsupported architecture" 236 | #endif 237 | #endif 238 | 239 | #if defined(_WIN32) 240 | #define SAFETYHOOK_OS_WINDOWS 1 241 | #define SAFETYHOOK_OS_LINUX 0 242 | #elif defined(__linux__) 243 | #define SAFETYHOOK_OS_WINDOWS 0 244 | #define SAFETYHOOK_OS_LINUX 1 245 | #else 246 | #error "Unsupported OS" 247 | #endif 248 | 249 | #if SAFETYHOOK_OS_WINDOWS 250 | #if SAFETYHOOK_COMPILER_MSVC 251 | #define SAFETYHOOK_CCALL __cdecl 252 | #define SAFETYHOOK_STDCALL __stdcall 253 | #define SAFETYHOOK_FASTCALL __fastcall 254 | #define SAFETYHOOK_THISCALL __thiscall 255 | #elif SAFETYHOOK_COMPILER_GCC || SAFETYHOOK_COMPILER_CLANG 256 | #define SAFETYHOOK_CCALL __attribute__((cdecl)) 257 | #define SAFETYHOOK_STDCALL __attribute__((stdcall)) 258 | #define SAFETYHOOK_FASTCALL __attribute__((fastcall)) 259 | #define SAFETYHOOK_THISCALL __attribute__((thiscall)) 260 | #endif 261 | #else 262 | #define SAFETYHOOK_CCALL 263 | #define SAFETYHOOK_STDCALL 264 | #define SAFETYHOOK_FASTCALL 265 | #define SAFETYHOOK_THISCALL 266 | #endif 267 | 268 | #if SAFETYHOOK_COMPILER_MSVC 269 | #define SAFETYHOOK_NOINLINE __declspec(noinline) 270 | #elif SAFETYHOOK_COMPILER_GCC || SAFETYHOOK_COMPILER_CLANG 271 | #define SAFETYHOOK_NOINLINE __attribute__((noinline)) 272 | #endif 273 | 274 | // 275 | // Header: safetyhook/utility.hpp 276 | // 277 | // Include stack: 278 | // - safetyhook.hpp 279 | // - safetyhook/easy.hpp 280 | // - safetyhook/inline_hook.hpp 281 | // 282 | 283 | #pragma once 284 | 285 | #ifndef SAFETYHOOK_USE_CXXMODULES 286 | #include 287 | #include 288 | #include 289 | #include 290 | #else 291 | import std.compat; 292 | #endif 293 | 294 | namespace safetyhook { 295 | template constexpr void store(uint8_t* address, const T& value) { 296 | std::copy_n(reinterpret_cast(&value), sizeof(T), address); 297 | } 298 | 299 | template 300 | concept FnPtr = requires(T f) { std::is_pointer_v&& std::is_function_v>; }; 301 | 302 | bool is_executable(uint8_t* address); 303 | 304 | class UnprotectMemory { 305 | public: 306 | UnprotectMemory() = delete; 307 | ~UnprotectMemory(); 308 | UnprotectMemory(const UnprotectMemory&) = delete; 309 | UnprotectMemory(UnprotectMemory&& other) noexcept; 310 | UnprotectMemory& operator=(const UnprotectMemory&) = delete; 311 | UnprotectMemory& operator=(UnprotectMemory&& other) noexcept; 312 | 313 | private: 314 | friend std::optional unprotect(uint8_t*, size_t); 315 | 316 | UnprotectMemory(uint8_t* address, size_t size, uint32_t original_protection) 317 | : m_address{address}, m_size{size}, m_original_protection{original_protection} {} 318 | 319 | uint8_t* m_address{}; 320 | size_t m_size{}; 321 | uint32_t m_original_protection{}; 322 | }; 323 | 324 | [[nodiscard]] std::optional unprotect(uint8_t* address, size_t size); 325 | 326 | template constexpr T align_up(T address, size_t align) { 327 | const auto unaligned_address = (uintptr_t)address; 328 | const auto aligned_address = (unaligned_address + align - 1) & ~(align - 1); 329 | return (T)aligned_address; 330 | } 331 | 332 | template constexpr T align_down(T address, size_t align) { 333 | const auto unaligned_address = (uintptr_t)address; 334 | const auto aligned_address = unaligned_address & ~(align - 1); 335 | return (T)aligned_address; 336 | } 337 | } // namespace safetyhook 338 | 339 | namespace safetyhook { 340 | /// @brief An inline hook. 341 | class InlineHook final { 342 | public: 343 | /// @brief Error type for InlineHook. 344 | struct Error { 345 | /// @brief The type of error. 346 | enum : uint8_t { 347 | BAD_ALLOCATION, ///< An error occurred when allocating memory. 348 | FAILED_TO_DECODE_INSTRUCTION, ///< Failed to decode an instruction. 349 | SHORT_JUMP_IN_TRAMPOLINE, ///< The trampoline contains a short jump. 350 | IP_RELATIVE_INSTRUCTION_OUT_OF_RANGE, ///< An IP-relative instruction is out of range. 351 | UNSUPPORTED_INSTRUCTION_IN_TRAMPOLINE, ///< An unsupported instruction was found in the trampoline. 352 | FAILED_TO_UNPROTECT, ///< Failed to unprotect memory. 353 | NOT_ENOUGH_SPACE, ///< Not enough space to create the hook. 354 | } type; 355 | 356 | /// @brief Extra information about the error. 357 | union { 358 | Allocator::Error allocator_error; ///< Allocator error information. 359 | uint8_t* ip; ///< IP of the problematic instruction. 360 | }; 361 | 362 | /// @brief Create a BAD_ALLOCATION error. 363 | /// @param err The Allocator::Error that failed. 364 | /// @return The new BAD_ALLOCATION error. 365 | [[nodiscard]] static Error bad_allocation(Allocator::Error err) { 366 | return {.type = BAD_ALLOCATION, .allocator_error = err}; 367 | } 368 | 369 | /// @brief Create a FAILED_TO_DECODE_INSTRUCTION error. 370 | /// @param ip The IP of the problematic instruction. 371 | /// @return The new FAILED_TO_DECODE_INSTRUCTION error. 372 | [[nodiscard]] static Error failed_to_decode_instruction(uint8_t* ip) { 373 | return {.type = FAILED_TO_DECODE_INSTRUCTION, .ip = ip}; 374 | } 375 | 376 | /// @brief Create a SHORT_JUMP_IN_TRAMPOLINE error. 377 | /// @param ip The IP of the problematic instruction. 378 | /// @return The new SHORT_JUMP_IN_TRAMPOLINE error. 379 | [[nodiscard]] static Error short_jump_in_trampoline(uint8_t* ip) { 380 | return {.type = SHORT_JUMP_IN_TRAMPOLINE, .ip = ip}; 381 | } 382 | 383 | /// @brief Create a IP_RELATIVE_INSTRUCTION_OUT_OF_RANGE error. 384 | /// @param ip The IP of the problematic instruction. 385 | /// @return The new IP_RELATIVE_INSTRUCTION_OUT_OF_RANGE error. 386 | [[nodiscard]] static Error ip_relative_instruction_out_of_range(uint8_t* ip) { 387 | return {.type = IP_RELATIVE_INSTRUCTION_OUT_OF_RANGE, .ip = ip}; 388 | } 389 | 390 | /// @brief Create a UNSUPPORTED_INSTRUCTION_IN_TRAMPOLINE error. 391 | /// @param ip The IP of the problematic instruction. 392 | /// @return The new UNSUPPORTED_INSTRUCTION_IN_TRAMPOLINE error. 393 | [[nodiscard]] static Error unsupported_instruction_in_trampoline(uint8_t* ip) { 394 | return {.type = UNSUPPORTED_INSTRUCTION_IN_TRAMPOLINE, .ip = ip}; 395 | } 396 | 397 | /// @brief Create a FAILED_TO_UNPROTECT error. 398 | /// @param ip The IP of the problematic instruction. 399 | /// @return The new FAILED_TO_UNPROTECT error. 400 | [[nodiscard]] static Error failed_to_unprotect(uint8_t* ip) { return {.type = FAILED_TO_UNPROTECT, .ip = ip}; } 401 | 402 | /// @brief Create a NOT_ENOUGH_SPACE error. 403 | /// @param ip The IP of the problematic instruction. 404 | /// @return The new NOT_ENOUGH_SPACE error. 405 | [[nodiscard]] static Error not_enough_space(uint8_t* ip) { return {.type = NOT_ENOUGH_SPACE, .ip = ip}; } 406 | }; 407 | 408 | /// @brief Create an inline hook. 409 | /// @param target The address of the function to hook. 410 | /// @param destination The destination address. 411 | /// @return The InlineHook or an InlineHook::Error if an error occurred. 412 | /// @note This will use the default global Allocator. 413 | /// @note If you don't care about error handling, use the easy API (safetyhook::create_inline). 414 | [[nodiscard]] static std::expected create(void* target, void* destination); 415 | 416 | /// @brief Create an inline hook. 417 | /// @param target The address of the function to hook. 418 | /// @param destination The destination address. 419 | /// @return The InlineHook or an InlineHook::Error if an error occurred. 420 | /// @note This will use the default global Allocator. 421 | /// @note If you don't care about error handling, use the easy API (safetyhook::create_inline). 422 | [[nodiscard]] static std::expected create(FnPtr auto target, FnPtr auto destination) { 423 | return create(reinterpret_cast(target), reinterpret_cast(destination)); 424 | } 425 | 426 | /// @brief Create an inline hook with a given Allocator. 427 | /// @param allocator The allocator to use. 428 | /// @param target The address of the function to hook. 429 | /// @param destination The destination address. 430 | /// @return The InlineHook or an InlineHook::Error if an error occurred. 431 | /// @note If you don't care about error handling, use the easy API (safetyhook::create_inline). 432 | [[nodiscard]] static std::expected create( 433 | const std::shared_ptr& allocator, void* target, void* destination); 434 | 435 | /// @brief Create an inline hook with a given Allocator. 436 | /// @param allocator The allocator to use. 437 | /// @param target The address of the function to hook. 438 | /// @param destination The destination address. 439 | /// @return The InlineHook or an InlineHook::Error if an error occurred. 440 | /// @note If you don't care about error handling, use the easy API (safetyhook::create_inline). 441 | [[nodiscard]] static std::expected create( 442 | const std::shared_ptr& allocator, FnPtr auto target, FnPtr auto destination) { 443 | return create(allocator, reinterpret_cast(target), reinterpret_cast(destination)); 444 | } 445 | 446 | InlineHook() = default; 447 | InlineHook(const InlineHook&) = delete; 448 | InlineHook(InlineHook&& other) noexcept; 449 | InlineHook& operator=(const InlineHook&) = delete; 450 | InlineHook& operator=(InlineHook&& other) noexcept; 451 | ~InlineHook(); 452 | 453 | /// @brief Reset the hook. 454 | /// @details This will restore the original function and remove the hook. 455 | /// @note This is called automatically in the destructor. 456 | void reset(); 457 | 458 | /// @brief Get a pointer to the target. 459 | /// @return A pointer to the target. 460 | [[nodiscard]] uint8_t* target() const { return m_target; } 461 | 462 | /// @brief Get the target address. 463 | /// @return The target address. 464 | [[nodiscard]] uintptr_t target_address() const { return reinterpret_cast(m_target); } 465 | 466 | /// @brief Get a pointer ot the destination. 467 | /// @return A pointer to the destination. 468 | [[nodiscard]] uint8_t* destination() const { return m_destination; } 469 | 470 | /// @brief Get the destination address. 471 | /// @return The destination address. 472 | [[nodiscard]] uintptr_t destination_address() const { return reinterpret_cast(m_destination); } 473 | 474 | /// @brief Get the trampoline Allocation. 475 | /// @return The trampoline Allocation. 476 | [[nodiscard]] const Allocation& trampoline() const { return m_trampoline; } 477 | 478 | /// @brief Tests if the hook is valid. 479 | /// @return True if the hook is valid, false otherwise. 480 | explicit operator bool() const { return static_cast(m_trampoline); } 481 | 482 | /// @brief Returns the address of the trampoline to call the original function. 483 | /// @tparam T The type of the function pointer. 484 | /// @return The address of the trampoline to call the original function. 485 | template [[nodiscard]] T original() const { return reinterpret_cast(m_trampoline.address()); } 486 | 487 | /// @brief Returns a vector containing the original bytes of the target function. 488 | /// @return A vector of the original bytes of the target function. 489 | [[nodiscard]] const auto& original_bytes() const { return m_original_bytes; } 490 | 491 | /// @brief Calls the original function. 492 | /// @tparam RetT The return type of the function. 493 | /// @tparam ...Args The argument types of the function. 494 | /// @param ...args The arguments to pass to the function. 495 | /// @return The result of calling the original function. 496 | /// @note This function will use the default calling convention set by your compiler. 497 | template RetT call(Args... args) { 498 | std::scoped_lock lock{m_mutex}; 499 | return m_trampoline ? original()(args...) : RetT(); 500 | } 501 | 502 | /// @brief Calls the original function. 503 | /// @tparam RetT The return type of the function. 504 | /// @tparam ...Args The argument types of the function. 505 | /// @param ...args The arguments to pass to the function. 506 | /// @return The result of calling the original function. 507 | /// @note This function will use the __cdecl calling convention. 508 | template RetT ccall(Args... args) { 509 | std::scoped_lock lock{m_mutex}; 510 | return m_trampoline ? original()(args...) : RetT(); 511 | } 512 | 513 | /// @brief Calls the original function. 514 | /// @tparam RetT The return type of the function. 515 | /// @tparam ...Args The argument types of the function. 516 | /// @param ...args The arguments to pass to the function. 517 | /// @return The result of calling the original function. 518 | /// @note This function will use the __thiscall calling convention. 519 | template RetT thiscall(Args... args) { 520 | std::scoped_lock lock{m_mutex}; 521 | return m_trampoline ? original()(args...) : RetT(); 522 | } 523 | 524 | /// @brief Calls the original function. 525 | /// @tparam RetT The return type of the function. 526 | /// @tparam ...Args The argument types of the function. 527 | /// @param ...args The arguments to pass to the function. 528 | /// @return The result of calling the original function. 529 | /// @note This function will use the __stdcall calling convention. 530 | template RetT stdcall(Args... args) { 531 | std::scoped_lock lock{m_mutex}; 532 | return m_trampoline ? original()(args...) : RetT(); 533 | } 534 | 535 | /// @brief Calls the original function. 536 | /// @tparam RetT The return type of the function. 537 | /// @tparam ...Args The argument types of the function. 538 | /// @param ...args The arguments to pass to the function. 539 | /// @return The result of calling the original function. 540 | /// @note This function will use the __fastcall calling convention. 541 | template RetT fastcall(Args... args) { 542 | std::scoped_lock lock{m_mutex}; 543 | return m_trampoline ? original()(args...) : RetT(); 544 | } 545 | 546 | /// @brief Calls the original function. 547 | /// @tparam RetT The return type of the function. 548 | /// @tparam ...Args The argument types of the function. 549 | /// @param ...args The arguments to pass to the function. 550 | /// @return The result of calling the original function. 551 | /// @note This function will use the default calling convention set by your compiler. 552 | /// @note This function is unsafe because it doesn't lock the mutex. Only use this if you don't care about unhook 553 | /// safety or are worried about the performance cost of locking the mutex. 554 | template RetT unsafe_call(Args... args) { 555 | return original()(args...); 556 | } 557 | 558 | /// @brief Calls the original function. 559 | /// @tparam RetT The return type of the function. 560 | /// @tparam ...Args The argument types of the function. 561 | /// @param ...args The arguments to pass to the function. 562 | /// @return The result of calling the original function. 563 | /// @note This function will use the __cdecl calling convention. 564 | /// @note This function is unsafe because it doesn't lock the mutex. Only use this if you don't care about unhook 565 | /// safety or are worried about the performance cost of locking the mutex. 566 | template RetT unsafe_ccall(Args... args) { 567 | return original()(args...); 568 | } 569 | 570 | /// @brief Calls the original function. 571 | /// @tparam RetT The return type of the function. 572 | /// @tparam ...Args The argument types of the function. 573 | /// @param ...args The arguments to pass to the function. 574 | /// @return The result of calling the original function. 575 | /// @note This function will use the __thiscall calling convention. 576 | /// @note This function is unsafe because it doesn't lock the mutex. Only use this if you don't care about unhook 577 | /// safety or are worried about the performance cost of locking the mutex. 578 | template RetT unsafe_thiscall(Args... args) { 579 | return original()(args...); 580 | } 581 | 582 | /// @brief Calls the original function. 583 | /// @tparam RetT The return type of the function. 584 | /// @tparam ...Args The argument types of the function. 585 | /// @param ...args The arguments to pass to the function. 586 | /// @return The result of calling the original function. 587 | /// @note This function will use the __stdcall calling convention. 588 | /// @note This function is unsafe because it doesn't lock the mutex. Only use this if you don't care about unhook 589 | /// safety or are worried about the performance cost of locking the mutex. 590 | template RetT unsafe_stdcall(Args... args) { 591 | return original()(args...); 592 | } 593 | 594 | /// @brief Calls the original function. 595 | /// @tparam RetT The return type of the function. 596 | /// @tparam ...Args The argument types of the function. 597 | /// @param ...args The arguments to pass to the function. 598 | /// @return The result of calling the original function. 599 | /// @note This function will use the __fastcall calling convention. 600 | /// @note This function is unsafe because it doesn't lock the mutex. Only use this if you don't care about unhook 601 | /// safety or are worried about the performance cost of locking the mutex. 602 | template RetT unsafe_fastcall(Args... args) { 603 | return original()(args...); 604 | } 605 | 606 | private: 607 | friend class MidHook; 608 | 609 | uint8_t* m_target{}; 610 | uint8_t* m_destination{}; 611 | Allocation m_trampoline{}; 612 | std::vector m_original_bytes{}; 613 | uintptr_t m_trampoline_size{}; 614 | std::recursive_mutex m_mutex{}; 615 | 616 | std::expected setup( 617 | const std::shared_ptr& allocator, uint8_t* target, uint8_t* destination); 618 | std::expected e9_hook(const std::shared_ptr& allocator); 619 | 620 | #if SAFETYHOOK_ARCH_X86_64 621 | std::expected ff_hook(const std::shared_ptr& allocator); 622 | #endif 623 | 624 | void destroy(); 625 | }; 626 | } // namespace safetyhook 627 | 628 | // 629 | // Header: safetyhook/mid_hook.hpp 630 | // 631 | // Include stack: 632 | // - safetyhook.hpp 633 | // - safetyhook/easy.hpp 634 | // 635 | 636 | /// @file safetyhook/mid_hook.hpp 637 | /// @brief Mid function hooking class. 638 | 639 | #pragma once 640 | 641 | #ifndef SAFETYHOOK_USE_CXXMODULES 642 | #include 643 | #include 644 | #else 645 | import std.compat; 646 | #endif 647 | 648 | 649 | // 650 | // Header: safetyhook/context.hpp 651 | // 652 | // Include stack: 653 | // - safetyhook.hpp 654 | // - safetyhook/easy.hpp 655 | // - safetyhook/mid_hook.hpp 656 | // 657 | 658 | /// @file safetyhook/context.hpp 659 | /// @brief Context structure for MidHook. 660 | 661 | #pragma once 662 | 663 | #ifndef SAFETYHOOK_USE_CXXMODULES 664 | #include 665 | #else 666 | import std.compat; 667 | #endif 668 | 669 | 670 | namespace safetyhook { 671 | union Xmm { 672 | uint8_t u8[16]; 673 | uint16_t u16[8]; 674 | uint32_t u32[4]; 675 | uint64_t u64[2]; 676 | float f32[4]; 677 | double f64[2]; 678 | }; 679 | 680 | /// @brief Context structure for 64-bit MidHook. 681 | /// @details This structure is used to pass the context of the hooked function to the destination allowing full access 682 | /// to the 64-bit registers at the moment the hook is called. 683 | /// @note rip will point to a trampoline containing the replaced instruction(s). 684 | /// @note rsp is read-only. Modifying it will have no effect. Use trampoline_rsp to modify rsp if needed but make sure 685 | /// the top of the stack is the rip you want to resume at. 686 | struct Context64 { 687 | Xmm xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, xmm14, xmm15; 688 | uintptr_t rflags, r15, r14, r13, r12, r11, r10, r9, r8, rdi, rsi, rdx, rcx, rbx, rax, rbp, rsp, trampoline_rsp, rip; 689 | }; 690 | 691 | /// @brief Context structure for 32-bit MidHook. 692 | /// @details This structure is used to pass the context of the hooked function to the destination allowing full access 693 | /// to the 32-bit registers at the moment the hook is called. 694 | /// @note eip will point to a trampoline containing the replaced instruction(s). 695 | /// @note esp is read-only. Modifying it will have no effect. Use trampoline_esp to modify esp if needed but make sure 696 | /// the top of the stack is the eip you want to resume at. 697 | struct Context32 { 698 | Xmm xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7; 699 | uintptr_t eflags, edi, esi, edx, ecx, ebx, eax, ebp, esp, trampoline_esp, eip; 700 | }; 701 | 702 | /// @brief Context structure for MidHook. 703 | /// @details This structure is used to pass the context of the hooked function to the destination allowing full access 704 | /// to the registers at the moment the hook is called. 705 | /// @note The structure is different depending on architecture. 706 | /// @note The structure only provides access to integer registers. 707 | #if SAFETYHOOK_ARCH_X86_64 708 | using Context = Context64; 709 | #elif SAFETYHOOK_ARCH_X86_32 710 | using Context = Context32; 711 | #endif 712 | 713 | } // namespace safetyhook 714 | 715 | namespace safetyhook { 716 | 717 | /// @brief A MidHook destination function. 718 | using MidHookFn = void (*)(Context& ctx); 719 | 720 | /// @brief A mid function hook. 721 | class MidHook final { 722 | public: 723 | /// @brief Error type for MidHook. 724 | struct Error { 725 | /// @brief The type of error. 726 | enum : uint8_t { 727 | BAD_ALLOCATION, 728 | BAD_INLINE_HOOK, 729 | } type; 730 | 731 | /// @brief Extra error information. 732 | union { 733 | Allocator::Error allocator_error; ///< Allocator error information. 734 | InlineHook::Error inline_hook_error; ///< InlineHook error information. 735 | }; 736 | 737 | /// @brief Create a BAD_ALLOCATION error. 738 | /// @param err The Allocator::Error that failed. 739 | /// @return The new BAD_ALLOCATION error. 740 | [[nodiscard]] static Error bad_allocation(Allocator::Error err) { 741 | return {.type = BAD_ALLOCATION, .allocator_error = err}; 742 | } 743 | 744 | /// @brief Create a BAD_INLINE_HOOK error. 745 | /// @param err The InlineHook::Error that failed. 746 | /// @return The new BAD_INLINE_HOOK error. 747 | [[nodiscard]] static Error bad_inline_hook(InlineHook::Error err) { 748 | return {.type = BAD_INLINE_HOOK, .inline_hook_error = err}; 749 | } 750 | }; 751 | 752 | /// @brief Creates a new MidHook object. 753 | /// @param target The address of the function to hook. 754 | /// @param destination_fn The destination function. 755 | /// @return The MidHook object or a MidHook::Error if an error occurred. 756 | /// @note This will use the default global Allocator. 757 | /// @note If you don't care about error handling, use the easy API (safetyhook::create_mid). 758 | [[nodiscard]] static std::expected create(void* target, MidHookFn destination_fn); 759 | 760 | /// @brief Creates a new MidHook object. 761 | /// @param target The address of the function to hook. 762 | /// @param destination_fn The destination function. 763 | /// @return The MidHook object or a MidHook::Error if an error occurred. 764 | /// @note This will use the default global Allocator. 765 | /// @note If you don't care about error handling, use the easy API (safetyhook::create_mid). 766 | [[nodiscard]] static std::expected create(FnPtr auto target, MidHookFn destination_fn) { 767 | return create(reinterpret_cast(target), destination_fn); 768 | } 769 | 770 | /// @brief Creates a new MidHook object with a given Allocator. 771 | /// @param allocator The Allocator to use. 772 | /// @param target The address of the function to hook. 773 | /// @param destination_fn The destination function. 774 | /// @return The MidHook object or a MidHook::Error if an error occurred. 775 | /// @note If you don't care about error handling, use the easy API (safetyhook::create_mid). 776 | [[nodiscard]] static std::expected create( 777 | const std::shared_ptr& allocator, void* target, MidHookFn destination_fn); 778 | 779 | /// @brief Creates a new MidHook object with a given Allocator. 780 | /// @tparam T The type of the function to hook. 781 | /// @param allocator The Allocator to use. 782 | /// @param target The address of the function to hook. 783 | /// @param destination_fn The destination function. 784 | /// @return The MidHook object or a MidHook::Error if an error occurred. 785 | /// @note If you don't care about error handling, use the easy API (safetyhook::create_mid). 786 | [[nodiscard]] static std::expected create( 787 | const std::shared_ptr& allocator, FnPtr auto target, MidHookFn destination_fn) { 788 | return create(allocator, reinterpret_cast(target), destination_fn); 789 | } 790 | 791 | MidHook() = default; 792 | MidHook(const MidHook&) = delete; 793 | MidHook(MidHook&& other) noexcept; 794 | MidHook& operator=(const MidHook&) = delete; 795 | MidHook& operator=(MidHook&& other) noexcept; 796 | ~MidHook() = default; 797 | 798 | /// @brief Reset the hook. 799 | /// @details This will remove the hook and free the stub. 800 | /// @note This is called automatically in the destructor. 801 | void reset(); 802 | 803 | /// @brief Get a pointer to the target. 804 | /// @return A pointer to the target. 805 | [[nodiscard]] uint8_t* target() const { return m_target; } 806 | 807 | /// @brief Get the address of the target. 808 | /// @return The address of the target. 809 | [[nodiscard]] uintptr_t target_address() const { return reinterpret_cast(m_target); } 810 | 811 | /// @brief Get the destination function. 812 | /// @return The destination function. 813 | [[nodiscard]] MidHookFn destination() const { return m_destination; } 814 | 815 | /// @brief Returns a vector containing the original bytes of the target function. 816 | /// @return A vector of the original bytes of the target function. 817 | [[nodiscard]] const auto& original_bytes() const { return m_hook.m_original_bytes; } 818 | 819 | /// @brief Tests if the hook is valid. 820 | /// @return true if the hook is valid, false otherwise. 821 | explicit operator bool() const { return static_cast(m_stub); } 822 | 823 | private: 824 | InlineHook m_hook{}; 825 | uint8_t* m_target{}; 826 | Allocation m_stub{}; 827 | MidHookFn m_destination{}; 828 | 829 | std::expected setup( 830 | const std::shared_ptr& allocator, uint8_t* target, MidHookFn destination); 831 | }; 832 | } // namespace safetyhook 833 | 834 | // 835 | // Header: safetyhook/vmt_hook.hpp 836 | // 837 | // Include stack: 838 | // - safetyhook.hpp 839 | // - safetyhook/easy.hpp 840 | // 841 | 842 | /// @file safetyhook/vmt_hook.hpp 843 | /// @brief VMT hooking classes 844 | 845 | #pragma once 846 | 847 | #ifndef SAFETYHOOK_USE_CXXMODULES 848 | #include 849 | #include 850 | #include 851 | #else 852 | import std.compat; 853 | #endif 854 | 855 | 856 | namespace safetyhook { 857 | /// @brief A hook class that allows for hooking a single method in a VMT. 858 | class VmHook final { 859 | public: 860 | VmHook() = default; 861 | VmHook(const VmHook&) = delete; 862 | VmHook(VmHook&& other) noexcept; 863 | VmHook& operator=(const VmHook&) = delete; 864 | VmHook& operator=(VmHook&& other) noexcept; 865 | ~VmHook(); 866 | 867 | /// @brief Removes the hook. 868 | void reset(); 869 | 870 | /// @brief Gets the original method pointer. 871 | template [[nodiscard]] T original() const { return reinterpret_cast(m_original_vm); } 872 | 873 | /// @brief Calls the original method. 874 | /// @tparam RetT The return type of the method. 875 | /// @tparam Args The argument types of the method. 876 | /// @param args The arguments to pass to the method. 877 | /// @return The return value of the method. 878 | /// @note This will call the original method with the default calling convention. 879 | template RetT call(Args... args) { 880 | return original()(args...); 881 | } 882 | 883 | /// @brief Calls the original method with the __cdecl calling convention. 884 | /// @tparam RetT The return type of the method. 885 | /// @tparam Args The argument types of the method. 886 | /// @param args The arguments to pass to the method. 887 | /// @return The return value of the method. 888 | template RetT ccall(Args... args) { 889 | return original()(args...); 890 | } 891 | 892 | /// @brief Calls the original method with the __thiscall calling convention. 893 | /// @tparam RetT The return type of the method. 894 | /// @tparam Args The argument types of the method. 895 | /// @param args The arguments to pass to the method. 896 | /// @return The return value of the method. 897 | template RetT thiscall(Args... args) { 898 | return original()(args...); 899 | } 900 | 901 | /// @brief Calls the original method with the __stdcall calling convention. 902 | /// @tparam RetT The return type of the method. 903 | /// @tparam Args The argument types of the method. 904 | /// @param args The arguments to pass to the method. 905 | /// @return The return value of the method. 906 | template RetT stdcall(Args... args) { 907 | return original()(args...); 908 | } 909 | 910 | /// @brief Calls the original method with the __fastcall calling convention. 911 | /// @tparam RetT The return type of the method. 912 | /// @tparam Args The argument types of the method. 913 | /// @param args The arguments to pass to the method. 914 | /// @return The return value of the method. 915 | template RetT fastcall(Args... args) { 916 | return original()(args...); 917 | } 918 | 919 | private: 920 | friend class VmtHook; 921 | 922 | uint8_t* m_original_vm{}; 923 | uint8_t* m_new_vm{}; 924 | uint8_t** m_vmt_entry{}; 925 | 926 | // This keeps the allocation alive until the hook is destroyed. 927 | std::shared_ptr m_new_vmt_allocation{}; 928 | 929 | void destroy(); 930 | }; 931 | 932 | /// @brief A hook class that copies an entire VMT for a given object and replaces it. 933 | class VmtHook final { 934 | public: 935 | /// @brief Error type for VmtHook. 936 | struct Error { 937 | /// @brief The type of error. 938 | enum : uint8_t { 939 | BAD_ALLOCATION, ///< An error occurred while allocating memory. 940 | } type; 941 | 942 | /// @brief Extra error information. 943 | union { 944 | Allocator::Error allocator_error; ///< Allocator error information. 945 | }; 946 | 947 | /// @brief Create a BAD_ALLOCATION error. 948 | /// @param err The Allocator::Error that failed. 949 | /// @return The new BAD_ALLOCATION error. 950 | [[nodiscard]] static Error bad_allocation(Allocator::Error err) { 951 | return {.type = BAD_ALLOCATION, .allocator_error = err}; 952 | } 953 | }; 954 | 955 | /// @brief Creates a new VmtHook object. Will clone the VMT of the given object and replace it. 956 | /// @param object The object to hook. 957 | /// @return The VmtHook object or a VmtHook::Error if an error occurred. 958 | [[nodiscard]] static std::expected create(void* object); 959 | 960 | VmtHook() = default; 961 | VmtHook(const VmtHook&) = delete; 962 | VmtHook(VmtHook&& other) noexcept; 963 | VmtHook& operator=(const VmtHook&) = delete; 964 | VmtHook& operator=(VmtHook&& other) noexcept; 965 | ~VmtHook(); 966 | 967 | /// @brief Applies the hook. 968 | /// @param object The object to apply the hook to. 969 | /// @note This will replace the VMT of the object with the new VMT. You can apply the hook to multiple objects. 970 | void apply(void* object); 971 | 972 | /// @brief Removes the hook. 973 | /// @param object The object to remove the hook from. 974 | void remove(void* object); 975 | 976 | /// @brief Removes the hook from all objects. 977 | void reset(); 978 | 979 | /// @brief Hooks a method in the VMT. 980 | /// @param index The index of the method to hook. 981 | /// @param new_function The new function to use. 982 | [[nodiscard]] std::expected hook_method(size_t index, FnPtr auto new_function) { 983 | VmHook hook{}; 984 | 985 | ++index; // Skip RTTI pointer. 986 | hook.m_original_vm = m_new_vmt[index]; 987 | store(reinterpret_cast(&hook.m_new_vm), new_function); 988 | hook.m_vmt_entry = &m_new_vmt[index]; 989 | hook.m_new_vmt_allocation = m_new_vmt_allocation; 990 | m_new_vmt[index] = hook.m_new_vm; 991 | 992 | return hook; 993 | } 994 | 995 | private: 996 | // Map of object instance to their original VMT. 997 | std::unordered_map m_objects{}; 998 | 999 | // The allocation is a shared_ptr, so it can be shared with VmHooks to ensure the memory is kept alive. 1000 | std::shared_ptr m_new_vmt_allocation{}; 1001 | uint8_t** m_new_vmt{}; 1002 | 1003 | void destroy(); 1004 | }; 1005 | } // namespace safetyhook 1006 | 1007 | namespace safetyhook { 1008 | /// @brief Easy to use API for creating an InlineHook. 1009 | /// @param target The address of the function to hook. 1010 | /// @param destination The address of the destination function. 1011 | /// @return The InlineHook object. 1012 | [[nodiscard]] InlineHook create_inline(void* target, void* destination); 1013 | 1014 | /// @brief Easy to use API for creating an InlineHook. 1015 | /// @param target The address of the function to hook. 1016 | /// @param destination The address of the destination function. 1017 | /// @return The InlineHook object. 1018 | [[nodiscard]] InlineHook create_inline(FnPtr auto target, FnPtr auto destination) { 1019 | return create_inline(reinterpret_cast(target), reinterpret_cast(destination)); 1020 | } 1021 | 1022 | /// @brief Easy to use API for creating a MidHook. 1023 | /// @param target the address of the function to hook. 1024 | /// @param destination The destination function. 1025 | /// @return The MidHook object. 1026 | [[nodiscard]] MidHook create_mid(void* target, MidHookFn destination); 1027 | 1028 | /// @brief Easy to use API for creating a MidHook. 1029 | /// @param target the address of the function to hook. 1030 | /// @param destination The destination function. 1031 | /// @return The MidHook object. 1032 | [[nodiscard]] MidHook create_mid(FnPtr auto target, MidHookFn destination) { 1033 | return create_mid(reinterpret_cast(target), destination); 1034 | } 1035 | 1036 | /// @brief Easy to use API for creating a VmtHook. 1037 | /// @param object The object to hook. 1038 | /// @return The VmtHook object. 1039 | [[nodiscard]] VmtHook create_vmt(void* object); 1040 | 1041 | /// @brief Easy to use API for creating a VmHook. 1042 | /// @param vmt The VmtHook to use to create the VmHook. 1043 | /// @param index The index of the method to hook. 1044 | /// @param destination The destination function. 1045 | /// @return The VmHook object. 1046 | [[nodiscard]] VmHook create_vm(VmtHook& vmt, size_t index, FnPtr auto destination) { 1047 | if (auto hook = vmt.hook_method(index, destination)) { 1048 | return std::move(*hook); 1049 | } else { 1050 | return {}; 1051 | } 1052 | } 1053 | 1054 | } // namespace safetyhook 1055 | 1056 | using SafetyHookContext = safetyhook::Context; 1057 | using SafetyHookInline = safetyhook::InlineHook; 1058 | using SafetyHookMid = safetyhook::MidHook; 1059 | using SafetyInlineHook [[deprecated("Use SafetyHookInline instead.")]] = safetyhook::InlineHook; 1060 | using SafetyMidHook [[deprecated("Use SafetyHookMid instead.")]] = safetyhook::MidHook; 1061 | using SafetyHookVmt = safetyhook::VmtHook; 1062 | using SafetyHookVm = safetyhook::VmHook; -------------------------------------------------------------------------------- /src/dllmain.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lyall/DDDAFix/e7689f81c7ba0ce06b4bb6d6747b398e93bd4fcb/src/dllmain.cpp -------------------------------------------------------------------------------- /src/helper.hpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include 3 | 4 | using namespace std; 5 | 6 | namespace Memory 7 | { 8 | template 9 | void Write(uintptr_t writeAddress, T value) 10 | { 11 | DWORD oldProtect; 12 | VirtualProtect((LPVOID)(writeAddress), sizeof(T), PAGE_EXECUTE_WRITECOPY, &oldProtect); 13 | *(reinterpret_cast(writeAddress)) = value; 14 | VirtualProtect((LPVOID)(writeAddress), sizeof(T), oldProtect, &oldProtect); 15 | } 16 | 17 | void PatchBytes(uintptr_t address, const char* pattern, unsigned int numBytes) 18 | { 19 | DWORD oldProtect; 20 | VirtualProtect((LPVOID)address, numBytes, PAGE_EXECUTE_READWRITE, &oldProtect); 21 | memcpy((LPVOID)address, pattern, numBytes); 22 | VirtualProtect((LPVOID)address, numBytes, oldProtect, &oldProtect); 23 | } 24 | 25 | 26 | static HMODULE GetThisDllHandle() 27 | { 28 | MEMORY_BASIC_INFORMATION info; 29 | size_t len = VirtualQueryEx(GetCurrentProcess(), (void*)GetThisDllHandle, &info, sizeof(info)); 30 | assert(len == sizeof(info)); 31 | return len ? (HMODULE)info.AllocationBase : NULL; 32 | } 33 | 34 | uint32_t ModuleTimestamp(void* module) 35 | { 36 | auto dosHeader = (PIMAGE_DOS_HEADER)module; 37 | auto ntHeaders = (PIMAGE_NT_HEADERS)((std::uint8_t*)module + dosHeader->e_lfanew); 38 | return ntHeaders->FileHeader.TimeDateStamp; 39 | } 40 | 41 | // CSGOSimple's pattern scan 42 | // https://github.com/OneshotGH/CSGOSimple-master/blob/master/CSGOSimple/helpers/utils.cpp 43 | std::uint8_t* PatternScan(void* module, const char* signature) 44 | { 45 | static auto pattern_to_byte = [](const char* pattern) { 46 | auto bytes = std::vector{}; 47 | auto start = const_cast(pattern); 48 | auto end = const_cast(pattern) + strlen(pattern); 49 | 50 | for (auto current = start; current < end; ++current) { 51 | if (*current == '?') { 52 | ++current; 53 | if (*current == '?') 54 | ++current; 55 | bytes.push_back(-1); 56 | } 57 | else { 58 | bytes.push_back(strtoul(current, ¤t, 16)); 59 | } 60 | } 61 | return bytes; 62 | }; 63 | 64 | auto dosHeader = (PIMAGE_DOS_HEADER)module; 65 | auto ntHeaders = (PIMAGE_NT_HEADERS)((std::uint8_t*)module + dosHeader->e_lfanew); 66 | 67 | auto sizeOfImage = ntHeaders->OptionalHeader.SizeOfImage; 68 | auto patternBytes = pattern_to_byte(signature); 69 | auto scanBytes = reinterpret_cast(module); 70 | 71 | auto s = patternBytes.size(); 72 | auto d = patternBytes.data(); 73 | 74 | for (auto i = 0ul; i < sizeOfImage - s; ++i) { 75 | bool found = true; 76 | for (auto j = 0ul; j < s; ++j) { 77 | if (scanBytes[i + j] != d[j] && d[j] != -1) { 78 | found = false; 79 | break; 80 | } 81 | } 82 | if (found) { 83 | return &scanBytes[i]; 84 | } 85 | } 86 | return nullptr; 87 | } 88 | 89 | uintptr_t GetAbsolute(uintptr_t address) noexcept 90 | { 91 | return (address + 4 + *reinterpret_cast(address)); 92 | } 93 | 94 | uintptr_t GetAbsolute32(uintptr_t address) noexcept 95 | { 96 | return (*reinterpret_cast(address)); 97 | } 98 | } 99 | 100 | namespace Util 101 | { 102 | std::pair GetPhysicalDesktopDimensions() { 103 | if (DEVMODE devMode{ .dmSize = sizeof(DEVMODE) }; EnumDisplaySettings(nullptr, ENUM_CURRENT_SETTINGS, &devMode)) 104 | return { devMode.dmPelsWidth, devMode.dmPelsHeight }; 105 | 106 | return {}; 107 | } 108 | 109 | bool IsStringInArray(const std::string& value, const std::vector& array) 110 | { 111 | return std::find(array.begin(), array.end(), value) != array.end(); 112 | } 113 | } -------------------------------------------------------------------------------- /src/stdafx.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include --------------------------------------------------------------------------------