├── .clang-format ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .vscode ├── settings.json └── tasks.json ├── LICENSE ├── Memcury.sln ├── Memcury.vcxproj ├── Memcury.vcxproj.filters ├── README.md ├── example.cpp ├── framework.h └── memcury.h /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: WebKit 3 | AlignAfterOpenBracket: DontAlign 4 | BreakBeforeBraces: Allman 5 | NamespaceIndentation: All 6 | SortIncludes: false 7 | 8 | ... 9 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: ["push", "pull_request"] 4 | 5 | env: 6 | SOLUTION_FILE_PATH: . 7 | 8 | BUILD_CONFIGURATION: Debug 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | build: 15 | runs-on: windows-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Add MSBuild to PATH 21 | uses: microsoft/setup-msbuild@v1.0.2 22 | 23 | - name: Restore NuGet packages 24 | working-directory: ${{env.GITHUB_WORKSPACE}} 25 | run: nuget restore ${{env.SOLUTION_FILE_PATH}} 26 | 27 | - name: Build 28 | working-directory: ${{env.GITHUB_WORKSPACE}} 29 | run: msbuild /m /p:Configuration=${{env.BUILD_CONFIGURATION}} ${{env.SOLUTION_FILE_PATH}} 30 | 31 | -------------------------------------------------------------------------------- /.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/main/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 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 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 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | Raider.vcxproj 400 | Raider.vcxproj 401 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "xlocale": "cpp", 4 | "iosfwd": "cpp", 5 | "xstring": "cpp", 6 | "xlocinfo": "cpp", 7 | "initializer_list": "cpp", 8 | "any": "cpp", 9 | "chrono": "cpp", 10 | "vector": "cpp", 11 | "type_traits": "cpp", 12 | "algorithm": "cpp", 13 | "array": "cpp", 14 | "atomic": "cpp", 15 | "bit": "cpp", 16 | "bitset": "cpp", 17 | "cctype": "cpp", 18 | "charconv": "cpp", 19 | "cinttypes": "cpp", 20 | "clocale": "cpp", 21 | "cmath": "cpp", 22 | "compare": "cpp", 23 | "complex": "cpp", 24 | "concepts": "cpp", 25 | "cstddef": "cpp", 26 | "cstdint": "cpp", 27 | "cstdio": "cpp", 28 | "cstdlib": "cpp", 29 | "cstring": "cpp", 30 | "ctime": "cpp", 31 | "cwchar": "cpp", 32 | "deque": "cpp", 33 | "exception": "cpp", 34 | "filesystem": "cpp", 35 | "format": "cpp", 36 | "forward_list": "cpp", 37 | "fstream": "cpp", 38 | "functional": "cpp", 39 | "hash_map": "cpp", 40 | "hash_set": "cpp", 41 | "iomanip": "cpp", 42 | "ios": "cpp", 43 | "iostream": "cpp", 44 | "istream": "cpp", 45 | "iterator": "cpp", 46 | "limits": "cpp", 47 | "list": "cpp", 48 | "locale": "cpp", 49 | "map": "cpp", 50 | "memory": "cpp", 51 | "mutex": "cpp", 52 | "new": "cpp", 53 | "numeric": "cpp", 54 | "optional": "cpp", 55 | "ostream": "cpp", 56 | "queue": "cpp", 57 | "ratio": "cpp", 58 | "set": "cpp", 59 | "sstream": "cpp", 60 | "stack": "cpp", 61 | "stdexcept": "cpp", 62 | "stop_token": "cpp", 63 | "streambuf": "cpp", 64 | "string": "cpp", 65 | "system_error": "cpp", 66 | "thread": "cpp", 67 | "tuple": "cpp", 68 | "typeinfo": "cpp", 69 | "unordered_map": "cpp", 70 | "unordered_set": "cpp", 71 | "utility": "cpp", 72 | "xfacet": "cpp", 73 | "xhash": "cpp", 74 | "xiosbase": "cpp", 75 | "xlocbuf": "cpp", 76 | "xlocmes": "cpp", 77 | "xlocmon": "cpp", 78 | "xlocnum": "cpp", 79 | "xloctime": "cpp", 80 | "xmemory": "cpp", 81 | "xstddef": "cpp", 82 | "xtr1common": "cpp", 83 | "xtree": "cpp", 84 | "xutility": "cpp", 85 | "random": "cpp", 86 | "variant": "cpp", 87 | "codecvt": "cpp", 88 | "regex": "cpp" 89 | } 90 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build (release)", 6 | "type": "shell", 7 | "command": "msbuild", 8 | "args": [ 9 | "/property:GenerateFullPaths=true", 10 | "/property:Configuration=Release", 11 | "/t:build", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "group": "build", 15 | "presentation": { 16 | "reveal": "silent" 17 | }, 18 | "problemMatcher": "$msCompile" 19 | }, 20 | { 21 | "label": "build (debug)", 22 | "type": "shell", 23 | "command": "msbuild", 24 | "args": [ 25 | "/property:GenerateFullPaths=true", 26 | "/t:build", 27 | "/consoleloggerparameters:NoSummary" 28 | ], 29 | "group": "build", 30 | "presentation": { 31 | "reveal": "silent" 32 | }, 33 | "problemMatcher": "$msCompile" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Kareem Olim 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 | -------------------------------------------------------------------------------- /Memcury.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31829.152 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Memcury", "Memcury.vcxproj", "{98B8B9DE-C4C2-4AF5-8965-A889B218033F}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {98B8B9DE-C4C2-4AF5-8965-A889B218033F}.Debug|x64.ActiveCfg = Debug|x64 17 | {98B8B9DE-C4C2-4AF5-8965-A889B218033F}.Debug|x64.Build.0 = Debug|x64 18 | {98B8B9DE-C4C2-4AF5-8965-A889B218033F}.Debug|x86.ActiveCfg = Debug|Win32 19 | {98B8B9DE-C4C2-4AF5-8965-A889B218033F}.Debug|x86.Build.0 = Debug|Win32 20 | {98B8B9DE-C4C2-4AF5-8965-A889B218033F}.Release|x64.ActiveCfg = Release|x64 21 | {98B8B9DE-C4C2-4AF5-8965-A889B218033F}.Release|x64.Build.0 = Release|x64 22 | {98B8B9DE-C4C2-4AF5-8965-A889B218033F}.Release|x86.ActiveCfg = Release|Win32 23 | {98B8B9DE-C4C2-4AF5-8965-A889B218033F}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {656A2E2A-CB83-40F9-84B2-56D1204A57C1} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /Memcury.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | Win32Proj 24 | {98b8b9de-c4c2-4af5-8965-a889b218033f} 25 | Memcury 26 | 10.0 27 | 28 | 29 | 30 | DynamicLibrary 31 | true 32 | v143 33 | Unicode 34 | 35 | 36 | DynamicLibrary 37 | false 38 | v143 39 | true 40 | Unicode 41 | 42 | 43 | DynamicLibrary 44 | true 45 | v143 46 | Unicode 47 | 48 | 49 | DynamicLibrary 50 | false 51 | v143 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | 76 | 77 | false 78 | 79 | 80 | true 81 | 82 | 83 | false 84 | 85 | 86 | 87 | Level3 88 | true 89 | WIN32;_DEBUG;MEMCURY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 90 | true 91 | NotUsing 92 | pch.h 93 | stdcpplatest 94 | 95 | 96 | Windows 97 | true 98 | false 99 | 100 | 101 | 102 | 103 | Level3 104 | true 105 | true 106 | true 107 | WIN32;NDEBUG;MEMCURY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 108 | true 109 | NotUsing 110 | pch.h 111 | stdcpplatest 112 | 113 | 114 | Windows 115 | true 116 | true 117 | true 118 | false 119 | 120 | 121 | 122 | 123 | Level3 124 | true 125 | _DEBUG;MEMCURY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 126 | true 127 | NotUsing 128 | pch.h 129 | stdcpplatest 130 | 131 | 132 | Windows 133 | true 134 | false 135 | 136 | 137 | 138 | 139 | Level3 140 | true 141 | true 142 | true 143 | NDEBUG;MEMCURY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 144 | true 145 | NotUsing 146 | pch.h 147 | stdcpplatest 148 | 149 | 150 | Windows 151 | true 152 | true 153 | true 154 | false 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /Memcury.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {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 | Header Files 20 | 21 | 22 | 23 | 24 | Source Files 25 | 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # memcury.h 2 | 3 | A simple single header solution for memory manipulation in C++. 4 | 5 | ## Usage 6 | 7 | Just `#include "memcury.h"` in your code!. 8 | 9 | Check [example](/example.cpp) for code examples. 10 | 11 | The current supported platform is Windows only, no plans for supporting more platforms, and c++23 or above. 12 | 13 | This project was intented to be used internaly (from a dynamic link library). 14 | 15 | ## The pitch 16 | 17 | - Containers: 18 | 19 | - PE::Address: A pointer container. 20 | - PE::Section: Portable executable section container for internal usage. 21 | 22 | - Modules: 23 | 24 | - Scanner: 25 | 26 | - Constructors: 27 | 28 | - Default: Takes a pointer to start the scanning from. 29 | - FindPattern: Finds a pattern in memory. 30 | - FindStringRef: Finds a string reference in memory, supports all types of strings. 31 | 32 | - Functions: 33 | - SetTargetModule: Sets the target module for the scanner. 34 | - ScanFor: Scans for a byte(s) near the current address. 35 | - FindFunctionBoundary: Finds the boundary of a function near the current address. 36 | - RelativeOffset: Gets the relative offset of the current address. 37 | - AbsoluteOffset: Gets the absolute offset of the current address. 38 | - GetAs: Gets the current address as a type. 39 | - Jump: Follow any jump instruction. 40 | - Get: Gets the current address as an int64. 41 | 42 | - TrampolineHook: 43 | 44 | - Constructors: 45 | - Default: Takes a pointer pointer to the target function and a pointer to the hook function. 46 | - Functions: 47 | - Commit: Commits the hook. 48 | - Revert: Reverts the hook. 49 | - Toggle: Toggles the hook on\off. 50 | 51 | - VEHHook: 52 | - Functions: 53 | - Init: Initializes the VEH Hook system. 54 | - AddHook: Adds a hook to the VEH Hook system. 55 | - RemoveHook: Removes a hook from the VEH Hook system. 56 | -------------------------------------------------------------------------------- /example.cpp: -------------------------------------------------------------------------------- 1 | #include "framework.h" 2 | #include "memcury.h" 3 | 4 | #define DefineNative(address, ret, name, params) inline ret(*name) params = (ret(*) params)(address); 5 | 6 | DefineNative(reinterpret_cast(GetModuleHandleA("GameAssembly.dll")) + 0x0104D670, int32_t, TestFunc, (void* __this, void** bytes, int32_t offset, void* value, void* formatterResolver, void* method)); 7 | 8 | constexpr const wchar_t* testStringRef = L"%s %s SetTimer passed a negative or zero time. The associated timer may fail to be created/fire! If using InitialStartDelayVariance, be sure it is smaller than (Time + InitialStartDelay)."; 9 | 10 | constexpr const char* jumpPatternExample = "74 05 E8 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? 48 89 5C 24 ??"; 11 | 12 | static void Main(HMODULE hModule) 13 | { 14 | if constexpr (Memcury::Globals::bLogging) 15 | { 16 | AllocConsole(); 17 | 18 | FILE* pFile; 19 | freopen_s(&pFile, "CONOUT$", "w", stdout); 20 | } 21 | 22 | Memcury::Safety::SetExceptionMode(); 23 | //*((unsigned int*)0) = 0xDEAD; 24 | 25 | auto scanner = Memcury::Scanner::FindStringRef(testStringRef) 26 | .ScanFor({ Memcury::ASM::Mnemonic("CALL") }, false) 27 | .RelativeOffset(1); 28 | 29 | //Jump example 30 | auto scanner2 = Memcury::Scanner::FindPattern(jumpPatternExample) 31 | .Jump() 32 | .RelativeOffset(3); 33 | 34 | printf("%p\n%p\n", scanner.GetAs(), scanner2.GetAs()); 35 | } 36 | 37 | bool DllMain(HMODULE hModule, DWORD ulReason, LPVOID lpReserved) 38 | { 39 | if (ulReason == DLL_PROCESS_ATTACH) 40 | { 41 | CreateThread(nullptr, 0, reinterpret_cast(&Main), hModule, 0, nullptr); 42 | } 43 | 44 | return true; 45 | } -------------------------------------------------------------------------------- /framework.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN 4 | #include 5 | -------------------------------------------------------------------------------- /memcury.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | Memcury is a single-header file library for memory manipulation in C++. 5 | 6 | Containers: 7 | -PE::Address: A pointer container. 8 | -PE::Section: Portable executable section container for internal usage. 9 | 10 | Modules: 11 | -Scanner: 12 | -Constructors: 13 | -Default: Takes a pointer to start the scanning from. 14 | -FindPattern: Finds a pattern in memory. 15 | -FindStringRef: Finds a string reference in memory, supports all types of strings. 16 | -Functions: 17 | -SetTargetModule: Sets the target module for the scanner. 18 | -ScanFor: Scans for a byte(s) near the current address. 19 | -FindFunctionBoundary: Finds the boundary of a function near the current address. 20 | -RelativeOffset: Gets the relative offset of the current address. 21 | -AbsoluteOffset: Gets the absolute offset of the current address. 22 | -GetAs: Gets the current address as a type. 23 | -Get: Gets the current address as an int64. 24 | 25 | -TrampolineHook: 26 | -Constructors: 27 | -Default: Takes a pointer pointer to the target function and a pointer to the hook function. 28 | -Functions: 29 | -Commit: Commits the hook. 30 | -Revert: Reverts the hook. 31 | -Toggle: Toggles the hook on\off. 32 | 33 | -VEHHook: 34 | -Functions: 35 | -Init: Initializes the VEH Hook system. 36 | -AddHook: Adds a hook to the VEH Hook system. 37 | -RemoveHook: Removes a hook from the VEH Hook system. 38 | */ 39 | 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #pragma comment(lib, "Dbghelp.lib") 50 | 51 | #define MemcuryAssert(cond) \ 52 | if (!(cond)) \ 53 | { \ 54 | MessageBoxA(nullptr, #cond, __FUNCTION__, MB_ICONERROR | MB_OK); \ 55 | Memcury::Safety::FreezeCurrentThread(); \ 56 | } 57 | 58 | #define MemcuryAssertM(cond, msg) \ 59 | if (!(cond)) \ 60 | { \ 61 | MessageBoxA(nullptr, msg, __FUNCTION__, MB_ICONERROR | MB_OK); \ 62 | Memcury::Safety::FreezeCurrentThread(); \ 63 | } 64 | 65 | #define MemcuryThrow(msg) \ 66 | MessageBoxA(nullptr, msg, __FUNCTION__, MB_ICONERROR | MB_OK); \ 67 | Memcury::Safety::FreezeCurrentThread(); 68 | 69 | namespace Memcury 70 | { 71 | extern "C" IMAGE_DOS_HEADER __ImageBase; 72 | 73 | inline auto GetCurrentModule() -> HMODULE 74 | { 75 | return reinterpret_cast(&__ImageBase); 76 | } 77 | 78 | namespace Util 79 | { 80 | template 81 | constexpr static auto IsInRange(T value, T min, T max) -> bool 82 | { 83 | return value >= min && value < max; 84 | } 85 | 86 | constexpr auto StrHash(const char* str, int h = 0) -> unsigned int 87 | { 88 | return !str[h] ? 5381 : (StrHash(str, h + 1) * 33) ^ str[h]; 89 | } 90 | 91 | inline auto IsSamePage(void* A, void* B) -> bool 92 | { 93 | MEMORY_BASIC_INFORMATION InfoA; 94 | if (!VirtualQuery(A, &InfoA, sizeof(InfoA))) 95 | { 96 | return true; 97 | } 98 | 99 | MEMORY_BASIC_INFORMATION InfoB; 100 | if (!VirtualQuery(B, &InfoB, sizeof(InfoB))) 101 | { 102 | return true; 103 | } 104 | 105 | return InfoA.BaseAddress == InfoB.BaseAddress; 106 | } 107 | 108 | inline auto GetModuleStartAndEnd() -> std::pair 109 | { 110 | auto HModule = GetCurrentModule(); 111 | auto NTHeaders = reinterpret_cast((uintptr_t)HModule + reinterpret_cast((uintptr_t)HModule)->e_lfanew); 112 | 113 | uintptr_t dllStart = (uintptr_t)HModule; 114 | uintptr_t dllEnd = (uintptr_t)HModule + NTHeaders->OptionalHeader.SizeOfImage; 115 | 116 | return { dllStart, dllEnd }; 117 | } 118 | 119 | inline auto CopyToClipboard(std::string str) 120 | { 121 | auto mem = GlobalAlloc(GMEM_FIXED, str.size() + 1); 122 | memcpy(mem, str.c_str(), str.size() + 1); 123 | 124 | OpenClipboard(nullptr); 125 | EmptyClipboard(); 126 | SetClipboardData(CF_TEXT, mem); 127 | CloseClipboard(); 128 | 129 | GlobalFree(mem); 130 | } 131 | } 132 | 133 | namespace Safety 134 | { 135 | enum class ExceptionMode 136 | { 137 | None, 138 | CatchDllExceptionsOnly, 139 | CatchAllExceptions 140 | }; 141 | 142 | static auto FreezeCurrentThread() -> void 143 | { 144 | SuspendThread(GetCurrentThread()); 145 | } 146 | 147 | static auto PrintStack(CONTEXT* ctx) -> void 148 | { 149 | STACKFRAME64 stack; 150 | memset(&stack, 0, sizeof(STACKFRAME64)); 151 | 152 | auto process = GetCurrentProcess(); 153 | auto thread = GetCurrentThread(); 154 | 155 | SymInitialize(process, NULL, TRUE); 156 | 157 | bool result; 158 | DWORD64 displacement = 0; 159 | 160 | char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)] { 0 }; 161 | char name[256] { 0 }; 162 | char module[256] { 0 }; 163 | 164 | PSYMBOL_INFO symbolInfo = (PSYMBOL_INFO)buffer; 165 | 166 | for (ULONG frame = 0;; frame++) 167 | { 168 | result = StackWalk64( 169 | IMAGE_FILE_MACHINE_AMD64, 170 | process, 171 | thread, 172 | &stack, 173 | ctx, 174 | NULL, 175 | SymFunctionTableAccess64, 176 | SymGetModuleBase64, 177 | NULL); 178 | 179 | if (!result) 180 | break; 181 | 182 | symbolInfo->SizeOfStruct = sizeof(SYMBOL_INFO); 183 | symbolInfo->MaxNameLen = MAX_SYM_NAME; 184 | SymFromAddr(process, (ULONG64)stack.AddrPC.Offset, &displacement, symbolInfo); 185 | 186 | HMODULE hModule = NULL; 187 | lstrcpyA(module, ""); 188 | GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (const wchar_t*)(stack.AddrPC.Offset), &hModule); 189 | 190 | if (hModule != NULL) 191 | GetModuleFileNameA(hModule, module, 256); 192 | 193 | printf("[%lu] Name: %s - Address: %p - Module: %s\n", frame, symbolInfo->Name, (void*)symbolInfo->Address, module); 194 | } 195 | } 196 | 197 | template 198 | auto MemcuryGlobalHandler(EXCEPTION_POINTERS* ExceptionInfo) -> long 199 | { 200 | auto [dllStart, dllEnd] = Util::GetModuleStartAndEnd(); 201 | 202 | if constexpr (mode == ExceptionMode::CatchDllExceptionsOnly) 203 | { 204 | if (!Util::IsInRange(ExceptionInfo->ContextRecord->Rip, dllStart, dllEnd)) 205 | { 206 | return EXCEPTION_CONTINUE_SEARCH; 207 | } 208 | } 209 | 210 | auto message = std::format("Memcury caught an exception at [{:x}]\nPress Yes if you want the address to be copied to your clipboard", ExceptionInfo->ContextRecord->Rip); 211 | if (MessageBoxA(nullptr, message.c_str(), "Error", MB_ICONERROR | MB_YESNO) == IDYES) 212 | { 213 | std::string clip = std::format("{:x}", ExceptionInfo->ContextRecord->Rip); 214 | Util::CopyToClipboard(clip); 215 | } 216 | 217 | PrintStack(ExceptionInfo->ContextRecord); 218 | 219 | FreezeCurrentThread(); 220 | 221 | return EXCEPTION_EXECUTE_HANDLER; 222 | } 223 | 224 | template 225 | static auto SetExceptionMode() -> void 226 | { 227 | SetUnhandledExceptionFilter(MemcuryGlobalHandler); 228 | } 229 | } 230 | 231 | namespace Globals 232 | { 233 | constexpr const bool bLogging = true; 234 | 235 | inline const char* moduleName = nullptr; 236 | } 237 | 238 | namespace ASM 239 | { 240 | //@todo: this whole namespace needs a rework, should somehow make this more modern and less ugly. 241 | enum MNEMONIC : uint8_t 242 | { 243 | JMP_REL8 = 0xEB, 244 | JMP_REL32 = 0xE9, 245 | JMP_EAX = 0xE0, 246 | CALL = 0xE8, 247 | LEA = 0x8D, 248 | CDQ = 0x99, 249 | CMOVL = 0x4C, 250 | CMOVS = 0x48, 251 | CMOVNS = 0x49, 252 | NOP = 0x90, 253 | INT3 = 0xCC, 254 | RETN_REL8 = 0xC2, 255 | RETN = 0xC3, 256 | NONE = 0x00 257 | }; 258 | 259 | constexpr int SIZE_OF_JMP_RELATIVE_INSTRUCTION = 5; 260 | constexpr int SIZE_OF_JMP_ABSLOUTE_INSTRUCTION = 13; 261 | 262 | constexpr auto MnemonicToString(MNEMONIC e) -> const char* 263 | { 264 | switch (e) 265 | { 266 | case JMP_REL8: 267 | return "JMP_REL8"; 268 | case JMP_REL32: 269 | return "JMP_REL32"; 270 | case JMP_EAX: 271 | return "JMP_EAX"; 272 | case CALL: 273 | return "CALL"; 274 | case LEA: 275 | return "LEA"; 276 | case CDQ: 277 | return "CDQ"; 278 | case CMOVL: 279 | return "CMOVL"; 280 | case CMOVS: 281 | return "CMOVS"; 282 | case CMOVNS: 283 | return "CMOVNS"; 284 | case NOP: 285 | return "NOP"; 286 | case INT3: 287 | return "INT3"; 288 | case RETN_REL8: 289 | return "RETN_REL8"; 290 | case RETN: 291 | return "RETN"; 292 | case NONE: 293 | return "NONE"; 294 | default: 295 | return "UNKNOWN"; 296 | } 297 | } 298 | 299 | constexpr auto Mnemonic(const char* s) -> MNEMONIC 300 | { 301 | switch (Util::StrHash(s)) 302 | { 303 | case Util::StrHash("JMP_REL8"): 304 | return JMP_REL8; 305 | case Util::StrHash("JMP_REL32"): 306 | return JMP_REL32; 307 | case Util::StrHash("JMP_EAX"): 308 | return JMP_EAX; 309 | case Util::StrHash("CALL"): 310 | return CALL; 311 | case Util::StrHash("LEA"): 312 | return LEA; 313 | case Util::StrHash("CDQ"): 314 | return CDQ; 315 | case Util::StrHash("CMOVL"): 316 | return CMOVL; 317 | case Util::StrHash("CMOVS"): 318 | return CMOVS; 319 | case Util::StrHash("CMOVNS"): 320 | return CMOVNS; 321 | case Util::StrHash("NOP"): 322 | return NOP; 323 | case Util::StrHash("INT3"): 324 | return INT3; 325 | case Util::StrHash("RETN_REL8"): 326 | return RETN_REL8; 327 | case Util::StrHash("RETN"): 328 | return RETN; 329 | default: 330 | return NONE; 331 | } 332 | } 333 | 334 | inline auto byteIsA(uint8_t byte, MNEMONIC opcode) -> bool 335 | { 336 | return byte == opcode; 337 | } 338 | 339 | inline auto byteIsAscii(uint8_t byte) -> bool 340 | { 341 | static constexpr bool isAscii[0x100] = { 342 | false, false, false, false, false, false, false, false, false, true, true, false, false, true, false, false, 343 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, 344 | true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, 345 | true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, 346 | true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, 347 | true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, 348 | true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, 349 | true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false, 350 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, 351 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, 352 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, 353 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, 354 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, 355 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, 356 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, 357 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false 358 | }; 359 | 360 | return isAscii[byte]; 361 | } 362 | 363 | inline bool isJump(uint8_t byte) 364 | { 365 | return byte >= 0x70 && byte <= 0x7F; 366 | } 367 | 368 | static auto pattern2bytes(const char* pattern) -> std::vector 369 | { 370 | auto bytes = std::vector {}; 371 | const auto start = const_cast(pattern); 372 | const auto end = const_cast(pattern) + strlen(pattern); 373 | 374 | for (auto current = start; current < end; ++current) 375 | { 376 | if (*current == '?') 377 | { 378 | ++current; 379 | if (*current == '?') 380 | ++current; 381 | bytes.push_back(-1); 382 | } 383 | else 384 | { 385 | bytes.push_back(strtoul(current, ¤t, 16)); 386 | } 387 | } 388 | return bytes; 389 | } 390 | } 391 | 392 | namespace PE 393 | { 394 | inline auto SetCurrentModule(const char* moduleName) -> void 395 | { 396 | Globals::moduleName = moduleName; 397 | } 398 | 399 | inline auto GetModuleBase() -> uintptr_t 400 | { 401 | return reinterpret_cast(GetModuleHandleA(Globals::moduleName)); 402 | } 403 | 404 | inline auto GetDOSHeader() -> PIMAGE_DOS_HEADER 405 | { 406 | return reinterpret_cast(GetModuleBase()); 407 | } 408 | 409 | inline auto GetNTHeaders() -> PIMAGE_NT_HEADERS 410 | { 411 | return reinterpret_cast(GetModuleBase() + GetDOSHeader()->e_lfanew); 412 | } 413 | 414 | class Address 415 | { 416 | uintptr_t _address; 417 | 418 | public: 419 | Address() 420 | { 421 | _address = 0; 422 | } 423 | 424 | Address(uintptr_t address) 425 | : _address(address) 426 | { 427 | } 428 | 429 | Address(void* address) 430 | : _address(reinterpret_cast(address)) 431 | { 432 | } 433 | 434 | auto operator=(uintptr_t address) -> Address 435 | { 436 | _address = address; 437 | return *this; 438 | } 439 | 440 | auto operator=(void* address) -> Address 441 | { 442 | _address = reinterpret_cast(address); 443 | return *this; 444 | } 445 | 446 | auto operator+(uintptr_t offset) -> Address 447 | { 448 | return Address(_address + offset); 449 | } 450 | 451 | bool operator>(uintptr_t offset) 452 | { 453 | return _address > offset; 454 | } 455 | 456 | bool operator>(Address address) 457 | { 458 | return _address > address._address; 459 | } 460 | 461 | bool operator<(uintptr_t offset) 462 | { 463 | return _address < offset; 464 | } 465 | 466 | bool operator<(Address address) 467 | { 468 | return _address < address._address; 469 | } 470 | 471 | bool operator>=(uintptr_t offset) 472 | { 473 | return _address >= offset; 474 | } 475 | 476 | bool operator>=(Address address) 477 | { 478 | return _address >= address._address; 479 | } 480 | 481 | bool operator<=(uintptr_t offset) 482 | { 483 | return _address <= offset; 484 | } 485 | 486 | bool operator<=(Address address) 487 | { 488 | return _address <= address._address; 489 | } 490 | 491 | bool operator==(uintptr_t offset) 492 | { 493 | return _address == offset; 494 | } 495 | 496 | bool operator==(Address address) 497 | { 498 | return _address == address._address; 499 | } 500 | 501 | bool operator!=(uintptr_t offset) 502 | { 503 | return _address != offset; 504 | } 505 | 506 | bool operator!=(Address address) 507 | { 508 | return _address != address._address; 509 | } 510 | 511 | auto RelativeOffset(uint32_t offset) -> Address 512 | { 513 | _address = ((_address + offset + 4) + *(int32_t*)(_address + offset)); 514 | return *this; 515 | } 516 | 517 | auto AbsoluteOffset(uint32_t offset) -> Address 518 | { 519 | _address = _address + offset; 520 | return *this; 521 | } 522 | 523 | auto Jump() -> Address 524 | { 525 | if (ASM::isJump(*reinterpret_cast(_address))) 526 | { 527 | UINT8 toSkip = *reinterpret_cast(_address + 1); 528 | _address = _address + 2 + toSkip; 529 | } 530 | 531 | return *this; 532 | } 533 | 534 | auto Get() -> uintptr_t 535 | { 536 | return _address; 537 | } 538 | 539 | template 540 | auto GetAs() -> T 541 | { 542 | return reinterpret_cast(_address); 543 | } 544 | 545 | auto IsValid() -> bool 546 | { 547 | return _address != 0; 548 | } 549 | }; 550 | 551 | class Section 552 | { 553 | public: 554 | std::string sectionName; 555 | IMAGE_SECTION_HEADER rawSection; 556 | 557 | static auto GetAllSections() -> std::vector
558 | { 559 | std::vector
sections; 560 | 561 | auto sectionsSize = GetNTHeaders()->FileHeader.NumberOfSections; 562 | auto section = IMAGE_FIRST_SECTION(GetNTHeaders()); 563 | 564 | for (WORD i = 0; i < sectionsSize; i++, section++) 565 | { 566 | auto secName = std::string((char*)section->Name); 567 | 568 | sections.push_back({ secName, *section }); 569 | } 570 | 571 | return sections; 572 | } 573 | 574 | static auto GetSection(std::string sectionName) -> Section 575 | { 576 | for (auto& section : GetAllSections()) 577 | { 578 | if (section.sectionName == sectionName) 579 | { 580 | return section; 581 | } 582 | } 583 | 584 | MemcuryThrow("Section not found"); 585 | return Section {}; 586 | } 587 | 588 | auto GetSectionSize() -> uint32_t 589 | { 590 | return rawSection.Misc.VirtualSize; 591 | } 592 | 593 | auto GetSectionStart() -> Address 594 | { 595 | return Address(GetModuleBase() + rawSection.VirtualAddress); 596 | } 597 | 598 | auto GetSectionEnd() -> Address 599 | { 600 | return Address(GetSectionStart() + GetSectionSize()); 601 | } 602 | 603 | auto isInSection(Address address) -> bool 604 | { 605 | return address >= GetSectionStart() && address < GetSectionEnd(); 606 | } 607 | }; 608 | } 609 | 610 | class Scanner 611 | { 612 | PE::Address _address; 613 | 614 | public: 615 | Scanner(PE::Address address) 616 | : _address(address) 617 | { 618 | } 619 | 620 | static auto SetTargetModule(const char* moduleName) -> void 621 | { 622 | PE::SetCurrentModule(moduleName); 623 | } 624 | 625 | static auto FindPatternEx(HANDLE handle, const char* pattern, const char* mask, uint64_t begin, uint64_t end) -> Scanner 626 | { 627 | auto scan = [](const char* pattern, const char* mask, char* begin, unsigned int size) -> char* 628 | { 629 | size_t patternLen = strlen(mask); 630 | for (unsigned int i = 0; i < size - patternLen; i++) 631 | { 632 | bool found = true; 633 | for (unsigned int j = 0; j < patternLen; j++) 634 | { 635 | if (mask[j] != '?' && pattern[j] != *(begin + i + j)) 636 | { 637 | found = false; 638 | break; 639 | } 640 | } 641 | 642 | if (found) 643 | return (begin + i); 644 | } 645 | return nullptr; 646 | }; 647 | 648 | uint64_t match = NULL; 649 | SIZE_T bytesRead; 650 | char* buffer = nullptr; 651 | MEMORY_BASIC_INFORMATION mbi = { 0 }; 652 | 653 | uint64_t curr = begin; 654 | 655 | for (uint64_t curr = begin; curr < end; curr += mbi.RegionSize) 656 | { 657 | if (!VirtualQueryEx(handle, (void*)curr, &mbi, sizeof(mbi))) 658 | continue; 659 | 660 | if (mbi.State != MEM_COMMIT || mbi.Protect == PAGE_NOACCESS) 661 | continue; 662 | 663 | buffer = new char[mbi.RegionSize]; 664 | 665 | if (ReadProcessMemory(handle, mbi.BaseAddress, buffer, mbi.RegionSize, &bytesRead)) 666 | { 667 | char* internalAddr = scan(pattern, mask, buffer, (unsigned int)bytesRead); 668 | 669 | if (internalAddr != nullptr) 670 | { 671 | match = curr + (uint64_t)(internalAddr - buffer); 672 | break; 673 | } 674 | } 675 | } 676 | delete[] buffer; 677 | 678 | MemcuryAssertM(match != 0, "FindPatternEx return nullptr"); 679 | 680 | return Scanner(match); 681 | } 682 | 683 | static auto FindPatternEx(HANDLE handle, const char* sig) -> Scanner 684 | { 685 | char pattern[100]; 686 | char mask[100]; 687 | 688 | char lastChar = ' '; 689 | unsigned int j = 0; 690 | 691 | for (unsigned int i = 0; i < strlen(sig); i++) 692 | { 693 | if ((sig[i] == '?' || sig[i] == '*') && (lastChar != '?' && lastChar != '*')) 694 | { 695 | pattern[j] = mask[j] = '?'; 696 | j++; 697 | } 698 | 699 | else if (isspace(lastChar)) 700 | { 701 | pattern[j] = lastChar = (char)strtol(&sig[i], 0, 16); 702 | mask[j] = 'x'; 703 | j++; 704 | } 705 | lastChar = sig[i]; 706 | } 707 | pattern[j] = mask[j] = '\0'; 708 | 709 | auto module = (uint64_t)GetModuleHandle(nullptr); 710 | 711 | return FindPatternEx(handle, pattern, mask, module, module + Memcury::PE::GetNTHeaders()->OptionalHeader.SizeOfImage); 712 | } 713 | 714 | static auto FindPattern(const char* signature) -> Scanner 715 | { 716 | PE::Address add { nullptr }; 717 | 718 | const auto sizeOfImage = PE::GetNTHeaders()->OptionalHeader.SizeOfImage; 719 | auto patternBytes = ASM::pattern2bytes(signature); 720 | const auto scanBytes = reinterpret_cast(PE::GetModuleBase()); 721 | 722 | const auto s = patternBytes.size(); 723 | const auto d = patternBytes.data(); 724 | 725 | for (auto i = 0ul; i < sizeOfImage - s; ++i) 726 | { 727 | bool found = true; 728 | for (auto j = 0ul; j < s; ++j) 729 | { 730 | if (scanBytes[i + j] != d[j] && d[j] != -1) 731 | { 732 | found = false; 733 | break; 734 | } 735 | } 736 | 737 | if (found) 738 | { 739 | add = reinterpret_cast(&scanBytes[i]); 740 | break; 741 | } 742 | } 743 | 744 | MemcuryAssertM(add != 0, "FindPattern return nullptr"); 745 | 746 | return Scanner(add); 747 | } 748 | 749 | // Supports wide and normal strings both std and pointers 750 | template 751 | static auto FindStringRef(T string, bool find_first = false) -> Scanner 752 | { 753 | PE::Address add { nullptr }; 754 | 755 | constexpr auto bIsWide = std::is_same::value; 756 | constexpr auto bIsChar = std::is_same::value; 757 | 758 | constexpr auto bIsPtr = bIsWide || bIsChar; 759 | 760 | auto textSection = PE::Section::GetSection(".text"); 761 | auto rdataSection = PE::Section::GetSection(".rdata"); 762 | 763 | const auto scanBytes = reinterpret_cast(textSection.GetSectionStart().Get()); 764 | 765 | // scan only text section 766 | for (DWORD i = 0x0; i < textSection.GetSectionSize(); i++) 767 | { 768 | if ((scanBytes[i] == ASM::CMOVL || scanBytes[i] == ASM::CMOVS) && scanBytes[i + 1] == ASM::LEA) 769 | { 770 | auto stringAdd = PE::Address(&scanBytes[i]).RelativeOffset(3); 771 | 772 | // Check if the string is in the .rdata section 773 | if (rdataSection.isInSection(stringAdd)) 774 | { 775 | auto strBytes = stringAdd.GetAs(); 776 | 777 | // Check if the first char is printable 778 | if (ASM::byteIsAscii(strBytes[0])) 779 | { 780 | if constexpr (!bIsPtr) 781 | { 782 | typedef T::value_type char_type; 783 | 784 | auto lea = stringAdd.GetAs(); 785 | 786 | T leaT(lea); 787 | 788 | if (leaT == string) 789 | { 790 | add = PE::Address(&scanBytes[i]); 791 | if(find_first) 792 | break; 793 | } 794 | } 795 | else 796 | { 797 | auto lea = stringAdd.GetAs(); 798 | 799 | if constexpr (bIsWide) 800 | { 801 | if (wcscmp(string, lea) == 0) 802 | { 803 | add = PE::Address(&scanBytes[i]); 804 | if(find_first) 805 | break; 806 | } 807 | } 808 | else 809 | { 810 | if (strcmp(string, lea) == 0) 811 | { 812 | add = PE::Address(&scanBytes[i]); 813 | if(find_first) 814 | break; 815 | } 816 | } 817 | } 818 | } 819 | } 820 | } 821 | } 822 | 823 | MemcuryAssertM(add != 0, "FindStringRef return nullptr"); 824 | 825 | return Scanner(add); 826 | } 827 | 828 | auto Jump() -> Scanner 829 | { 830 | _address.Jump(); 831 | return *this; 832 | } 833 | 834 | auto ScanFor(std::vector opcodesToFind, bool forward = true, int toSkip = 0) -> Scanner 835 | { 836 | const auto scanBytes = _address.GetAs(); 837 | 838 | for (auto i = (forward ? 1 : -1); forward ? (i < 2048) : (i > -2048); forward ? i++ : i--) 839 | { 840 | bool found = true; 841 | 842 | for (int k = 0; k < opcodesToFind.size() && found; k++) 843 | { 844 | if (opcodesToFind[k] == -1) 845 | continue; 846 | found = opcodesToFind[k] == scanBytes[i + k]; 847 | } 848 | 849 | if (found) 850 | { 851 | _address = &scanBytes[i]; 852 | if (toSkip != 0) 853 | { 854 | return ScanFor(opcodesToFind, forward, toSkip - 1); 855 | } 856 | 857 | break; 858 | } 859 | } 860 | 861 | return *this; 862 | } 863 | 864 | auto FindFunctionBoundary(bool forward = false) -> Scanner 865 | { 866 | const auto scanBytes = _address.GetAs(); 867 | 868 | for (auto i = (forward ? 1 : -1); forward ? (i < 2048) : (i > -2048); forward ? i++ : i--) 869 | { 870 | if ( // ASM::byteIsA(scanBytes[i], ASM::MNEMONIC::JMP_REL8) || 871 | // ASM::byteIsA(scanBytes[i], ASM::MNEMONIC::JMP_REL32) || 872 | // ASM::byteIsA(scanBytes[i], ASM::MNEMONIC::JMP_EAX) || 873 | ASM::byteIsA(scanBytes[i], ASM::MNEMONIC::RETN_REL8) || ASM::byteIsA(scanBytes[i], ASM::MNEMONIC::RETN) || ASM::byteIsA(scanBytes[i], ASM::MNEMONIC::INT3)) 874 | { 875 | _address = (uintptr_t)&scanBytes[i + 1]; 876 | break; 877 | } 878 | } 879 | 880 | return *this; 881 | } 882 | 883 | auto RelativeOffset(uint32_t offset) -> Scanner 884 | { 885 | _address.RelativeOffset(offset); 886 | 887 | return *this; 888 | } 889 | 890 | auto AbsoluteOffset(uint32_t offset) -> Scanner 891 | { 892 | _address.AbsoluteOffset(offset); 893 | 894 | return *this; 895 | } 896 | 897 | template 898 | auto GetAs() -> T 899 | { 900 | return _address.GetAs(); 901 | } 902 | 903 | auto Get() -> uintptr_t 904 | { 905 | return _address.Get(); 906 | } 907 | 908 | auto IsValid() -> bool 909 | { 910 | return _address.IsValid(); 911 | } 912 | }; 913 | 914 | /* Bad don't use it tbh... */ 915 | class TrampolineHook 916 | { 917 | void** originalFunctionPtr; 918 | PE::Address originalFunction; 919 | PE::Address hookFunction; 920 | PE::Address allocatedPage; 921 | std::vector restore; 922 | 923 | void PointToCodeIfNot(PE::Address& ptr) 924 | { 925 | auto bytes = ptr.GetAs(); 926 | 927 | if (ASM::byteIsA(bytes[0], ASM::MNEMONIC::JMP_REL32)) 928 | { 929 | ptr = bytes + 5 + *(int32_t*)&bytes[1]; 930 | } 931 | } 932 | 933 | void* AllocatePageNearAddress(void* targetAddr) 934 | { 935 | SYSTEM_INFO sysInfo; 936 | GetSystemInfo(&sysInfo); 937 | const uint64_t PAGE_SIZE = sysInfo.dwPageSize; 938 | 939 | uint64_t startAddr = (uint64_t(targetAddr) & ~(PAGE_SIZE - 1)); // round down to nearest page boundary 940 | uint64_t minAddr = min(startAddr - 0x7FFFFF00, (uint64_t)sysInfo.lpMinimumApplicationAddress); 941 | uint64_t maxAddr = max(startAddr + 0x7FFFFF00, (uint64_t)sysInfo.lpMaximumApplicationAddress); 942 | 943 | uint64_t startPage = (startAddr - (startAddr % PAGE_SIZE)); 944 | 945 | for (uint64_t pageOffset = 1; pageOffset; pageOffset++) 946 | { 947 | uint64_t byteOffset = pageOffset * PAGE_SIZE; 948 | uint64_t highAddr = startPage + byteOffset; 949 | uint64_t lowAddr = (startPage > byteOffset) ? startPage - byteOffset : 0; 950 | 951 | bool needsExit = highAddr > maxAddr && lowAddr < minAddr; 952 | 953 | if (highAddr < maxAddr) 954 | { 955 | void* outAddr = VirtualAlloc((void*)highAddr, PAGE_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); 956 | if (outAddr) 957 | return outAddr; 958 | } 959 | 960 | if (lowAddr > minAddr) 961 | { 962 | void* outAddr = VirtualAlloc((void*)lowAddr, PAGE_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); 963 | if (outAddr != nullptr) 964 | return outAddr; 965 | } 966 | 967 | if (needsExit) 968 | { 969 | break; 970 | } 971 | } 972 | 973 | return nullptr; 974 | } 975 | 976 | void WriteAbsoluteJump(void* jumpLocation, void* destination) 977 | { 978 | uint8_t absJumpInstructions[] = { 979 | ASM::Mnemonic("CMOVNS"), 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r10, addr 980 | 0x41, 0xFF, 0xE2 // jmp r10 981 | }; 982 | 983 | auto destination64 = (uint64_t)destination; 984 | memcpy(&absJumpInstructions[2], &destination64, sizeof(destination64)); 985 | memcpy(jumpLocation, absJumpInstructions, sizeof(absJumpInstructions)); 986 | } 987 | 988 | uintptr_t PrepareRestore() 989 | { 990 | /* 991 | This is not a correct way to do it at all, since not all functions sub from the stack 992 | This needs so much more tests, but it works for now. 993 | */ 994 | 995 | Scanner scanner(originalFunction); 996 | scanner.ScanFor({ 0x48, 0x83, 0xEC }); // sub rsp 997 | 998 | auto restoreSize = scanner.Get() - originalFunction.Get(); 999 | 1000 | MemcuryAssert(restoreSize > 0 && restoreSize < 0x100); 1001 | 1002 | restore.reserve(restoreSize); 1003 | for (auto i = 0; i < restoreSize; i++) 1004 | { 1005 | restore.push_back(originalFunction.GetAs()[i]); 1006 | } 1007 | 1008 | return restoreSize; 1009 | } 1010 | 1011 | void WriteRestore() 1012 | { 1013 | auto restorePtr = allocatedPage + ASM::SIZE_OF_JMP_ABSLOUTE_INSTRUCTION + 2; 1014 | 1015 | memcpy(restorePtr.GetAs(), restore.data(), restore.size()); 1016 | 1017 | *originalFunctionPtr = restorePtr.GetAs(); 1018 | 1019 | // Write a jump back to where the execution should resume 1020 | restorePtr.AbsoluteOffset((uint32_t)restore.size()); 1021 | 1022 | auto contuineExecution = originalFunction + restore.size(); 1023 | 1024 | WriteAbsoluteJump(restorePtr.GetAs(), contuineExecution.GetAs()); 1025 | } 1026 | 1027 | auto PrepareJMPInstruction(uint64_t dst) 1028 | { 1029 | uint8_t bytes[5] = { ASM::Mnemonic("JMP_REL32"), 0x0, 0x0, 0x0, 0x0 }; 1030 | 1031 | const uint64_t relAddr = dst - (originalFunction.Get() + ASM::SIZE_OF_JMP_RELATIVE_INSTRUCTION); 1032 | memcpy(bytes + 1, &relAddr, 4); 1033 | 1034 | return std::move(bytes); 1035 | } 1036 | 1037 | bool IsHooked() 1038 | { 1039 | return originalFunction.GetAs()[0] == ASM::Mnemonic("JMP_REL32"); 1040 | } 1041 | 1042 | public: 1043 | TrampolineHook(void** originalFunction, void* hookFunction) 1044 | { 1045 | this->originalFunctionPtr = originalFunction; 1046 | 1047 | this->originalFunction = *originalFunction; 1048 | this->hookFunction = hookFunction; 1049 | 1050 | PointToCodeIfNot(this->originalFunction); 1051 | PointToCodeIfNot(this->hookFunction); 1052 | }; 1053 | 1054 | bool Commit() 1055 | { 1056 | auto fnStart = originalFunction.GetAs(); 1057 | 1058 | auto restoreSize = PrepareRestore(); 1059 | 1060 | if (!allocatedPage.IsValid()) 1061 | { 1062 | allocatedPage = AllocatePageNearAddress(fnStart); 1063 | } 1064 | 1065 | memset(allocatedPage.GetAs(), ASM::MNEMONIC::INT3, 0x1000); 1066 | 1067 | WriteAbsoluteJump(allocatedPage.GetAs(), hookFunction.GetAs()); 1068 | 1069 | DWORD oldProtect; 1070 | VirtualProtect(fnStart, 1024, PAGE_EXECUTE_READWRITE, &oldProtect); 1071 | 1072 | auto jmpInstruction = PrepareJMPInstruction(allocatedPage.Get()); 1073 | 1074 | WriteRestore(); 1075 | 1076 | memset(fnStart, ASM::MNEMONIC::INT3, restoreSize); 1077 | memcpy(fnStart, jmpInstruction, ASM::SIZE_OF_JMP_RELATIVE_INSTRUCTION); 1078 | 1079 | return true; 1080 | } 1081 | 1082 | bool Revert() 1083 | { 1084 | auto fnStart = originalFunction.GetAs(); 1085 | 1086 | DWORD oldProtect; 1087 | VirtualProtect(fnStart, 1024, PAGE_EXECUTE_READWRITE, &oldProtect); 1088 | 1089 | memcpy(fnStart, restore.data(), restore.size()); 1090 | 1091 | *originalFunctionPtr = originalFunction.GetAs(); 1092 | 1093 | // VirtualFree(allocatedPage.GetAs(), 0x1000, MEM_RELEASE); 1094 | 1095 | return true; 1096 | } 1097 | 1098 | auto Toggle() 1099 | { 1100 | if (IsHooked()) 1101 | Revert(); 1102 | else 1103 | Commit(); 1104 | 1105 | return IsHooked(); 1106 | } 1107 | }; 1108 | 1109 | namespace VEHHook 1110 | { 1111 | struct HOOK_INFO 1112 | { 1113 | void* Original; 1114 | void* Detour; 1115 | 1116 | HOOK_INFO(void* Original, void* Detour) 1117 | : Original(Original) 1118 | , Detour(Detour) 1119 | { 1120 | } 1121 | }; 1122 | 1123 | inline std::vector Hooks; 1124 | inline std::vector HookProtections; 1125 | inline HANDLE ExceptionHandler; 1126 | 1127 | inline long Handler(EXCEPTION_POINTERS* Exception) 1128 | { 1129 | if (Exception->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) 1130 | { 1131 | auto Itr = std::find_if(Hooks.begin(), Hooks.end(), [Rip = Exception->ContextRecord->Rip](const HOOK_INFO& Hook) 1132 | { return Hook.Original == (void*)Rip; }); 1133 | if (Itr != Hooks.end()) 1134 | { 1135 | Exception->ContextRecord->Rip = (uintptr_t)Itr->Detour; 1136 | } 1137 | 1138 | Exception->ContextRecord->EFlags |= 0x100; // SINGLE_STEP_FLAG 1139 | 1140 | return EXCEPTION_CONTINUE_EXECUTION; 1141 | } 1142 | else if (Exception->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) 1143 | { 1144 | // TODO: find a way to only vp the function that about to get executed 1145 | for (auto& Hook : Hooks) 1146 | { 1147 | DWORD dwOldProtect; 1148 | VirtualProtect(Hook.Original, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &dwOldProtect); 1149 | } 1150 | 1151 | return EXCEPTION_CONTINUE_EXECUTION; 1152 | } 1153 | 1154 | return EXCEPTION_CONTINUE_SEARCH; 1155 | } 1156 | 1157 | inline bool Init() 1158 | { 1159 | if (ExceptionHandler == nullptr) 1160 | { 1161 | ExceptionHandler = AddVectoredExceptionHandler(true, (PVECTORED_EXCEPTION_HANDLER)Handler); 1162 | } 1163 | return ExceptionHandler != nullptr; 1164 | } 1165 | 1166 | inline bool AddHook(void* Target, void* Detour) 1167 | { 1168 | if (ExceptionHandler == nullptr) 1169 | { 1170 | return false; 1171 | } 1172 | 1173 | if (Util::IsSamePage(Target, Detour)) 1174 | { 1175 | return false; 1176 | } 1177 | 1178 | if (!VirtualProtect(Target, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &HookProtections.emplace_back())) 1179 | { 1180 | HookProtections.pop_back(); 1181 | return false; 1182 | } 1183 | 1184 | Hooks.emplace_back(Target, Detour); 1185 | return true; 1186 | } 1187 | 1188 | inline bool RemoveHook(void* Original) 1189 | { 1190 | auto Itr = std::find_if(Hooks.begin(), Hooks.end(), [Original](const HOOK_INFO& Hook) 1191 | { return Hook.Original == Original; }); 1192 | 1193 | if (Itr == Hooks.end()) 1194 | { 1195 | return false; 1196 | } 1197 | 1198 | const auto ProtItr = HookProtections.begin() + std::distance(Hooks.begin(), Itr); 1199 | Hooks.erase(Itr); 1200 | 1201 | DWORD dwOldProtect; 1202 | bool Ret = VirtualProtect(Original, 1, *ProtItr, &dwOldProtect); 1203 | HookProtections.erase(ProtItr); 1204 | 1205 | return false; 1206 | } 1207 | } 1208 | } 1209 | --------------------------------------------------------------------------------