├── .gitattributes ├── .github └── workflows │ └── dotnet.yml ├── .gitignore ├── LICENSE.txt ├── MemHack.sln ├── MemHack ├── MemHackConsole.csproj └── Program.cs ├── MemHackGUI ├── .config │ └── dotnet-tools.json ├── Content │ └── Content.mgcb ├── Icon.bmp ├── Icon.ico ├── MemHackGUI.cs ├── MemHackGUI.csproj ├── Program.cs └── app.manifest ├── MemHackLib ├── IMemHack.cs ├── MemHackLib.csproj ├── PlatformImplementations │ ├── MemHackLin.cs │ └── MemHackWin.cs └── Utils.cs ├── MemHackMe ├── MemHackMe.csproj └── Program.cs ├── MonoGame.ImGuiNet ├── DrawVertDeclaration.cs ├── ImGuiRenderer.cs ├── Monogame.ImGuiNet.csproj └── Utils │ └── FilePicker.cs ├── README.md ├── Resources ├── image-1.png └── image.png ├── build.ps1 └── build.sh /.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/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a .NET project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net 3 | 4 | name: .NET 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Setup .NET 20 | uses: actions/setup-dotnet@v4 21 | with: 22 | dotnet-version: 8.0.x 23 | - name: Restore dependencies 24 | run: dotnet restore 25 | - name: Build 26 | run: dotnet build --no-restore 27 | - name: Test 28 | run: dotnet test --no-build --verbosity normal 29 | -------------------------------------------------------------------------------- /.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 364 | 365 | # Ignore builds folder 366 | builds/ -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 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 | -------------------------------------------------------------------------------- /MemHack.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.11.35327.3 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MemHackConsole", "MemHack\MemHackConsole.csproj", "{AA2A0F91-5D74-4DCB-83ED-410C3B6B37B7}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MemHackMe", "MemHackMe\MemHackMe.csproj", "{85846298-5E64-4DE9-999F-0FA5B4404FA1}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MemHackGUI", "MemHackGUI\MemHackGUI.csproj", "{CAD4C60E-C392-4F44-8DEC-186153AD61EC}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Monogame.ImGuiNet", "MonoGame.ImGuiNet\Monogame.ImGuiNet.csproj", "{6A0E607F-CBB7-4541-9EE9-147216B94B05}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MemHackLib", "MemHackLib\MemHackLib.csproj", "{F91A21C3-360A-4EB5-8F37-6C356F627F15}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {AA2A0F91-5D74-4DCB-83ED-410C3B6B37B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {AA2A0F91-5D74-4DCB-83ED-410C3B6B37B7}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {AA2A0F91-5D74-4DCB-83ED-410C3B6B37B7}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {AA2A0F91-5D74-4DCB-83ED-410C3B6B37B7}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {85846298-5E64-4DE9-999F-0FA5B4404FA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {85846298-5E64-4DE9-999F-0FA5B4404FA1}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {85846298-5E64-4DE9-999F-0FA5B4404FA1}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {85846298-5E64-4DE9-999F-0FA5B4404FA1}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {CAD4C60E-C392-4F44-8DEC-186153AD61EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {CAD4C60E-C392-4F44-8DEC-186153AD61EC}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {CAD4C60E-C392-4F44-8DEC-186153AD61EC}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {CAD4C60E-C392-4F44-8DEC-186153AD61EC}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {6A0E607F-CBB7-4541-9EE9-147216B94B05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {6A0E607F-CBB7-4541-9EE9-147216B94B05}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {6A0E607F-CBB7-4541-9EE9-147216B94B05}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {6A0E607F-CBB7-4541-9EE9-147216B94B05}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {F91A21C3-360A-4EB5-8F37-6C356F627F15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {F91A21C3-360A-4EB5-8F37-6C356F627F15}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {F91A21C3-360A-4EB5-8F37-6C356F627F15}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {F91A21C3-360A-4EB5-8F37-6C356F627F15}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {32CDC973-CF9D-4B23-AA64-48BE65F17A16} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /MemHack/MemHackConsole.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /MemHack/Program.cs: -------------------------------------------------------------------------------- 1 | using MemHackLib; 2 | using System.Collections.Concurrent; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | 6 | namespace MemHack; 7 | 8 | public class Program 9 | { 10 | private static byte[] buffer = []; 11 | private static List foundAddresses = []; 12 | 13 | private static void Main(string[] args) 14 | { 15 | IMemHack memHack = IMemHack.Create(); 16 | 17 | List<(string title, uint processId)> windows = memHack.GetAllProcesses(); 18 | int j = 0; 19 | foreach ((string title, uint processId) window in windows) 20 | { 21 | Console.WriteLine($"{j}. {window.title} - {window.processId}"); 22 | j++; 23 | } 24 | 25 | Console.Write("Enter the window number: "); 26 | bool isWindowNumber = int.TryParse(Console.ReadLine(), out int windowNumber); 27 | 28 | if (!isWindowNumber) 29 | { 30 | Console.WriteLine("Invalid window number"); 31 | return; 32 | } 33 | 34 | if (windowNumber < 0 || windowNumber >= windows.Count) 35 | { 36 | Console.WriteLine("Invalid window number"); 37 | return; 38 | } 39 | 40 | (string title, uint processId) = windows[windowNumber]; 41 | 42 | Console.Write("Enter the value: "); 43 | bool isValue = long.TryParse(Console.ReadLine(), out long desiredValue); 44 | 45 | if (!isValue) 46 | { 47 | Console.WriteLine("Invalid value"); 48 | return; 49 | } 50 | 51 | Console.WriteLine("Searching for addresses..."); 52 | foundAddresses = memHack.MemorySearch(processId, desiredValue); 53 | 54 | int k = 0; 55 | while (true) 56 | { 57 | Console.Clear(); 58 | Console.WriteLine("____________INFO____________"); 59 | Console.WriteLine($"Window title: {title}"); 60 | Console.WriteLine($"Process ID: {processId}"); 61 | Console.WriteLine($"Addresses found: {foundAddresses.Count}"); 62 | Console.WriteLine("____________MENU____________"); 63 | Console.WriteLine("1. Print addresses"); 64 | Console.WriteLine("2. Filter for new value"); 65 | Console.WriteLine("3. Change address value"); 66 | Console.WriteLine("4. Change user given address"); 67 | Console.WriteLine("5. Restart"); 68 | Console.WriteLine("6. Exit"); 69 | 70 | Console.Write("Enter the option: "); 71 | string option = Console.ReadLine(); 72 | if (option != null) 73 | switch (option) 74 | { 75 | case "1": 76 | k = 0; 77 | foreach (nint foundAddress in foundAddresses) 78 | { 79 | long value = Utils.BufferConvert(buffer, 0, memHack.ValueType); 80 | Console.WriteLine($"{k}. 0x{foundAddress.ToInt64():X} - {value}"); 81 | k++; 82 | } 83 | 84 | Console.WriteLine("Press any key to continue..."); 85 | Console.ReadKey(); 86 | break; 87 | case "2": 88 | Console.Write("Enter the new value: "); 89 | isValue = long.TryParse(Console.ReadLine(), out long newValue); 90 | 91 | if (!isValue) 92 | { 93 | Console.WriteLine("Invalid value"); 94 | break; 95 | } 96 | 97 | foundAddresses = memHack.FilterPointers(processId, foundAddresses, newValue); 98 | 99 | break; 100 | case "3": 101 | int index = 0; 102 | if (foundAddresses.Count > 1) 103 | { 104 | Console.WriteLine("Enter the address index: "); 105 | isValue = int.TryParse(Console.ReadLine(), out index); 106 | 107 | if (!isValue || index < 0 || index >= foundAddresses.Count) 108 | { 109 | Console.WriteLine("Invalid index"); 110 | break; 111 | } 112 | } 113 | 114 | Console.WriteLine("Enter the new value: "); 115 | isValue = int.TryParse(Console.ReadLine(), out int newAddressValue); 116 | 117 | if (!isValue) 118 | { 119 | Console.WriteLine("Invalid value"); 120 | break; 121 | } 122 | 123 | memHack.WriteAddressValue(processId, foundAddresses[index], newAddressValue); 124 | 125 | Console.WriteLine("Press any key to continue..."); 126 | Console.ReadKey(); 127 | 128 | break; 129 | case "4": 130 | Console.WriteLine("Enter the address to read: "); 131 | string addressString = Console.ReadLine(); 132 | bool isAddress = long.TryParse(addressString, out long addressValue); 133 | if (!isAddress) 134 | { 135 | Console.WriteLine("Invalid address"); 136 | return; 137 | } 138 | 139 | // read new address value 140 | Console.WriteLine("Enter the new address value: "); 141 | string newAddressString = Console.ReadLine(); 142 | bool isNewAddress = long.TryParse(newAddressString, out long newaddrvalue); 143 | if (!isNewAddress) 144 | { 145 | Console.WriteLine("Invalid new address value"); 146 | return; 147 | } 148 | 149 | memHack.WriteAddressValue(processId, (nint)addressValue, newaddrvalue); 150 | Console.ReadLine(); 151 | 152 | break; 153 | case "5": 154 | buffer = []; 155 | foundAddresses = []; 156 | Main(args); 157 | break; 158 | case "6": 159 | return; 160 | default: 161 | Console.WriteLine("Invalid option"); 162 | break; 163 | } 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /MemHackGUI/.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-mgcb": { 6 | "version": "3.8.2.1105", 7 | "commands": [ 8 | "mgcb" 9 | ] 10 | }, 11 | "dotnet-mgcb-editor": { 12 | "version": "3.8.2.1105", 13 | "commands": [ 14 | "mgcb-editor" 15 | ] 16 | }, 17 | "dotnet-mgcb-editor-linux": { 18 | "version": "3.8.2.1105", 19 | "commands": [ 20 | "mgcb-editor-linux" 21 | ] 22 | }, 23 | "dotnet-mgcb-editor-windows": { 24 | "version": "3.8.2.1105", 25 | "commands": [ 26 | "mgcb-editor-windows" 27 | ] 28 | }, 29 | "dotnet-mgcb-editor-mac": { 30 | "version": "3.8.2.1105", 31 | "commands": [ 32 | "mgcb-editor-mac" 33 | ] 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /MemHackGUI/Content/Content.mgcb: -------------------------------------------------------------------------------- 1 | 2 | #----------------------------- Global Properties ----------------------------# 3 | 4 | /outputDir:bin/$(Platform) 5 | /intermediateDir:obj/$(Platform) 6 | /platform:DesktopGL 7 | /config: 8 | /profile:Reach 9 | /compress:False 10 | 11 | #-------------------------------- References --------------------------------# 12 | 13 | 14 | #---------------------------------- Content ---------------------------------# 15 | 16 | -------------------------------------------------------------------------------- /MemHackGUI/Icon.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vycdev/MemHack/09419a2e484b7e2b29c50e629a5de300c5753be5/MemHackGUI/Icon.bmp -------------------------------------------------------------------------------- /MemHackGUI/Icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vycdev/MemHack/09419a2e484b7e2b29c50e629a5de300c5753be5/MemHackGUI/Icon.ico -------------------------------------------------------------------------------- /MemHackGUI/MemHackGUI.cs: -------------------------------------------------------------------------------- 1 | using ImGuiNET; 2 | using MemHackLib; 3 | using Microsoft.Xna.Framework; 4 | using Microsoft.Xna.Framework.Graphics; 5 | using Microsoft.Xna.Framework.Input; 6 | using MonoGame.ImGuiNet; 7 | using System; 8 | using System.Collections.Generic; 9 | 10 | namespace MemHackGUI; 11 | public class MemHackGUI : Game 12 | { 13 | private GraphicsDeviceManager _graphics; 14 | private SpriteBatch _spriteBatch; 15 | private static ImGuiRenderer GuiRenderer; 16 | bool _toolActive = true; 17 | 18 | bool useProcesses = true; 19 | 20 | List<(string title, uint processId)> processList = []; 21 | int selectedWindowIndex = 0; 22 | 23 | int searchedValue = 0; 24 | int newValue = 0; 25 | 26 | int selectedPointerIndex = -1; 27 | List foundPointers = []; 28 | 29 | int itemsPerPage = 20; // Number of items to display per page 30 | int currentPage = 0; // Track the current page (starting at 0) 31 | 32 | string writeValueResult = ""; 33 | 34 | IMemHack MemHack; 35 | 36 | public MemHackGUI() 37 | { 38 | _graphics = new GraphicsDeviceManager(this); 39 | Content.RootDirectory = "Content"; 40 | IsMouseVisible = true; 41 | 42 | // set window size 43 | _graphics.PreferredBackBufferWidth = 500; 44 | _graphics.PreferredBackBufferHeight = 600; 45 | _graphics.ApplyChanges(); 46 | 47 | // allow window resize 48 | Window.AllowUserResizing = true; 49 | 50 | // set window title 51 | Window.Title = "MemHack"; 52 | 53 | // Init hacking library 54 | MemHack = IMemHack.Create(); 55 | 56 | // Get all opened processes 57 | processList = MemHack.GetAllProcesses(); 58 | } 59 | 60 | protected override void Initialize() 61 | { 62 | GuiRenderer = new ImGuiRenderer(this); 63 | GuiRenderer.RebuildFontAtlas(); 64 | 65 | base.Initialize(); 66 | } 67 | 68 | protected override void LoadContent() 69 | { 70 | _spriteBatch = new SpriteBatch(GraphicsDevice); 71 | } 72 | 73 | protected override void Update(GameTime gameTime) 74 | { 75 | if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) 76 | Exit(); 77 | 78 | base.Update(gameTime); 79 | } 80 | 81 | protected override void Draw(GameTime gameTime) 82 | { 83 | GraphicsDevice.Clear(Color.Black); 84 | base.Draw(gameTime); 85 | 86 | GuiRenderer.BeginLayout(gameTime); 87 | 88 | // Get the application window size 89 | var viewport = GraphicsDevice.Viewport; 90 | var windowSize = new System.Numerics.Vector2(viewport.Width, viewport.Height); 91 | 92 | // Get ImGui IO for configuration 93 | var io = ImGui.GetIO(); 94 | 95 | // Enable docking 96 | io.ConfigFlags |= ImGuiConfigFlags.DockingEnable; 97 | 98 | // Create a root dockspace that occupies the entire game window 99 | ImGui.SetNextWindowPos(System.Numerics.Vector2.Zero); // Position at the top-left corner 100 | ImGui.SetNextWindowSize(windowSize); // Match the size of the game window 101 | ImGui.SetNextWindowViewport(ImGui.GetMainViewport().ID); 102 | 103 | // Set window flags to disable resizing, moving, and closing 104 | var windowFlags = ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove; 105 | windowFlags |= ImGuiWindowFlags.NoBringToFrontOnFocus | ImGuiWindowFlags.NoDocking; 106 | 107 | // Begin the main docking window 108 | ImGui.Begin("DockSpace Window", windowFlags); 109 | 110 | // Create the dockspace 111 | var dockspaceId = ImGui.GetID("MyDockspace"); 112 | ImGui.DockSpace(dockspaceId, System.Numerics.Vector2.Zero, ImGuiDockNodeFlags.AutoHideTabBar); 113 | 114 | // Ensure dockable windows are placed inside the dockspace 115 | ImGui.SetNextWindowDockID(dockspaceId, ImGuiCond.Always); 116 | 117 | // Save previous state of useProcesses 118 | bool prevUseProcesses = useProcesses; 119 | 120 | // Add content to the docked window 121 | var dockedWindowFlags = ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoMove; 122 | if (ImGui.Begin("MemHack", dockedWindowFlags)) 123 | { 124 | if(ImGui.Checkbox("List all processes", ref useProcesses)) 125 | { 126 | if(useProcesses != prevUseProcesses) 127 | { 128 | processList = useProcesses ? MemHack.GetAllProcesses() : MemHack.GetAllWindows(); 129 | selectedWindowIndex = 0; 130 | } 131 | } 132 | 133 | if (ImGui.BeginCombo("Select Process", processList.Count == 0 ? "No Process Found" : processList[selectedWindowIndex].title)) 134 | { 135 | for (int i = 0; i < processList.Count; i++) 136 | { 137 | bool isSelected = (selectedWindowIndex == i); 138 | if (ImGui.Selectable(processList[i].title, isSelected)) 139 | selectedWindowIndex = i; 140 | 141 | // Highlight the selected item 142 | if (isSelected) 143 | ImGui.SetItemDefaultFocus(); 144 | } 145 | 146 | ImGui.EndCombo(); 147 | } 148 | 149 | 150 | ImGui.InputInt("Scan value", ref searchedValue); 151 | 152 | if (ImGui.Button("Refresh Process List")) 153 | { 154 | processList = useProcesses ? MemHack.GetAllProcesses() : MemHack.GetAllWindows(); 155 | selectedWindowIndex = 0; 156 | } 157 | 158 | ImGui.SameLine(); 159 | 160 | if (ImGui.Button("New Scan")) 161 | { 162 | selectedPointerIndex = -1; 163 | foundPointers = MemHack.MemorySearch(processList[selectedWindowIndex].processId, searchedValue); 164 | } 165 | 166 | ImGui.SameLine(); 167 | 168 | if (foundPointers.Count == 0) 169 | ImGui.BeginDisabled(); // Disable "Next Scan" button until the condition is met 170 | 171 | if (ImGui.Button("Next Scan")) 172 | { 173 | selectedPointerIndex = -1; 174 | foundPointers = MemHack.FilterPointers(processList[selectedWindowIndex].processId, foundPointers, searchedValue); 175 | } 176 | 177 | if (foundPointers.Count == 0) 178 | ImGui.EndDisabled(); // End the disabled block 179 | 180 | ImGui.Text($"Found Addresses: {foundPointers.Count}"); 181 | 182 | // Pagination settings 183 | int totalItems = foundPointers.Count; 184 | int totalPages = (int)Math.Ceiling((float)totalItems / itemsPerPage); 185 | 186 | // Calculate the range of items to display based on the current page 187 | int startIndex = currentPage * itemsPerPage; 188 | int endIndex = Math.Min(startIndex + itemsPerPage, totalItems); 189 | 190 | // Begin scrollable area (child window) 191 | ImGui.BeginChild("Scrollable List", new System.Numerics.Vector2(0, 200), ImGuiChildFlags.None, ImGuiWindowFlags.AlwaysVerticalScrollbar); 192 | 193 | // Render the items for the current page 194 | for (int i = startIndex; i < endIndex; i++) 195 | { 196 | bool isSelected = (selectedPointerIndex == i); 197 | if (ImGui.Selectable($"0x{foundPointers[i]:X}", isSelected)) // Use item index or other string representation 198 | selectedPointerIndex = i; // Update selected item index 199 | 200 | if (isSelected) 201 | ImGui.SetItemDefaultFocus(); // Focus the selected item 202 | } 203 | 204 | // End child window 205 | ImGui.EndChild(); 206 | 207 | // Pagination Controls 208 | ImGui.Spacing(); // Adds some space between the list and the pagination buttons 209 | 210 | // Previous Page Button 211 | if (currentPage > 0 && ImGui.Button("Previous Page")) 212 | currentPage--; // Go to the previous page 213 | 214 | if(currentPage > 0) 215 | ImGui.SameLine(); // Place the next button on the same line 216 | 217 | // Next Page Button 218 | if (currentPage < totalPages - 1 && ImGui.Button("Next Page")) 219 | currentPage++; // Go to the next page 220 | 221 | // Display Page Info (e.g., "Page 1 of 10") 222 | if(currentPage < totalPages) 223 | ImGui.SameLine(); 224 | 225 | ImGui.Text($"Page {currentPage + 1} of {totalPages}"); 226 | 227 | ImGui.InputInt("New value", ref newValue); 228 | 229 | if(selectedPointerIndex == -1) 230 | ImGui.BeginDisabled(); // Disable "Write Value" button until the condition is met 231 | 232 | if (ImGui.Button("Write Value")) 233 | writeValueResult = MemHack.WriteAddressValue(processList[selectedWindowIndex].processId, foundPointers[selectedPointerIndex], newValue); 234 | 235 | if(selectedPointerIndex == -1) 236 | ImGui.EndDisabled(); // Disable "Write Value" button until the condition is met 237 | 238 | ImGui.Text(writeValueResult); 239 | 240 | ImGui.End(); 241 | } 242 | 243 | // End the main docking window 244 | ImGui.End(); 245 | 246 | GuiRenderer.EndLayout(); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /MemHackGUI/MemHackGUI.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | WinExe 4 | net8.0 5 | Major 6 | false 7 | false 8 | 9 | 10 | app.manifest 11 | Icon.ico 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Icon.ico 20 | 21 | 22 | Icon.bmp 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /MemHackGUI/Program.cs: -------------------------------------------------------------------------------- 1 | using var game = new MemHackGUI.MemHackGUI(); 2 | game.Run(); 3 | -------------------------------------------------------------------------------- /MemHackGUI/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | true/pm 39 | permonitorv2,permonitor 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /MemHackLib/IMemHack.cs: -------------------------------------------------------------------------------- 1 | using MemHackLib.PlatformImplementations; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace MemHackLib; 10 | public interface IMemHack 11 | { 12 | public const uint BufferSize = 1024; 13 | 14 | public static IMemHack Create() { 15 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 16 | return new MemHackWin(); // Windows-specific implementation 17 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 18 | return new MemHackLin(); // Linux-specific implementation 19 | else 20 | throw new PlatformNotSupportedException("This platform is not supported."); 21 | } 22 | 23 | public Type ValueType { get; set; } 24 | 25 | public List MemorySearch(uint processId, long desiredValue); 26 | public string WriteAddressValue(uint processId, nint targetPointer, long value); 27 | public List FilterPointers(uint processId, List pointers, long newValue); 28 | public List<(string title, uint processId)> GetAllWindows(); 29 | public List<(string title, uint processId)> GetAllProcesses(); 30 | } 31 | -------------------------------------------------------------------------------- /MemHackLib/MemHackLib.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /MemHackLib/PlatformImplementations/MemHackLin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace MemHackLib.PlatformImplementations 10 | { 11 | internal class MemHackLin : IMemHack 12 | { 13 | public Type ValueType { get; set; } = typeof(int); 14 | 15 | #region Linux Specifics 16 | 17 | // Define constants for ptrace commands 18 | private const int PTRACE_ATTACH = 16; 19 | private const int PTRACE_DETACH = 17; 20 | private const int PTRACE_PEEKDATA = 2; 21 | private const int PTRACE_POKEDATA = 5; // PTRACE_POKEDATA to write memory 22 | 23 | // Define process access flags 24 | [Flags] 25 | public enum ProcessAccessFlags 26 | { 27 | PROCESS_VM_READ = 0x0010, 28 | PROCESS_VM_WRITE = 0x0020, 29 | PROCESS_VM_OPERATION = 0x0008, 30 | } 31 | 32 | // P/Invoke for ptrace and other necessary Linux system calls 33 | [DllImport("libc.so.6", SetLastError = true)] 34 | private static extern int ptrace(int request, int pid, nint addr, nint data); 35 | 36 | [DllImport("libc.so.6", SetLastError = true)] 37 | private static extern int waitpid(int pid, out int status, int options); 38 | 39 | [DllImport("libc.so.6", SetLastError = true)] 40 | private static extern int open(string path, int flags); 41 | 42 | [DllImport("libc.so.6", SetLastError = true)] 43 | private static extern int close(int fd); 44 | 45 | [DllImport("libc.so.6", SetLastError = true)] 46 | private static extern int read(int fd, byte[] buffer, int size); 47 | 48 | [DllImport("libc.so.6", SetLastError = true)] 49 | private static extern int write(int fd, byte[] buffer, int size); 50 | 51 | // P/Invoke declarations for X11 functions 52 | [DllImport("libX11.so.6", EntryPoint = "XOpenDisplay")] 53 | private static extern nint XOpenDisplay(string display); 54 | 55 | [DllImport("libX11.so.6", EntryPoint = "XCloseDisplay")] 56 | private static extern int XCloseDisplay(nint display); 57 | 58 | [DllImport("libX11.so.6", EntryPoint = "XRootWindow")] 59 | private static extern nint XRootWindow(nint display, int screen); 60 | 61 | [DllImport("libX11.so.6", EntryPoint = "XQueryTree")] 62 | private static extern int XQueryTree(nint display, nint window, out nint root, out nint parent, out nint children, out uint nchildren); 63 | 64 | [DllImport("libX11.so.6", EntryPoint = "XFetchName")] 65 | private static extern int XFetchName(nint display, nint window, StringBuilder windowName); 66 | 67 | [DllImport("libX11.so.6", EntryPoint = "XGetWindowAttributes")] 68 | private static extern int XGetWindowAttributes(nint display, nint window, out XWindowAttributes attributes); 69 | 70 | // Define the X11 Window Attributes struct 71 | [StructLayout(LayoutKind.Sequential)] 72 | public struct XWindowAttributes 73 | { 74 | public int x, y; 75 | public int width, height; 76 | public int border_width; 77 | public int depth; 78 | public nint visual; 79 | public nint colormap; 80 | public int map_state; 81 | public int all_event_masks; 82 | public int your_event_mask; 83 | public nint do_not_propagate_mask; 84 | public nint override_redirect; 85 | } 86 | 87 | [DllImport("libX11.so.6", EntryPoint = "XGetWindowProperty")] 88 | private static extern int XGetWindowProperty(nint display, nint window, Atom property, long offset, long length, bool delete, Atom reqType, out Atom actualType, out int actualFormat, out long nItems, out long bytesAfter, out nint prop); 89 | 90 | // Define the Atom types for the properties 91 | private static readonly Atom WM_PID = new(34); // WM_PID atom (commonly used for process ID in X11) 92 | private static readonly Atom WM_CLIENT_MACHINE = new(31); // WM_CLIENT_MACHINE atom (for client machine name) 93 | 94 | // Struct to define Atom type for X11 95 | [StructLayout(LayoutKind.Sequential)] 96 | public struct Atom 97 | { 98 | public long atom; 99 | public Atom(long atom) => this.atom = atom; 100 | } 101 | 102 | #endregion 103 | 104 | // Example memory reading function using ptrace 105 | private static long ReadMemory(int pid, nint address) 106 | { 107 | int value = ptrace(PTRACE_PEEKDATA, pid, address, 0); 108 | 109 | if (value == -1) 110 | throw new InvalidOperationException("Error reading memory."); 111 | 112 | return value; 113 | } 114 | 115 | // Linux-specific FilterPointers implementation 116 | public List FilterPointers(uint processId, List pointers, long newValue) 117 | { 118 | ConcurrentBag filteredPointers = []; 119 | 120 | // Attach to the target process 121 | int pid = (int)processId; 122 | 123 | ptrace(PTRACE_ATTACH, pid, 0, 0); 124 | waitpid(pid, out int status, 0); // Wait for the process to stop 125 | 126 | Parallel.ForEach(pointers, pointer => 127 | { 128 | try 129 | { 130 | long readValue = ReadMemory(pid, pointer); 131 | if (readValue == newValue) 132 | { 133 | filteredPointers.Add(pointer); // Add valid pointer to thread-safe collection 134 | } 135 | } 136 | catch (Exception) 137 | { 138 | // Handle errors reading memory, such as invalid addresses 139 | } 140 | }); 141 | 142 | // Detach from the target process after the operation is complete 143 | ptrace(PTRACE_DETACH, pid, 0, 0); 144 | 145 | return filteredPointers.Distinct().ToList(); // Remove duplicates and convert to list 146 | } 147 | 148 | // Get all processes by reading the /proc filesystem 149 | public List<(string title, uint processId)> GetAllProcesses() 150 | { 151 | List<(string title, uint processId)> processes = []; 152 | 153 | // Get all directories in /proc that are numeric (representing process IDs) 154 | var processDirs = Directory.GetDirectories("/proc") 155 | .Where(dir => uint.TryParse(new DirectoryInfo(dir).Name, out _)); // Only directories with numeric names 156 | 157 | foreach (var dir in processDirs) 158 | { 159 | try 160 | { 161 | string pid = new DirectoryInfo(dir).Name; 162 | string statusFile = Path.Combine(dir, "status"); 163 | 164 | if (File.Exists(statusFile)) 165 | { 166 | // Read the status file to get the process name 167 | var lines = File.ReadAllLines(statusFile); 168 | string processName = lines 169 | .FirstOrDefault(line => line.StartsWith("Name:")) 170 | ?.Split([':'], 2)[1] 171 | .Trim() ?? string.Empty; 172 | 173 | if (!string.IsNullOrEmpty(processName)) 174 | processes.Add((processName, uint.Parse(pid))); 175 | } 176 | } 177 | catch 178 | { 179 | // Ignore any errors (e.g., access permissions, process disappearing) 180 | } 181 | } 182 | 183 | return processes; 184 | } 185 | 186 | // Get all windows and their titles using X11 187 | public List<(string title, uint processId)> GetAllWindows() 188 | { 189 | List<(string title, uint processId)> windows = []; 190 | nint display = XOpenDisplay(null); 191 | 192 | if (display == nint.Zero) 193 | throw new InvalidOperationException("Unable to open X display."); 194 | 195 | nint rootWindow = XRootWindow(display, 0); 196 | 197 | XQueryTree(display, rootWindow, out rootWindow, out nint parentWindow, out nint childrenWindow, out uint numChildren); 198 | 199 | nint[] childWindows = new nint[numChildren]; 200 | Marshal.Copy(childrenWindow, childWindows, 0, (int)numChildren); 201 | 202 | foreach (var window in childWindows) 203 | { 204 | try 205 | { 206 | StringBuilder windowTitle = new(256); 207 | if (XFetchName(display, window, windowTitle) > 0) 208 | { 209 | string title = windowTitle.ToString(); 210 | if (!string.IsNullOrWhiteSpace(title)) 211 | { 212 | // Check if the window is visible (simplified logic) 213 | XGetWindowAttributes(display, window, out XWindowAttributes attributes); 214 | if (attributes.map_state == 1) // If window is mapped (visible) 215 | { 216 | uint processId = GetProcessIdFromWindow(display, window); // Simplified for demo, requires additional work 217 | windows.Add((title, processId)); 218 | } 219 | } 220 | } 221 | } 222 | catch (Exception) 223 | { 224 | // Ignore errors 225 | } 226 | } 227 | 228 | XCloseDisplay(display); 229 | return windows; 230 | } 231 | 232 | // Get the process ID associated with a window using X11 and `/proc` 233 | private uint GetProcessIdFromWindow(nint display, nint window) 234 | { 235 | try 236 | { 237 | // Query the window properties to check for the PID 238 | int result = XGetWindowProperty(display, window, WM_PID, 0, 0, false, WM_PID, out Atom actualType, out int actualFormat, out long nItems, out long bytesAfter, out nint prop); 239 | 240 | if (result == 0 && prop != nint.Zero) 241 | { 242 | // If we successfully fetched a property, try to interpret it as a PID 243 | uint pid = (uint)Marshal.ReadInt32(prop); 244 | return pid; 245 | } 246 | 247 | // Fallback: Try matching the window with the process using /proc/{pid}/fd/ 248 | string[] files = Directory.GetFiles("/proc"); 249 | foreach (var file in files) 250 | { 251 | string pidDir = Path.GetFileName(file); 252 | if (uint.TryParse(pidDir, out uint pid)) 253 | { 254 | string[] fdFiles = Directory.GetFiles($"/proc/{pid}/fd/"); 255 | foreach (var fd in fdFiles) 256 | { 257 | try 258 | { 259 | // Read the symbolic link at the file descriptor path 260 | var fileInfo = new FileInfo(fd); 261 | var linkTarget = fileInfo.LinkTarget; 262 | if (linkTarget != null && linkTarget.Contains($"window-{window:X}", StringComparison.InvariantCultureIgnoreCase)) 263 | { 264 | return pid; // Found a matching process for the window 265 | } 266 | } 267 | catch 268 | { 269 | // Ignore errors for invalid file descriptors 270 | } 271 | } 272 | } 273 | } 274 | } 275 | catch 276 | { 277 | // Ignore any exceptions and return a placeholder PID 278 | } 279 | 280 | return 0; // Return 0 if the PID can't be found 281 | } 282 | 283 | // A simple read memory function using /proc/{pid}/mem 284 | private bool ReadProcessMemory(int processId, nint address, byte[] buffer, int size, out int bytesRead) 285 | { 286 | bytesRead = 0; 287 | try 288 | { 289 | string procMemPath = $"/proc/{processId}/mem"; 290 | using FileStream fs = new(procMemPath, FileMode.Open, FileAccess.Read); 291 | 292 | fs.Seek(address, SeekOrigin.Begin); 293 | bytesRead = fs.Read(buffer, 0, size); 294 | return bytesRead > 0; 295 | } 296 | catch 297 | { 298 | return false; 299 | } 300 | } 301 | 302 | // OpenProcess using ptrace 303 | private nint OpenProcess(int processId) 304 | { 305 | // Attach to the target process using ptrace (PTRACE_ATTACH) 306 | int result = ptrace(PTRACE_ATTACH, processId, nint.Zero, nint.Zero); 307 | 308 | // If ptrace fails, return an invalid handle or throw an exception 309 | if (result == -1) 310 | throw new InvalidOperationException($"Failed to attach to process {processId}. Error: {Marshal.GetLastWin32Error()}"); 311 | 312 | // Allow time for the process to stop (optional: use sleep to wait for process stop) 313 | Thread.Sleep(100); 314 | 315 | // Return a handle to the process (can return the processId or other identifier) 316 | return processId; 317 | } 318 | 319 | // Close the process handle using ptrace 320 | private void CloseProcess(int processId) 321 | { 322 | // Detach from the target process using ptrace (PTRACE_DETACH) 323 | int result = ptrace(PTRACE_DETACH, processId, nint.Zero, nint.Zero); 324 | 325 | // If ptrace fails, throw an exception 326 | if (result == -1) 327 | throw new InvalidOperationException($"Failed to detach from process {processId}. Error: {Marshal.GetLastWin32Error()}"); 328 | } 329 | 330 | public List MemorySearch(uint processId, long desiredValue) 331 | { 332 | ConcurrentBag result = []; 333 | int valueSize = Marshal.SizeOf(ValueType); 334 | 335 | // Open process memory (use ptrace or /proc/{pid}/mem) 336 | nint handle = OpenProcess((int)processId); 337 | 338 | // Check if the process handle is valid (replace with your own logic) 339 | if (handle == nint.Zero) 340 | return [.. result]; 341 | 342 | string mapsFilePath = $"/proc/{processId}/maps"; 343 | var memoryRegions = File.ReadLines(mapsFilePath) 344 | .Where(line => line.Contains("rw")) 345 | .Select(line => line.Split(' ')[0]) 346 | .ToList(); 347 | 348 | // Iterate through memory regions 349 | foreach (var region in memoryRegions) 350 | { 351 | var parts = region.Split('-'); 352 | nint startAddress = (nint)Convert.ToInt64(parts[0], 16); 353 | nint endAddress = (nint)Convert.ToInt64(parts[1], 16); 354 | long regionSize = endAddress - startAddress; 355 | 356 | // Process memory region in chunks 357 | Parallel.For(0, (int)Math.Ceiling((double)regionSize / IMemHack.BufferSize), chunk => 358 | { 359 | long offset = chunk * IMemHack.BufferSize; 360 | if (offset >= regionSize) 361 | return; 362 | 363 | nint currentAddress = nint.Add(startAddress, (int)offset); 364 | byte[] buffer = new byte[IMemHack.BufferSize]; 365 | 366 | if (ReadProcessMemory((int)processId, currentAddress, buffer, (int)IMemHack.BufferSize, out int bytesRead) && bytesRead > 0) 367 | { 368 | byte[] desiredBytes = BitConverter.GetBytes(desiredValue); 369 | 370 | for (int j = 0; j <= bytesRead - valueSize; j++) 371 | { 372 | bool match = true; 373 | for (int k = 0; k < valueSize; k++) 374 | { 375 | if (buffer[j + k] != desiredBytes[k]) 376 | { 377 | match = false; 378 | break; 379 | } 380 | } 381 | 382 | if (match) 383 | { 384 | nint pointer = nint.Add(currentAddress, j); 385 | result.Add(pointer); // Add to thread-safe collection 386 | } 387 | } 388 | } 389 | }); 390 | } 391 | 392 | // Close the process handle 393 | CloseProcess((int)processId); 394 | 395 | return result.Distinct().ToList(); // Return unique results 396 | } 397 | 398 | // Write value to process memory 399 | public string WriteAddressValue(uint processId, nint targetPointer, long value) 400 | { 401 | // Open the process memory using ptrace 402 | nint handle = OpenProcess((int)processId); 403 | 404 | if (handle == nint.Zero) 405 | return $"Failed to open process {processId}. Error: {Marshal.GetLastWin32Error()}"; 406 | 407 | // Use ptrace PTRACE_POKEDATA to write the memory value 408 | nint addr = new(targetPointer); 409 | nint data; 410 | 411 | if(ValueType == typeof(int)) 412 | data = new((int)value); 413 | else if (ValueType == typeof(long)) 414 | data = new((long)value); 415 | else if (ValueType == typeof(short)) 416 | data = new((short)value); 417 | else if (ValueType == typeof(byte)) 418 | data = new((byte)value); 419 | else 420 | return "Unsupported value type."; 421 | 422 | int result = ptrace(PTRACE_POKEDATA, (int)processId, addr, data); 423 | 424 | if (result == -1) 425 | return $"Failed to write memory at 0x{targetPointer:X}. Error code: {Marshal.GetLastWin32Error()}"; 426 | 427 | // Detach from the process once writing is done 428 | CloseProcess((int)processId); 429 | 430 | return $"Successfully wrote value {value} to address 0x{targetPointer:X}."; 431 | } 432 | } 433 | } 434 | -------------------------------------------------------------------------------- /MemHackLib/PlatformImplementations/MemHackWin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Runtime.InteropServices; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace MemHackLib.PlatformImplementations; 11 | 12 | public class MemHackWin : IMemHack 13 | { 14 | public Type ValueType { get; set; } = typeof(int); 15 | 16 | #region WINAPI 17 | // Import Windows API functions 18 | 19 | // Delegate for EnumWindows callback 20 | public delegate bool EnumWindowsProc(nint hWnd, nint lParam); 21 | 22 | // P/Invoke for EnumWindows 23 | [DllImport("user32.dll", SetLastError = true)] 24 | public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, nint lParam); 25 | 26 | // P/Invoke for GetWindowText 27 | [DllImport("user32.dll", SetLastError = true)] 28 | public static extern int GetWindowText(nint hWnd, StringBuilder lpString, int nMaxCount); 29 | 30 | // P/Invoke for GetWindowThreadProcessId 31 | [DllImport("user32.dll", SetLastError = true)] 32 | public static extern uint GetWindowThreadProcessId(nint hWnd, out uint lpdwProcessId); 33 | 34 | [DllImport("user32.dll")] 35 | public static extern bool IsWindowVisible(nint hWnd); 36 | 37 | [DllImport("kernel32.dll")] 38 | public static extern nint OpenProcess(long dwDesiredAccess, bool bInheritHandle, uint dwProcessId); 39 | 40 | [DllImport("kernel32.dll", SetLastError = true)] 41 | static extern bool ReadProcessMemory(nint hProcess, nint lpBaseAddress, byte[] lpBuffer, uint nSize, out nint lpNumberOfBytesRead); 42 | 43 | [DllImport("kernel32.dll", SetLastError = true)] 44 | static extern bool VirtualQueryEx(nint hProcess, nint lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, uint dwLength); 45 | 46 | [DllImport("kernel32.dll", SetLastError = true)] 47 | static extern bool WriteProcessMemory(nint hProcess, nint lpBaseAddress, byte[] lpBuffer, uint nSize, out nint lpNumberOfBytesWritten); 48 | 49 | [DllImport("kernel32.dll", SetLastError = true)] 50 | public static extern bool VirtualProtectEx( 51 | nint hProcess, // Handle to the process 52 | nint lpAddress, // The base address of the memory region 53 | uint dwSize, // The size of the region 54 | uint flNewProtect, // The new protection flags 55 | out uint lpflOldProtect // The old protection flags (output) 56 | ); 57 | 58 | [StructLayout(LayoutKind.Sequential)] 59 | struct MEMORY_BASIC_INFORMATION 60 | { 61 | public nint BaseAddress; 62 | public nint AllocationBase; 63 | public uint AllocationProtect; 64 | public nint RegionSize; 65 | public uint State; 66 | public uint Protect; 67 | public uint Type; 68 | } 69 | 70 | [Flags] 71 | public enum MemoryProtection : uint 72 | { 73 | PAGE_READWRITE = 0x04, 74 | PAGE_EXECUTE_READWRITE = 0x40, 75 | PAGE_READONLY = 0x02 76 | } 77 | 78 | [Flags] 79 | public enum ProcessAccessFlags : long 80 | { 81 | PROCESS_VM_OPERATION = 0x0008, 82 | PROCESS_VM_READ = 0x0010, 83 | PROCESS_VM_WRITE = 0x0020, 84 | PROCESS_QUERY_INFORMATION = 0x0400, 85 | PROCESS_ALL_ACCESS = 0x000F0000L | 0x00100000L | 0xFFF 86 | } 87 | 88 | #endregion 89 | 90 | public List FilterPointers(uint processId, List pointers, long newValue) 91 | { 92 | ConcurrentBag filteredPointers = []; 93 | 94 | nint handle = OpenProcess((int)(ProcessAccessFlags.PROCESS_VM_OPERATION | ProcessAccessFlags.PROCESS_VM_WRITE | ProcessAccessFlags.PROCESS_VM_READ), false, processId); 95 | 96 | Parallel.ForEach(pointers, pointer => 97 | { 98 | byte[] buffer = new byte[Marshal.SizeOf(ValueType)]; 99 | 100 | if (ReadProcessMemory(handle, pointer, buffer, (uint)buffer.Length, out nint bytesRead) && bytesRead > 0) 101 | { 102 | long readValue = Utils.BufferConvert(buffer, 0, ValueType); 103 | if (readValue == newValue) 104 | filteredPointers.Add(pointer); // Add valid pointer to thread-safe collection 105 | } 106 | }); 107 | 108 | return filteredPointers.Distinct().ToList(); // Remove duplicates and convert to list 109 | } 110 | 111 | public List<(string title, uint processId)> GetAllProcesses() 112 | { 113 | List<(string title, uint processId)> processes = []; 114 | 115 | foreach (var process in Process.GetProcesses()) 116 | { 117 | try 118 | { 119 | processes.Add((process.ProcessName, (uint)process.Id)); 120 | } 121 | catch 122 | { 123 | // Ignore any processes that might no longer exist 124 | } 125 | } 126 | 127 | return processes; 128 | } 129 | 130 | public List<(string title, uint processId)> GetAllWindows() 131 | { 132 | List<(string title, uint processId)> windows = []; 133 | 134 | EnumWindows((hWnd, lParam) => 135 | { 136 | StringBuilder sb = new StringBuilder(256); 137 | GetWindowText(hWnd, sb, sb.Capacity); 138 | string title = sb.ToString(); 139 | 140 | if (!string.IsNullOrWhiteSpace(title) && IsWindowVisible(hWnd)) 141 | { 142 | GetWindowThreadProcessId(hWnd, out uint processId); 143 | windows.Add((title, processId)); 144 | } 145 | 146 | return true; // Continue enumeration 147 | }, nint.Zero); 148 | 149 | return windows; 150 | } 151 | 152 | public List MemorySearch(uint processId, long desiredValue) 153 | { 154 | ConcurrentBag result = []; 155 | int valueSize = Marshal.SizeOf(ValueType); 156 | 157 | nint address = nint.Zero; 158 | nint handle = OpenProcess((int)(ProcessAccessFlags.PROCESS_VM_OPERATION | ProcessAccessFlags.PROCESS_VM_WRITE | ProcessAccessFlags.PROCESS_VM_READ), false, processId); 159 | 160 | MEMORY_BASIC_INFORMATION memInfo = new(); 161 | uint memInfoSize = (uint)Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION)); 162 | 163 | while (true) 164 | { 165 | if (!VirtualQueryEx(handle, address, out memInfo, memInfoSize)) 166 | break; 167 | 168 | // Skip uncommitted memory regions 169 | if (memInfo.State == 0x10000 || memInfo.State == 0x2000) 170 | { 171 | address = (nint)(memInfo.BaseAddress + memInfo.RegionSize.ToInt64()); 172 | continue; 173 | } 174 | 175 | // Check if the memory region is readable and writable 176 | if ((memInfo.Protect & (uint)(MemoryProtection.PAGE_READWRITE | MemoryProtection.PAGE_EXECUTE_READWRITE | MemoryProtection.PAGE_READONLY)) != 0) 177 | { 178 | nint regionBaseAddress = memInfo.BaseAddress; 179 | long regionSize = memInfo.RegionSize.ToInt64(); 180 | 181 | // Process memory region in chunks 182 | Parallel.For(0, (int)Math.Ceiling((double)regionSize / IMemHack.BufferSize), chunk => 183 | { 184 | long offset = chunk * IMemHack.BufferSize; 185 | if (offset >= regionSize) 186 | return; 187 | 188 | nint currentAddress = nint.Add(regionBaseAddress, (int)offset); 189 | byte[] buffer = new byte[IMemHack.BufferSize]; 190 | 191 | if (ReadProcessMemory(handle, currentAddress, buffer, IMemHack.BufferSize, out nint bytesRead) && bytesRead > 0) 192 | { 193 | byte[] desiredBytes = BitConverter.GetBytes(desiredValue); 194 | 195 | for (int j = 0; j <= bytesRead - valueSize; j++) 196 | { 197 | bool match = true; 198 | for (int k = 0; k < valueSize; k++) 199 | if (buffer[j + k] != desiredBytes[k]) 200 | { 201 | match = false; 202 | break; 203 | } 204 | 205 | if (match) 206 | { 207 | nint pointer = nint.Add(currentAddress, j); 208 | result.Add(pointer); // Add to thread-safe collection 209 | } 210 | } 211 | } 212 | }); 213 | } 214 | 215 | address = (nint)(memInfo.BaseAddress + memInfo.RegionSize.ToInt64()); 216 | } 217 | 218 | return [.. result.Distinct()]; 219 | } 220 | 221 | public string WriteAddressValue(uint processId, nint targetPointer, long value) 222 | { 223 | byte[] newValueBuffer = BitConverter.GetBytes(value); 224 | 225 | nint handle = OpenProcess((long)(ProcessAccessFlags.PROCESS_VM_READ | ProcessAccessFlags.PROCESS_VM_WRITE | ProcessAccessFlags.PROCESS_QUERY_INFORMATION | ProcessAccessFlags.PROCESS_VM_OPERATION), false, processId); 226 | 227 | if (handle == nint.Zero) 228 | return $"Failed to open process {processId}. Error: {Marshal.GetLastWin32Error()}"; 229 | 230 | if (VirtualQueryEx(handle, targetPointer, out MEMORY_BASIC_INFORMATION memInfo, (uint)Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION)))) 231 | { 232 | Console.WriteLine($"Base Address: 0x{memInfo.BaseAddress:X}"); 233 | Console.WriteLine($"Region Size: 0x{memInfo.RegionSize:X}"); 234 | Console.WriteLine($"Protection: 0x{memInfo.Protect:X}"); 235 | Console.WriteLine($"State: 0x{memInfo.State:X}"); 236 | 237 | if (memInfo.State != 0x1000) 238 | return $"Address 0x{targetPointer:X} is not in a committed state."; 239 | 240 | if ((memInfo.Protect & (uint)(MemoryProtection.PAGE_READWRITE | MemoryProtection.PAGE_EXECUTE_READWRITE)) == 0) 241 | { 242 | Console.WriteLine($"Address 0x{targetPointer:X} does not have write permissions. Attempting to change protection..."); 243 | if (!VirtualProtectEx(handle, targetPointer, (uint)newValueBuffer.Length, (uint)MemoryProtection.PAGE_READWRITE, out uint oldProtect)) 244 | return $"Failed to change protection for address 0x{targetPointer:X}. Error: {Marshal.GetLastWin32Error()}"; 245 | } 246 | 247 | if (WriteProcessMemory(handle, targetPointer, newValueBuffer, (uint)newValueBuffer.Length, out nint bytesWritten) && bytesWritten == newValueBuffer.Length) 248 | return $"Successfully wrote value {value} to address 0x{targetPointer:X}."; 249 | else 250 | return $"Failed to write memory at 0x{targetPointer:X}. Error code: {Marshal.GetLastWin32Error()}"; 251 | } 252 | else 253 | return $"VirtualQueryEx failed for address 0x{targetPointer:X}. Error: {Marshal.GetLastWin32Error()}"; 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /MemHackLib/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MemHackLib; 8 | 9 | public static class Utils 10 | { 11 | public static long BufferConvert(byte[] buffer, int offset, Type type) => type switch 12 | { 13 | var valueType when valueType == typeof(short) => BitConverter.ToInt16(buffer, offset), 14 | var valueType when valueType == typeof(int) => BitConverter.ToInt32(buffer, offset), 15 | var valueType when valueType == typeof(long) => BitConverter.ToInt64(buffer, offset), 16 | _ => BitConverter.ToInt32(buffer, offset), 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /MemHackMe/MemHackMe.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /MemHackMe/Program.cs: -------------------------------------------------------------------------------- 1 |  2 | int prevValue = 0; 3 | int hackMe = 0; 4 | 5 | while(true) 6 | { 7 | Console.Clear(); 8 | Console.WriteLine("___________VALUES___________"); 9 | Console.WriteLine($"Previous value: {prevValue}"); 10 | Console.WriteLine($"HackMe Value: {hackMe}"); 11 | Console.WriteLine("____________MENU____________"); 12 | Console.WriteLine("1. Add 1 to the value"); 13 | Console.WriteLine("2. Do nothing"); 14 | 15 | Console.WriteLine("Enter the option: "); 16 | bool isOption = int.TryParse(Console.ReadLine(), out int option); 17 | if (!isOption) 18 | { 19 | Console.WriteLine("Invalid option"); 20 | continue; 21 | } 22 | 23 | switch (option) 24 | { 25 | case 1: 26 | prevValue = hackMe; 27 | hackMe++; 28 | break; 29 | case 2: 30 | break; 31 | default: 32 | Console.WriteLine("Invalid option"); 33 | continue; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /MonoGame.ImGuiNet/DrawVertDeclaration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Graphics; 2 | using ImGuiNET; 3 | 4 | namespace MonoGame.ImGuiNet; 5 | 6 | public static class DrawVertDeclaration 7 | { 8 | public static readonly VertexDeclaration Declaration; 9 | 10 | public static readonly int Size; 11 | 12 | static DrawVertDeclaration() 13 | { 14 | unsafe { Size = sizeof(ImDrawVert); } 15 | 16 | Declaration = new VertexDeclaration( 17 | Size, 18 | 19 | // Position 20 | new VertexElement(0, VertexElementFormat.Vector2, VertexElementUsage.Position, 0), 21 | 22 | // UV 23 | new VertexElement(8, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), 24 | 25 | // Color 26 | new VertexElement(16, VertexElementFormat.Color, VertexElementUsage.Color, 0) 27 | ); 28 | } 29 | } -------------------------------------------------------------------------------- /MonoGame.ImGuiNet/ImGuiRenderer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Microsoft.Xna.Framework.Input; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Runtime.InteropServices; 7 | using ImGuiNET; 8 | using System.Threading; 9 | using System.Reflection; 10 | 11 | namespace MonoGame.ImGuiNet; 12 | 13 | public class ImGuiRenderer 14 | { 15 | private Game _game; 16 | 17 | // Graphics 18 | private GraphicsDevice _graphicsDevice; 19 | 20 | private BasicEffect _effect; 21 | private RasterizerState _rasterizerState; 22 | 23 | private byte[] _vertexData; 24 | private VertexBuffer _vertexBuffer; 25 | private int _vertexBufferSize; 26 | 27 | private byte[] _indexData; 28 | private IndexBuffer _indexBuffer; 29 | private int _indexBufferSize; 30 | 31 | // Textures 32 | private Dictionary _loadedTextures; 33 | 34 | private int _textureId; 35 | private IntPtr? _fontTextureId; 36 | 37 | // Input 38 | private int _scrollWheelValue; 39 | private int _horizontalScrollWheelValue; 40 | private readonly float WHEEL_DELTA = 120; 41 | private Keys[] _allKeys = Enum.GetValues(); 42 | 43 | public ImGuiRenderer(Game game) 44 | { 45 | var context = ImGui.CreateContext(); 46 | ImGui.SetCurrentContext(context); 47 | 48 | _game = game ?? throw new ArgumentNullException(nameof(game)); 49 | _graphicsDevice = game.GraphicsDevice; 50 | 51 | _loadedTextures = []; 52 | 53 | _rasterizerState = new RasterizerState() 54 | { 55 | CullMode = CullMode.None, 56 | DepthBias = 0, 57 | FillMode = FillMode.Solid, 58 | MultiSampleAntiAlias = false, 59 | ScissorTestEnable = true, 60 | SlopeScaleDepthBias = 0 61 | }; 62 | 63 | SetupInput(); 64 | } 65 | 66 | #region ImGuiRenderer 67 | 68 | /// 69 | /// Creates a texture and loads the font data from ImGui. Should be called when the is initialized but before any rendering is done 70 | /// 71 | public virtual unsafe void RebuildFontAtlas() 72 | { 73 | // Get font texture from ImGui 74 | var io = ImGui.GetIO(); 75 | io.Fonts.GetTexDataAsRGBA32(out byte* pixelData, out int width, out int height, out int bytesPerPixel); 76 | 77 | // Copy the data to a managed array 78 | var pixels = new byte[width * height * bytesPerPixel]; 79 | unsafe { Marshal.Copy(new IntPtr(pixelData), pixels, 0, pixels.Length); } 80 | 81 | // Create and register the texture as an XNA texture 82 | var tex2d = new Texture2D(_graphicsDevice, width, height, false, SurfaceFormat.Color); 83 | tex2d.SetData(pixels); 84 | 85 | // Should a texture already have been build previously, unbind it first so it can be deallocated 86 | if (_fontTextureId.HasValue) UnbindTexture(_fontTextureId.Value); 87 | 88 | // Bind the new texture to an ImGui-friendly id 89 | _fontTextureId = BindTexture(tex2d); 90 | 91 | // Let ImGui know where to find the texture 92 | io.Fonts.SetTexID(_fontTextureId.Value); 93 | io.Fonts.ClearTexData(); // Clears CPU side texture data 94 | } 95 | 96 | /// 97 | /// Creates a pointer to a texture, which can be passed through ImGui calls such as . That pointer is then used by ImGui to let us know what texture to draw 98 | /// 99 | public virtual IntPtr BindTexture(Texture2D texture) 100 | { 101 | var id = new IntPtr(_textureId++); 102 | 103 | _loadedTextures.Add(id, texture); 104 | 105 | return id; 106 | } 107 | 108 | /// 109 | /// Removes a previously created texture pointer, releasing its reference and allowing it to be deallocated 110 | /// 111 | public virtual void UnbindTexture(IntPtr textureId) 112 | { 113 | _loadedTextures.Remove(textureId); 114 | } 115 | 116 | /// 117 | /// Sets up ImGui for a new frame, should be called at frame start 118 | /// 119 | public virtual void BeginLayout(GameTime gameTime) 120 | { 121 | ImGui.GetIO().DeltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds; 122 | 123 | UpdateInput(); 124 | 125 | ImGui.NewFrame(); 126 | } 127 | 128 | /// 129 | /// Asks ImGui for the generated geometry data and sends it to the graphics pipeline, should be called after the UI is drawn using ImGui.** calls 130 | /// 131 | public virtual void EndLayout() 132 | { 133 | ImGui.Render(); 134 | 135 | unsafe { RenderDrawData(ImGui.GetDrawData()); } 136 | } 137 | 138 | #endregion ImGuiRenderer 139 | 140 | #region Setup & Update 141 | 142 | /// 143 | /// Setup key input event handler. 144 | /// 145 | protected virtual void SetupInput() 146 | { 147 | var io = ImGui.GetIO(); 148 | 149 | // MonoGame-specific ////////////////////// 150 | _game.Window.TextInput += (s, a) => 151 | { 152 | if (a.Character == '\t') return; 153 | io.AddInputCharacter(a.Character); 154 | }; 155 | 156 | /////////////////////////////////////////// 157 | 158 | // FNA-specific /////////////////////////// 159 | //TextInputEXT.TextInput += c => 160 | //{ 161 | // if (c == '\t') return; 162 | 163 | // ImGui.GetIO().AddInputCharacter(c); 164 | //}; 165 | /////////////////////////////////////////// 166 | } 167 | 168 | /// 169 | /// Updates the to the current matrices and texture 170 | /// 171 | protected virtual Effect UpdateEffect(Texture2D texture) 172 | { 173 | _effect = _effect ?? new BasicEffect(_graphicsDevice); 174 | 175 | var io = ImGui.GetIO(); 176 | 177 | _effect.World = Matrix.Identity; 178 | _effect.View = Matrix.Identity; 179 | _effect.Projection = Matrix.CreateOrthographicOffCenter(0f, io.DisplaySize.X, io.DisplaySize.Y, 0f, -1f, 1f); 180 | _effect.TextureEnabled = true; 181 | _effect.Texture = texture; 182 | _effect.VertexColorEnabled = true; 183 | 184 | return _effect; 185 | } 186 | 187 | /// 188 | /// Sends XNA input state to ImGui 189 | /// 190 | protected virtual void UpdateInput() 191 | { 192 | if (!_game.IsActive) return; 193 | 194 | var io = ImGui.GetIO(); 195 | 196 | var mouse = Mouse.GetState(); 197 | var keyboard = Keyboard.GetState(); 198 | 199 | io.AddMousePosEvent(mouse.X, mouse.Y); 200 | io.AddMouseButtonEvent(0, mouse.LeftButton == ButtonState.Pressed); 201 | io.AddMouseButtonEvent(1, mouse.RightButton == ButtonState.Pressed); 202 | io.AddMouseButtonEvent(2, mouse.MiddleButton == ButtonState.Pressed); 203 | io.AddMouseButtonEvent(3, mouse.XButton1 == ButtonState.Pressed); 204 | io.AddMouseButtonEvent(4, mouse.XButton2 == ButtonState.Pressed); 205 | 206 | io.AddMouseWheelEvent( 207 | (mouse.HorizontalScrollWheelValue - _horizontalScrollWheelValue) / WHEEL_DELTA, 208 | (mouse.ScrollWheelValue - _scrollWheelValue) / WHEEL_DELTA); 209 | _scrollWheelValue = mouse.ScrollWheelValue; 210 | _horizontalScrollWheelValue = mouse.HorizontalScrollWheelValue; 211 | 212 | foreach (var key in _allKeys) 213 | { 214 | if (TryMapKeys(key, out ImGuiKey imguikey)) 215 | { 216 | io.AddKeyEvent(imguikey, keyboard.IsKeyDown(key)); 217 | } 218 | } 219 | 220 | io.DisplaySize = new System.Numerics.Vector2(_graphicsDevice.PresentationParameters.BackBufferWidth, _graphicsDevice.PresentationParameters.BackBufferHeight); 221 | io.DisplayFramebufferScale = new System.Numerics.Vector2(1f, 1f); 222 | } 223 | 224 | private bool TryMapKeys(Keys key, out ImGuiKey imguikey) 225 | { 226 | //Special case not handed in the switch... 227 | //If the actual key we put in is "None", return none and true. 228 | //otherwise, return none and false. 229 | if (key == Keys.None) 230 | { 231 | imguikey = ImGuiKey.None; 232 | return true; 233 | } 234 | 235 | imguikey = key switch 236 | { 237 | Keys.Back => ImGuiKey.Backspace, 238 | Keys.Tab => ImGuiKey.Tab, 239 | Keys.Enter => ImGuiKey.Enter, 240 | Keys.CapsLock => ImGuiKey.CapsLock, 241 | Keys.Escape => ImGuiKey.Escape, 242 | Keys.Space => ImGuiKey.Space, 243 | Keys.PageUp => ImGuiKey.PageUp, 244 | Keys.PageDown => ImGuiKey.PageDown, 245 | Keys.End => ImGuiKey.End, 246 | Keys.Home => ImGuiKey.Home, 247 | Keys.Left => ImGuiKey.LeftArrow, 248 | Keys.Right => ImGuiKey.RightArrow, 249 | Keys.Up => ImGuiKey.UpArrow, 250 | Keys.Down => ImGuiKey.DownArrow, 251 | Keys.PrintScreen => ImGuiKey.PrintScreen, 252 | Keys.Insert => ImGuiKey.Insert, 253 | Keys.Delete => ImGuiKey.Delete, 254 | >= Keys.D0 and <= Keys.D9 => ImGuiKey._0 + (key - Keys.D0), 255 | >= Keys.A and <= Keys.Z => ImGuiKey.A + (key - Keys.A), 256 | >= Keys.NumPad0 and <= Keys.NumPad9 => ImGuiKey.Keypad0 + (key - Keys.NumPad0), 257 | Keys.Multiply => ImGuiKey.KeypadMultiply, 258 | Keys.Add => ImGuiKey.KeypadAdd, 259 | Keys.Subtract => ImGuiKey.KeypadSubtract, 260 | Keys.Decimal => ImGuiKey.KeypadDecimal, 261 | Keys.Divide => ImGuiKey.KeypadDivide, 262 | >= Keys.F1 and <= Keys.F12 => ImGuiKey.F1 + (key - Keys.F1), 263 | Keys.NumLock => ImGuiKey.NumLock, 264 | Keys.Scroll => ImGuiKey.ScrollLock, 265 | Keys.LeftShift => ImGuiKey.ModShift, 266 | Keys.LeftControl => ImGuiKey.ModCtrl, 267 | Keys.LeftAlt => ImGuiKey.ModAlt, 268 | Keys.OemSemicolon => ImGuiKey.Semicolon, 269 | Keys.OemPlus => ImGuiKey.Equal, 270 | Keys.OemComma => ImGuiKey.Comma, 271 | Keys.OemMinus => ImGuiKey.Minus, 272 | Keys.OemPeriod => ImGuiKey.Period, 273 | Keys.OemQuestion => ImGuiKey.Slash, 274 | Keys.OemTilde => ImGuiKey.GraveAccent, 275 | Keys.OemOpenBrackets => ImGuiKey.LeftBracket, 276 | Keys.OemCloseBrackets => ImGuiKey.RightBracket, 277 | Keys.OemPipe => ImGuiKey.Backslash, 278 | Keys.OemQuotes => ImGuiKey.Apostrophe, 279 | _ => ImGuiKey.None, 280 | }; 281 | 282 | return imguikey != ImGuiKey.None; 283 | } 284 | 285 | #endregion Setup & Update 286 | 287 | #region Internals 288 | 289 | /// 290 | /// Gets the geometry as set up by ImGui and sends it to the graphics device 291 | /// 292 | private void RenderDrawData(ImDrawDataPtr drawData) 293 | { 294 | // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers 295 | var lastViewport = _graphicsDevice.Viewport; 296 | var lastScissorBox = _graphicsDevice.ScissorRectangle; 297 | 298 | _graphicsDevice.BlendFactor = Color.White; 299 | _graphicsDevice.BlendState = BlendState.NonPremultiplied; 300 | _graphicsDevice.RasterizerState = _rasterizerState; 301 | _graphicsDevice.DepthStencilState = DepthStencilState.DepthRead; 302 | 303 | // Handle cases of screen coordinates != from framebuffer coordinates (e.g. retina displays) 304 | drawData.ScaleClipRects(ImGui.GetIO().DisplayFramebufferScale); 305 | 306 | // Setup projection 307 | _graphicsDevice.Viewport = new Viewport(0, 0, _graphicsDevice.PresentationParameters.BackBufferWidth, _graphicsDevice.PresentationParameters.BackBufferHeight); 308 | 309 | UpdateBuffers(drawData); 310 | 311 | RenderCommandLists(drawData); 312 | 313 | // Restore modified state 314 | _graphicsDevice.Viewport = lastViewport; 315 | _graphicsDevice.ScissorRectangle = lastScissorBox; 316 | } 317 | 318 | private unsafe void UpdateBuffers(ImDrawDataPtr drawData) 319 | { 320 | if (drawData.TotalVtxCount == 0) 321 | { 322 | return; 323 | } 324 | 325 | // Expand buffers if we need more room 326 | if (drawData.TotalVtxCount > _vertexBufferSize) 327 | { 328 | _vertexBuffer?.Dispose(); 329 | 330 | _vertexBufferSize = (int)(drawData.TotalVtxCount * 1.5f); 331 | _vertexBuffer = new VertexBuffer(_graphicsDevice, DrawVertDeclaration.Declaration, _vertexBufferSize, BufferUsage.None); 332 | _vertexData = new byte[_vertexBufferSize * DrawVertDeclaration.Size]; 333 | } 334 | 335 | if (drawData.TotalIdxCount > _indexBufferSize) 336 | { 337 | _indexBuffer?.Dispose(); 338 | 339 | _indexBufferSize = (int)(drawData.TotalIdxCount * 1.5f); 340 | _indexBuffer = new IndexBuffer(_graphicsDevice, IndexElementSize.SixteenBits, _indexBufferSize, BufferUsage.None); 341 | _indexData = new byte[_indexBufferSize * sizeof(ushort)]; 342 | } 343 | 344 | // Copy ImGui's vertices and indices to a set of managed byte arrays 345 | int vtxOffset = 0; 346 | int idxOffset = 0; 347 | 348 | for (int n = 0; n < drawData.CmdListsCount; n++) 349 | { 350 | ImDrawListPtr cmdList = drawData.CmdLists[n]; 351 | 352 | fixed (void* vtxDstPtr = &_vertexData[vtxOffset * DrawVertDeclaration.Size]) 353 | fixed (void* idxDstPtr = &_indexData[idxOffset * sizeof(ushort)]) 354 | { 355 | Buffer.MemoryCopy((void*)cmdList.VtxBuffer.Data, vtxDstPtr, _vertexData.Length, cmdList.VtxBuffer.Size * DrawVertDeclaration.Size); 356 | Buffer.MemoryCopy((void*)cmdList.IdxBuffer.Data, idxDstPtr, _indexData.Length, cmdList.IdxBuffer.Size * sizeof(ushort)); 357 | } 358 | 359 | vtxOffset += cmdList.VtxBuffer.Size; 360 | idxOffset += cmdList.IdxBuffer.Size; 361 | } 362 | 363 | // Copy the managed byte arrays to the gpu vertex- and index buffers 364 | _vertexBuffer.SetData(_vertexData, 0, drawData.TotalVtxCount * DrawVertDeclaration.Size); 365 | _indexBuffer.SetData(_indexData, 0, drawData.TotalIdxCount * sizeof(ushort)); 366 | } 367 | 368 | private unsafe void RenderCommandLists(ImDrawDataPtr drawData) 369 | { 370 | _graphicsDevice.SetVertexBuffer(_vertexBuffer); 371 | _graphicsDevice.Indices = _indexBuffer; 372 | 373 | int vtxOffset = 0; 374 | int idxOffset = 0; 375 | 376 | for (int n = 0; n < drawData.CmdListsCount; n++) 377 | { 378 | ImDrawListPtr cmdList = drawData.CmdLists[n]; 379 | 380 | for (int cmdi = 0; cmdi < cmdList.CmdBuffer.Size; cmdi++) 381 | { 382 | ImDrawCmdPtr drawCmd = cmdList.CmdBuffer[cmdi]; 383 | 384 | if (drawCmd.ElemCount == 0) 385 | { 386 | continue; 387 | } 388 | 389 | if (!_loadedTextures.ContainsKey(drawCmd.TextureId)) 390 | { 391 | throw new InvalidOperationException($"Could not find a texture with id '{drawCmd.TextureId}', please check your bindings"); 392 | } 393 | 394 | _graphicsDevice.ScissorRectangle = new Rectangle( 395 | (int)drawCmd.ClipRect.X, 396 | (int)drawCmd.ClipRect.Y, 397 | (int)(drawCmd.ClipRect.Z - drawCmd.ClipRect.X), 398 | (int)(drawCmd.ClipRect.W - drawCmd.ClipRect.Y) 399 | ); 400 | 401 | var effect = UpdateEffect(_loadedTextures[drawCmd.TextureId]); 402 | 403 | foreach (var pass in effect.CurrentTechnique.Passes) 404 | { 405 | pass.Apply(); 406 | 407 | #pragma warning disable CS0618 // // FNA does not expose an alternative method. 408 | _graphicsDevice.DrawIndexedPrimitives( 409 | primitiveType: PrimitiveType.TriangleList, 410 | baseVertex: (int)drawCmd.VtxOffset + vtxOffset, 411 | minVertexIndex: 0, 412 | numVertices: cmdList.VtxBuffer.Size, 413 | startIndex: (int)drawCmd.IdxOffset + idxOffset, 414 | primitiveCount: (int)drawCmd.ElemCount / 3 415 | ); 416 | #pragma warning restore CS0618 417 | } 418 | } 419 | 420 | vtxOffset += cmdList.VtxBuffer.Size; 421 | idxOffset += cmdList.IdxBuffer.Size; 422 | } 423 | } 424 | 425 | #endregion Internals 426 | } 427 | -------------------------------------------------------------------------------- /MonoGame.ImGuiNet/Monogame.ImGuiNet.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Library 4 | net8.0 5 | Major 6 | false 7 | false 8 | 1.1.0 9 | 10 | 11 | 12 | Mezo 13 | Abslute Chaos 14 | Copyright (c) 2023 Mezo 15 | 16 | https://github.com/Mezo-hx/MonoGame.ImGuiNet 17 | git 18 | main 19 | en 20 | 21 | 22 | 23 | MonoGame.ImGuiNet 24 | 25 | NugetpkgIcon.png 26 | 27 | MonoGame;ImGui;import;processes;read;write;gui; 28 | 29 | README.md 30 | 31 | [General fixes] 32 | 33 | 34 | A superset of ImGuiNet with additional components designed for Monogame 35 | 36 | true 37 | MonoGame.ImGuiNet 38 | MIT 39 | True 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /MonoGame.ImGuiNet/Utils/FilePicker.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.InteropServices; 3 | using System.Windows; 4 | 5 | namespace Monogame.ImGuiNet.Utils 6 | { 7 | public class FilePicker 8 | { 9 | public static string DefaultPath_Windows = "C:\\"; //The default path for windows 10 | public static string ImagePath; //The File path from the image 11 | public static void BeginFilePicker() 12 | { 13 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 14 | { 15 | // TODO: Stop being lazy and release the FilePicker as quick as possible 16 | } 17 | 18 | // TODO: Add support for Linux and Mac 19 | } 20 | 21 | public static void BeginFolderPicker() 22 | { 23 | // TODO: Functionality for folder picking 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MemHack 2 | [![Join the chat at https://discord.gg/nU63sFMcnX](https://img.shields.io/discord/1165553796223602708?style=flat-square&logo=discord&logoColor=white&label=Discord&color=%237289DA&link=https%3A%2F%2Fdiscord.gg%2FnU63sFMcnX)](https://discord.gg/nU63sFMcnX) 3 | 4 | MemHack is a Cheat Engine clone written in C#. It provides the basic functionalities of reading and writing to a process memory. This project was made for educational purposes along with a YouTube video that shows the process of making it. 5 | 6 | ## MemHackConsole 7 | 8 | ![alt text](Resources/image.png) 9 | 10 | ## MemHackGUI 11 | 12 | ![alt text](Resources/image-1.png) 13 | 14 | # Features to consider: 15 | Most of these were taken from Youtube comments and Discord suggestions. 16 | 17 | - [x] Linux 18 | - [ ] Flatpack version 19 | - [ ] Memory mapping 20 | - [x] Ability to select a process or a window 21 | - [ ] Pointer scanning 22 | - [ ] Lua scripting support 23 | - [ ] Value type selector 24 | - [ ] Strings support 25 | - [ ] Dll injection 26 | 27 | Other things to consider: 28 | - [x] CI/CD 29 | - [ ] Unit testing 30 | 31 | # Project Structure 32 | 33 | ### **MemHackConsole** 34 | A console version of MemHack, primarily used for debugging purposes. 35 | 36 | ### **MemHackGUI** 37 | The main project, featuring a GUI for a more user-friendly experience with additional features. 38 | 39 | ### **MemHackLib** 40 | A shared library containing the core functionalities of MemHack, used by both `MemHackConsole` and `MemHackGUI`. 41 | 42 | ### **MemHackMe** 43 | A simple console application for testing MemHack. 44 | 45 | ### **Monogame.ImGuiNet** 46 | A fork of the `ImGui.NET` library, used by `MemHackGUI` to create its graphical user interface. 47 | 48 | 49 | ### Credits 50 | Huge inspiration from LLGD: 51 | https://github.com/meemknight/memGrab 52 | https://youtu.be/47xNiBMVSvU 53 | -------------------------------------------------------------------------------- /Resources/image-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vycdev/MemHack/09419a2e484b7e2b29c50e629a5de300c5753be5/Resources/image-1.png -------------------------------------------------------------------------------- /Resources/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vycdev/MemHack/09419a2e484b7e2b29c50e629a5de300c5753be5/Resources/image.png -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | $solutionDir = Get-Location 2 | $buildDir = "$solutionDir\builds" 3 | 4 | # Ensure the builds directory exists 5 | if (!(Test-Path -Path $buildDir)) { 6 | New-Item -ItemType Directory -Path $buildDir | Out-Null 7 | } 8 | 9 | # Get all project files in the solution 10 | $projects = dotnet sln list | Select-Object -Skip 2 11 | 12 | # Define target runtimes 13 | $runtimes = @("win-x64", "linux-x64") 14 | 15 | foreach ($project in $projects) { 16 | $projectName = [System.IO.Path]::GetFileNameWithoutExtension($project) 17 | 18 | foreach ($runtime in $runtimes) { 19 | $outputPath = "$buildDir\$projectName\$runtime" 20 | 21 | Write-Host "Publishing $project for $runtime to $outputPath..." 22 | dotnet publish $project -c Release --self-contained -r $runtime -o $outputPath 23 | } 24 | } 25 | 26 | Write-Host "Build completed!" 27 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get the solution directory 4 | solutionDir=$(pwd) 5 | buildDir="$solutionDir/builds" 6 | 7 | # Ensure the builds directory exists 8 | mkdir -p "$buildDir" 9 | 10 | # Get all project files in the solution 11 | projects=$(dotnet sln list | tail -n +3) # Skip the first two lines 12 | 13 | # Define target runtimes 14 | runtimes=("win-x64" "linux-x64") 15 | 16 | # Iterate over projects 17 | for project in $projects; do 18 | projectName=$(basename "$project" .csproj) 19 | 20 | for runtime in "${runtimes[@]}"; do 21 | outputPath="$buildDir/$projectName/$runtime" 22 | 23 | echo "Publishing $project for $runtime to $outputPath..." 24 | dotnet publish "$project" -c Release --self-contained -r "$runtime" -o "$outputPath" 25 | done 26 | done 27 | 28 | echo "Build completed!" 29 | --------------------------------------------------------------------------------