├── .gitattributes ├── .github └── workflows │ ├── OsuMemoryDataProvider-nuget.yml │ └── ProcessMemoryDataFinder-nuget.yml ├── .gitignore ├── LICENSE ├── OsuMemoryDataProvider ├── IOsuMemoryReader.cs ├── MathExtensions.cs ├── OsuMemoryDataProvider.csproj ├── OsuMemoryModels │ ├── Abstract │ │ ├── MainPlayerScore.cs │ │ ├── Mods.cs │ │ ├── MultiplayerPlayer.cs │ │ ├── MultiplayerPlayerPlayData.cs │ │ ├── PlayerScore.cs │ │ ├── RulesetPlayData.cs │ │ └── readme.txt │ ├── BanchoStatus.cs │ ├── BaseAddresses.cs │ ├── Direct │ │ ├── BanchoUser.cs │ │ ├── CurrentBeatmap.cs │ │ ├── GeneralData.cs │ │ ├── KeyOverlay.cs │ │ ├── LeaderBoard.cs │ │ ├── MainPlayer.cs │ │ ├── Player.cs │ │ ├── ResultsScreen.cs │ │ ├── Skin.cs │ │ ├── SongSelectionScores.cs │ │ └── readme.txt │ └── RankingType.cs ├── OsuMemoryReader.cs ├── OsuMemoryStatus.cs ├── PlayContainer.cs ├── Properties │ └── AssemblyInfo.cs ├── SignatureNames.cs ├── StructuredOsuMemoryReader.cs └── TourneyIpcState.cs ├── OsuMemoryDataProviderTester ├── CompilerServices.cs ├── Form1.Designer.cs ├── Form1.cs ├── Form1.resx ├── Mods.cs ├── OsuMemoryDataProviderTester.csproj ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── app.config └── tourney-client.bat ├── ProcessMemoryDataFinder.sln ├── ProcessMemoryDataFinder ├── API │ ├── Internals.cs │ ├── MemoryReader.cs │ ├── ProcessMemoryReader.cs │ ├── Sig.cs │ ├── SigMemoryReader.cs │ └── SigScan.cs ├── CompilerServices.cs ├── IntPtrExtensions.cs ├── ObjectReader.cs ├── PatternHelpers.cs ├── ProcessExtensions.cs ├── ProcessMemoryDataFinder.csproj ├── ProcessTargetOptions.cs ├── Properties │ └── AssemblyInfo.cs └── Structured │ ├── AddressFinder.cs │ ├── IStructuredMemoryReader.cs │ ├── MemoryAddressAttribute.cs │ ├── PropInfo.cs │ ├── StructuredMemoryReader.cs │ └── Tokenizer │ ├── AddressTokenizer.cs │ ├── DslToken.cs │ ├── TokenDefinition.cs │ ├── TokenMatch.cs │ └── TokenType.cs ├── README.md └── StructuredOsuMemoryProviderTester ├── CompilerServices.cs ├── Form1.Designer.cs ├── Form1.cs ├── Form1.resx ├── Program.cs └── StructuredOsuMemoryProviderTester.csproj /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sln text eol=crlf 2 | *.cs text eol=crlf 3 | *.csproj text eol=crlf 4 | *.config text eol=crlf -------------------------------------------------------------------------------- /.github/workflows/OsuMemoryDataProvider-nuget.yml: -------------------------------------------------------------------------------- 1 | name: OsuMemoryDataProvider nuget CI 2 | on: 3 | push: 4 | tags: 5 | - osu_v* 6 | jobs: 7 | build: 8 | runs-on: windows-latest 9 | 10 | steps: 11 | - uses: actions/checkout@master 12 | - name: build 13 | run: dotnet build -c Release /p:Platform="Any CPU" 14 | - name: Setup Nuget.exe 15 | uses: nuget/setup-nuget@v2 16 | with: 17 | nuget-api-key: ${{ secrets.NUGET_API_KEY }} 18 | nuget-version: 'latest' 19 | - name: upload sig tester 20 | uses: actions/upload-artifact@v4 21 | with: 22 | name: sigMemoryTester-Net48 23 | path: OsuMemoryDataProviderTester\bin\AnyCPU\Release\net48\* 24 | - name: upload structured tester 25 | uses: actions/upload-artifact@v4 26 | with: 27 | name: structuredMemoryTester-Net8 28 | path: StructuredOsuMemoryProviderTester\bin\AnyCPU\Release\net8.0-windows\* 29 | - name: Nuget Push 30 | run: nuget push OsuMemoryDataProvider\bin\AnyCPU\Release\*.nupkg -Source https://api.nuget.org/v3/index.json 31 | -------------------------------------------------------------------------------- /.github/workflows/ProcessMemoryDataFinder-nuget.yml: -------------------------------------------------------------------------------- 1 | name: ProcessMemoryDataFinder nuget CI 2 | on: 3 | push: 4 | tags: 5 | - process_v* 6 | jobs: 7 | build: 8 | runs-on: windows-latest 9 | 10 | steps: 11 | - uses: actions/checkout@master 12 | - name: build 13 | run: dotnet build -c Release /p:Platform="Any CPU" 14 | - name: Setup Nuget.exe 15 | uses: nuget/setup-nuget@v2 16 | with: 17 | nuget-api-key: ${{ secrets.NUGET_API_KEY }} 18 | nuget-version: 'latest' 19 | - name: Nuget Push 20 | run: nuget push ProcessMemoryDataFinder\bin\AnyCPU\Release\*.nupkg -Source https://api.nuget.org/v3/index.json 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | ### VisualStudio ### 4 | ## Ignore Visual Studio temporary files, build results, and 5 | ## files generated by popular Visual Studio add-ons. 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | build/ 24 | bld/ 25 | [Bb]in/ 26 | [Oo]bj/ 27 | 28 | # Visual Studo 2015 cache/options directory 29 | .vs/ 30 | 31 | # MSTest test Results 32 | [Tt]est[Rr]esult*/ 33 | [Bb]uild[Ll]og.* 34 | 35 | # NUNIT 36 | *.VisualState.xml 37 | TestResult.xml 38 | 39 | # Build Results of an ATL Project 40 | [Dd]ebugPS/ 41 | [Rr]eleasePS/ 42 | dlldata.c 43 | 44 | *_i.c 45 | *_p.c 46 | *_i.h 47 | *.ilk 48 | *.meta 49 | *.obj 50 | *.pch 51 | *.pdb 52 | *.pgc 53 | *.pgd 54 | *.rsp 55 | *.sbr 56 | *.tlb 57 | *.tli 58 | *.tlh 59 | *.tmp 60 | *.tmp_proj 61 | *.log 62 | *.vspscc 63 | *.vssscc 64 | .builds 65 | *.pidb 66 | *.svclog 67 | *.scc 68 | 69 | # Chutzpah Test files 70 | _Chutzpah* 71 | 72 | # Visual C++ cache files 73 | ipch/ 74 | *.aps 75 | *.ncb 76 | *.opensdf 77 | *.sdf 78 | *.cachefile 79 | 80 | # Visual Studio profiler 81 | *.psess 82 | *.vsp 83 | *.vspx 84 | 85 | # TFS 2012 Local Workspace 86 | $tf/ 87 | 88 | # Guidance Automation Toolkit 89 | *.gpState 90 | 91 | # ReSharper is a .NET coding add-in 92 | _ReSharper*/ 93 | *.[Rr]e[Ss]harper 94 | *.DotSettings.user 95 | 96 | # JustCode is a .NET coding addin-in 97 | .JustCode 98 | 99 | # TeamCity is a build add-in 100 | _TeamCity* 101 | 102 | # DotCover is a Code Coverage Tool 103 | *.dotCover 104 | 105 | # NCrunch 106 | _NCrunch_* 107 | .*crunch*.local.xml 108 | 109 | # MightyMoose 110 | *.mm.* 111 | AutoTest.Net/ 112 | 113 | # Web workbench (sass) 114 | .sass-cache/ 115 | 116 | # Installshield output folder 117 | [Ee]xpress/ 118 | 119 | # DocProject is a documentation generator add-in 120 | DocProject/buildhelp/ 121 | DocProject/Help/*.HxT 122 | DocProject/Help/*.HxC 123 | DocProject/Help/*.hhc 124 | DocProject/Help/*.hhk 125 | DocProject/Help/*.hhp 126 | DocProject/Help/Html2 127 | DocProject/Help/html 128 | 129 | # Click-Once directory 130 | publish/ 131 | 132 | # Publish Web Output 133 | *.[Pp]ublish.xml 134 | *.azurePubxml 135 | # TODO: Comment the next line if you want to checkin your web deploy settings 136 | # but database connection strings (with potential passwords) will be unencrypted 137 | *.pubxml 138 | *.publishproj 139 | 140 | # NuGet Packages 141 | *.nupkg 142 | # The packages folder can be ignored because of Package Restore 143 | **/packages/* 144 | # except build/, which is used as an MSBuild target. 145 | !**/packages/build/ 146 | # Uncomment if necessary however generally it will be regenerated when needed 147 | #!**/packages/repositories.config 148 | 149 | # Windows Azure Build Output 150 | csx/ 151 | *.build.csdef 152 | 153 | # Windows Store app package directory 154 | AppPackages/ 155 | 156 | # Others 157 | *.[Cc]ache 158 | ClientBin/ 159 | [Ss]tyle[Cc]op.* 160 | ~$* 161 | *~ 162 | *.dbmdl 163 | *.dbproj.schemaview 164 | *.pfx 165 | *.publishsettings 166 | node_modules/ 167 | bower_components/ 168 | 169 | # RIA/Silverlight projects 170 | Generated_Code/ 171 | 172 | # Backup & report files from converting an old project file 173 | # to a newer Visual Studio version. Backup files are not needed, 174 | # because we have git ;-) 175 | _UpgradeReport_Files/ 176 | Backup*/ 177 | UpgradeLog*.XML 178 | UpgradeLog*.htm 179 | 180 | # SQL Server files 181 | *.mdf 182 | *.ldf 183 | 184 | # Business Intelligence projects 185 | *.rdl.data 186 | *.bim.layout 187 | *.bim_*.settings 188 | 189 | # Microsoft Fakes 190 | FakesAssemblies/ 191 | 192 | # Node.js Tools for Visual Studio 193 | .ntvs_analysis.dat 194 | 195 | # Visual Studio 6 build log 196 | *.plg 197 | 198 | # Visual Studio 6 workspace options file 199 | *.opt 200 | -------------------------------------------------------------------------------- /OsuMemoryDataProvider/IOsuMemoryReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace OsuMemoryDataProvider 5 | { 6 | [Obsolete("This version of reader is not updated anymore with new values. Use StructuredOsuMemoryReader in new implementations.", false)] 7 | public interface IOsuMemoryReader 8 | { 9 | /// 10 | /// Fills all fields of PlayContainer with data read from osu! memory. 11 | /// 12 | /// Initalized object to fill with data 13 | void GetPlayData(PlayContainer playContainer); 14 | 15 | List HitErrors(); 16 | 17 | string PlayerName(); 18 | int GetMods(); 19 | int GetPlayingMods(); 20 | int GetMapId(); 21 | float GetMapAr(); 22 | float GetMapCs(); 23 | float GetMapHp(); 24 | float GetMapOd(); 25 | int GetMapSetId(); 26 | string GetSkinFolderName(); 27 | string GetOsuFileName(); 28 | string GetMapFolderName(); 29 | string GetSongString(); 30 | string GetMapMd5(); 31 | int ReadPlayTime(); 32 | double ReadPlayerHp(); 33 | double ReadDisplayedPlayerHp(); 34 | int ReadPlayedGameMode(); 35 | int ReadSongSelectGameMode(); 36 | 37 | ushort ReadHit300(); 38 | ushort ReadHit100(); 39 | ushort ReadHit50(); 40 | ushort ReadHitGeki(); 41 | ushort ReadHitKatsu(); 42 | ushort ReadHitMiss(); 43 | double ReadAcc(); 44 | ushort ReadCombo(); 45 | ushort ReadComboMax(); 46 | 47 | int GetRetrys(); 48 | 49 | int ReadScore(); 50 | bool IsReplay(); 51 | 52 | /// 53 | ///this works for both normal score and V2 but requires 5 pointer jumps compared to 2 in 54 | /// 55 | /// 56 | int ReadScoreV2(); 57 | TourneyIpcState GetTourneyIpcState(out int ipcNumber); 58 | int ReadTourneyLeftStars(); 59 | int ReadTourneyRightStars(); 60 | int ReadTourneyBO(); 61 | bool ReadTourneyStarsVisible(); 62 | bool ReadTourneyScoreVisible(); 63 | 64 | OsuMemoryStatus GetCurrentStatus(out int statusNumber); 65 | } 66 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/MathExtensions.cs: -------------------------------------------------------------------------------- 1 | #if NET48 2 | using System; 3 | 4 | namespace OsuMemoryDataProvider 5 | { 6 | public static class Math 7 | { 8 | public static T Clamp(T val, T min, T max) where T : IComparable 9 | { 10 | if (val.CompareTo(min) < 0) return min; 11 | else if (val.CompareTo(max) > 0) return max; 12 | else return val; 13 | } 14 | } 15 | } 16 | #endif -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryDataProvider.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net48;net8.0 4 | x86;x64;AnyCPU 5 | Library 6 | false 7 | OsuMemoryDataProvider 8 | OsuMemoryDataProvider 9 | 2019-2022 Piotr Partyka 10 | true 11 | Piotrekol 12 | OsuMemoryDataProvider 13 | Read osu! game memory values based on pre-made memory signatures(patterns) 14 | GPL-3.0-or-later 15 | https://github.com/Piotrekol/ProcessMemoryDataFinder/tree/master/OsuMemoryDataProvider 16 | https://github.com/Piotrekol/ProcessMemoryDataFinder/tree/master/OsuMemoryDataProvider 17 | OsuMemoryDataProvider 18 | Read osu! game memory values based on pre-made memory signatures(patterns) 19 | 0.11.0 20 | true 21 | snupkg 22 | latest 23 | 24 | 25 | 26 | 27 | 28 | bin\x86\Debug\ 29 | MinimumRecommendedRules.ruleset 30 | 31 | 32 | bin\AnyCPU\Debug\ 33 | MinimumRecommendedRules.ruleset 34 | 35 | 36 | bin\x64\Debug\ 37 | MinimumRecommendedRules.ruleset 38 | 39 | 40 | bin\x86\Release\ 41 | MinimumRecommendedRules.ruleset 42 | 43 | 44 | bin\AnyCPU\Release\ 45 | MinimumRecommendedRules.ruleset 46 | 47 | 48 | bin\x64\Release\ 49 | MinimumRecommendedRules.ruleset 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryModels/Abstract/MainPlayerScore.cs: -------------------------------------------------------------------------------- 1 | using ProcessMemoryDataFinder.Structured; 2 | 3 | namespace OsuMemoryDataProvider.OsuMemoryModels.Abstract 4 | { 5 | public class MainPlayerScore : PlayerScore 6 | { 7 | [MemoryAddress("+0x6C")] public int Position { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryModels/Abstract/Mods.cs: -------------------------------------------------------------------------------- 1 | using ProcessMemoryDataFinder.Structured; 2 | 3 | namespace OsuMemoryDataProvider.OsuMemoryModels.Abstract 4 | { 5 | public class Mods 6 | { 7 | [MemoryAddress("[+0x1C]+0x8")] 8 | public int ModsXor1 { get; set; } 9 | [MemoryAddress("[+0x1C]+0xC")] 10 | public int ModsXor2 { get; set; } 11 | public int Value => ModsXor1 ^ ModsXor2; 12 | } 13 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryModels/Abstract/MultiplayerPlayer.cs: -------------------------------------------------------------------------------- 1 | using ProcessMemoryDataFinder.Structured; 2 | 3 | namespace OsuMemoryDataProvider.OsuMemoryModels.Abstract 4 | { 5 | public class MultiplayerPlayer 6 | { 7 | [MemoryAddress("+0x8")] 8 | public string Username { get; set; } 9 | [MemoryAddress("+0x30")] 10 | public int Score { get; set; } 11 | 12 | //Nested play data is created after specific user achieved any hitresult during current play. 13 | //that means in singleplayer this will not resolve until user has hit anything, 14 | //and in multiplayer ^ and if any of the players in the room decides to quit before reporting any hitresult 15 | [MemoryAddress("[+0x20]", true)] 16 | protected MultiplayerPlayerPlayData _multiplayerPlayerPlayData { get; set; } = new MultiplayerPlayerPlayData(); 17 | 18 | public ushort Combo => _multiplayerPlayerPlayData?.Combo ?? 0; 19 | public ushort MaxCombo => _multiplayerPlayerPlayData?.MaxCombo ?? 0; 20 | public Mods Mods => _multiplayerPlayerPlayData?.Mods; 21 | public ushort Hit300 => _multiplayerPlayerPlayData?.Hit300 ?? 0; 22 | public ushort Hit100 => _multiplayerPlayerPlayData?.Hit100 ?? 0; 23 | public ushort Hit50 => _multiplayerPlayerPlayData?.Hit50 ?? 0; 24 | public ushort HitMiss => _multiplayerPlayerPlayData?.HitMiss ?? 0; 25 | [MemoryAddress("+0x40")] 26 | public int Team { get; set; } 27 | [MemoryAddress("+0x2C")] 28 | public int Position { get; set; } 29 | [MemoryAddress("+0x4B")] 30 | public bool IsPassing { get; set; } 31 | } 32 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryModels/Abstract/MultiplayerPlayerPlayData.cs: -------------------------------------------------------------------------------- 1 | using ProcessMemoryDataFinder.Structured; 2 | 3 | namespace OsuMemoryDataProvider.OsuMemoryModels.Abstract 4 | { 5 | public class MultiplayerPlayerPlayData 6 | { 7 | [MemoryAddress("+0x94")] 8 | public ushort Combo { get; set; } 9 | [MemoryAddress("+0x68")] 10 | public ushort MaxCombo { get; set; } 11 | [MemoryAddress("")] 12 | public Mods Mods { get; set; } = new Mods(); 13 | [MemoryAddress("+0x8A")] 14 | public ushort Hit300 { get; set; } 15 | [MemoryAddress("+0x88")] 16 | public ushort Hit100 { get; set; } 17 | [MemoryAddress("+0x8C")] 18 | public ushort Hit50 { get; set; } 19 | [MemoryAddress("+0x92")] 20 | public ushort HitMiss { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryModels/Abstract/PlayerScore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ProcessMemoryDataFinder.Structured; 3 | 4 | namespace OsuMemoryDataProvider.OsuMemoryModels.Abstract 5 | { 6 | public class PlayerScore 7 | { 8 | [MemoryAddress("+0x28")] public string Username { get; set; } 9 | [MemoryAddress("")] public Mods Mods { get; set; } = new Mods(); 10 | [MemoryAddress("+0x64")] public int Mode { get; set; } 11 | [MemoryAddress("+0x68")] public ushort MaxCombo { get; set; } 12 | [MemoryAddress("+0x78")] public virtual int Score { get; set; } 13 | [MemoryAddress("+0x88")] public ushort Hit100 { get; set; } 14 | [MemoryAddress("+0x8A")] public ushort Hit300 { get; set; } 15 | [MemoryAddress("+0x8C")] public ushort Hit50 { get; set; } 16 | [MemoryAddress("+0x8E")] public ushort HitGeki { get; set; } 17 | [MemoryAddress("+0x90")] public ushort HitKatu { get; set; } 18 | [MemoryAddress("+0x92")] public ushort HitMiss { get; set; } 19 | [MemoryAddress("+0xA0")] protected long RawDate { get; set; } 20 | public DateTime Date 21 | { 22 | get 23 | { 24 | var ticks = RawDate & InternalTicksMask; 25 | if (ticks > DateTime.MinValue.Ticks && ticks < DateTime.MaxValue.Ticks) 26 | return new DateTime(ticks); 27 | return DateTime.MinValue; 28 | } 29 | } 30 | [MemoryAddress("[+0x48]+0x70")] public int? UserId { get; set; } 31 | 32 | private static long InternalTicksMask = 4611686018427387903L; 33 | } 34 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryModels/Abstract/RulesetPlayData.cs: -------------------------------------------------------------------------------- 1 | using ProcessMemoryDataFinder.Structured; 2 | 3 | namespace OsuMemoryDataProvider.OsuMemoryModels.Abstract 4 | { 5 | public class RulesetPlayData 6 | { 7 | [MemoryAddress("[+0x38]+0x28")] 8 | public string Username { get; set; } 9 | 10 | [MemoryAddress("[+0x38]")] 11 | public Mods Mods { get; set; } = new Mods(); 12 | 13 | [MemoryAddress("[+0x38]+0x64")] 14 | public int Mode { get; set; } 15 | [MemoryAddress("[+0x38]+0x68")] 16 | public ushort MaxCombo { get; set; } 17 | [MemoryAddress("[CurrentRuleset]+0x100")] 18 | public virtual int Score { get; set; } 19 | public virtual int ScoreV2 { get => Score; set => Score = value; } 20 | [MemoryAddress("[+0x38]+0x88")] 21 | public ushort Hit100 { get; set; } 22 | [MemoryAddress("[+0x38]+0x8A")] 23 | public ushort Hit300 { get; set; } 24 | [MemoryAddress("[+0x38]+0x8C")] 25 | public ushort Hit50 { get; set; } 26 | [MemoryAddress("[+0x38]+0x8E")] 27 | public ushort HitGeki { get; set; } 28 | [MemoryAddress("[+0x38]+0x90")] 29 | public ushort HitKatu { get; set; } 30 | [MemoryAddress("[+0x38]+0x92")] 31 | public ushort HitMiss { get; set; } 32 | [MemoryAddress("[+0x38]+0x94")] 33 | public ushort Combo { get; set; } 34 | 35 | } 36 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryModels/Abstract/readme.txt: -------------------------------------------------------------------------------- 1 | Classes contained in this folder: 2 | require intermediary parent class that will provide base memory address; 3 | and/or can be used only as a prop with valid MemoryAddress attribute. 4 | 5 | These can not be read by passing their reference to IStructuredMemoryReader.TryRead() (will result in invalid read) -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryModels/BanchoStatus.cs: -------------------------------------------------------------------------------- 1 | namespace OsuMemoryDataProvider.OsuMemoryModels 2 | { 3 | //TODO: figure out rest of the values 4 | public enum BanchoStatus 5 | { 6 | Idle = 0, 7 | Afk = 1, 8 | Playing = 2, 9 | } 10 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryModels/BaseAddresses.cs: -------------------------------------------------------------------------------- 1 | using OsuMemoryDataProvider.OsuMemoryModels.Direct; 2 | 3 | namespace OsuMemoryDataProvider.OsuMemoryModels 4 | { 5 | public class OsuBaseAddresses 6 | { 7 | public CurrentBeatmap Beatmap { get; set; } = new CurrentBeatmap(); 8 | public Player Player { get; set; } = new Player(); 9 | public LeaderBoard LeaderBoard { get; set; } = new LeaderBoard(); 10 | public SongSelectionScores SongSelectionScores { get; set; } = new SongSelectionScores(); 11 | public Skin Skin { get; set; } = new Skin(); 12 | public ResultsScreen ResultsScreen { get; set; } = new ResultsScreen(); 13 | public GeneralData GeneralData { get; set; } = new GeneralData(); 14 | public BanchoUser BanchoUser { get; set; } = new BanchoUser(); 15 | public KeyOverlay KeyOverlay { get; set; } = new KeyOverlay(); 16 | } 17 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryModels/Direct/BanchoUser.cs: -------------------------------------------------------------------------------- 1 | using ProcessMemoryDataFinder.Structured; 2 | using System; 3 | 4 | namespace OsuMemoryDataProvider.OsuMemoryModels.Direct 5 | { 6 | [MemoryAddress(null)] 7 | public class BanchoUser 8 | { 9 | [MemoryAddress("IsLoggedIn")] 10 | public bool IsLoggedIn { get; set; } 11 | 12 | internal InternalBanchoUser internalBanchoUser = new(); 13 | 14 | [MemoryAddress(null)] 15 | internal InternalBanchoUser InternalBanchoUser 16 | { 17 | get 18 | { 19 | return IsLoggedIn 20 | ? internalBanchoUser 21 | : null; 22 | } 23 | set => internalBanchoUser = value; 24 | } 25 | 26 | public string Username => InternalBanchoUser?.Username; 27 | public int? UserId => InternalBanchoUser?.UserId; 28 | public string UserCountry => InternalBanchoUser?.UserCountry; 29 | public string UserPpAccLevel => InternalBanchoUser?.UserPpAccLevel; 30 | public BanchoStatus? BanchoStatus => (BanchoStatus?)InternalBanchoUser?.RawBanchoStatus; 31 | } 32 | 33 | [MemoryAddress("[UserPanel]", checkClassAddress: true)] 34 | internal class InternalBanchoUser 35 | { 36 | [MemoryAddress("+0x30")] public string Username { get; set; } 37 | [MemoryAddress("+0x70")] public int? UserId { get; set; } 38 | [MemoryAddress("+0x2C")] public string UserCountry { get; set; } 39 | [MemoryAddress("+0x1C")] public string UserPpAccLevel { get; set; } 40 | //[MemoryAddress("+0x74")] public float? UserLevel { get; set; } 41 | [MemoryAddress("+0x88")] public int? RawBanchoStatus { get; set; } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryModels/Direct/CurrentBeatmap.cs: -------------------------------------------------------------------------------- 1 | using ProcessMemoryDataFinder.Structured; 2 | 3 | namespace OsuMemoryDataProvider.OsuMemoryModels.Direct 4 | { 5 | [MemoryAddress("[CurrentBeatmap]", false, true)] 6 | public class CurrentBeatmap 7 | { 8 | [MemoryAddress("+0xC8")] 9 | public int Id { get; set; } 10 | [MemoryAddress("+0xCC")] 11 | public int SetId { get; set; } 12 | [MemoryAddress("+0x80")] 13 | public string MapString { get; set; } 14 | [MemoryAddress("+0x78")] 15 | public string FolderName { get; set; } 16 | [MemoryAddress("+0x90")] 17 | public string OsuFileName { get; set; } 18 | [MemoryAddress("+0x6C")] 19 | public string Md5 { get; set; } 20 | [MemoryAddress("+0x2C")] 21 | public float Ar { get; set; } 22 | [MemoryAddress("+0x30")] 23 | public float Cs { get; set; } 24 | [MemoryAddress("+0x34")] 25 | public float Hp { get; set; } 26 | [MemoryAddress("+0x38")] 27 | public float Od { get; set; } 28 | [MemoryAddress("+0x12C")] 29 | public short Status { get; set; } 30 | } 31 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryModels/Direct/GeneralData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ProcessMemoryDataFinder.Structured; 3 | 4 | namespace OsuMemoryDataProvider.OsuMemoryModels.Direct 5 | { 6 | public class GeneralData 7 | { 8 | [MemoryAddress("OsuStatus")] public int RawStatus { get; set; } 9 | [MemoryAddress("GameMode")] public int GameMode { get; set; } 10 | [MemoryAddress("Retries")] public int Retries { get; set; } 11 | [MemoryAddress("AudioTime")] public int AudioTime { get; set; } 12 | [MemoryAddress("[TotalAudioTimeBase]+0x4")] public double TotalAudioTime { get; set; } 13 | [MemoryAddress("ChatIsExpanded", true)] public bool ChatIsExpanded { get; set; } 14 | [MemoryAddress("Mods")] public int Mods { get; set; } 15 | [MemoryAddress("[Settings + 0x4] + 0xC")] public bool ShowPlayingInterface { get; set; } 16 | [MemoryAddress("[Settings + 0x44] + 0x4")] public string OsuVersion { get; set; } 17 | 18 | public OsuMemoryStatus OsuStatus 19 | { 20 | get 21 | { 22 | if (Enum.IsDefined(typeof(OsuMemoryStatus), RawStatus)) 23 | { 24 | return (OsuMemoryStatus)RawStatus; 25 | } 26 | 27 | return OsuMemoryStatus.Unknown; 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryModels/Direct/KeyOverlay.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ProcessMemoryDataFinder.Structured; 3 | 4 | namespace OsuMemoryDataProvider.OsuMemoryModels.Direct 5 | { 6 | [MemoryAddress(KeyOverlay.ClassAddress, false, true, nameof(RawHasKeyOverlay))] 7 | public class KeyOverlay 8 | { 9 | internal const string ClassAddress = "[[[[CurrentRuleset]+0xB0]+0x10]+0x4]"; 10 | 11 | [MemoryAddress("")] 12 | private int? RawHasKeyOverlay { get; set; } 13 | public bool Enabled => RawHasKeyOverlay.HasValue && RawHasKeyOverlay != 0; 14 | 15 | [MemoryAddress("[+0x8]+0x1C")] 16 | public bool K1Pressed { get; set; } 17 | [MemoryAddress("[+0x8]+0x14")] 18 | public int K1Count { get; set; } 19 | [MemoryAddress("[+0xC] + 0x1C")] 20 | public bool K2Pressed { get; set; } 21 | [MemoryAddress("[+0xC] + 0x14")] 22 | public int K2Count { get; set; } 23 | [MemoryAddress("[+0x10] + 0x1C")] 24 | public bool M1Pressed { get; set; } 25 | [MemoryAddress("[+0x10] + 0x14")] 26 | public int M1Count { get; set; } 27 | [MemoryAddress("[+0x14] + 0x1C")] 28 | public bool M2Pressed { get; set; } 29 | [MemoryAddress("[+0x14] + 0x14")] 30 | public int M2Count { get; set; } 31 | } 32 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryModels/Direct/LeaderBoard.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using OsuMemoryDataProvider.OsuMemoryModels.Abstract; 5 | using ProcessMemoryDataFinder.Structured; 6 | 7 | namespace OsuMemoryDataProvider.OsuMemoryModels.Direct 8 | { 9 | [MemoryAddress("[[CurrentRuleset]+0x7C]+0x24", false, true, nameof(RawHasLeaderboard))] 10 | public class LeaderBoard 11 | { 12 | //single player: top50 + player top score + current score. possibly more with local scores leaderboard? 13 | protected const int AmountOfPlayerSlots = 52; 14 | 15 | public LeaderBoard() 16 | { 17 | RawPlayers = Enumerable.Range(0, AmountOfPlayerSlots).Select(x => new MultiplayerPlayer()).ToList(); 18 | } 19 | 20 | [MemoryAddress("")] 21 | private int? RawHasLeaderboard { get; set; } 22 | public bool HasLeaderBoard => RawHasLeaderboard.HasValue && RawHasLeaderboard != 0; 23 | private MainPlayer _mainPlayer = new MainPlayer(); 24 | 25 | [MemoryAddress("[[]+0x10]")] 26 | public MainPlayer MainPlayer 27 | { 28 | get => HasLeaderBoard ? _mainPlayer : null; 29 | set => _mainPlayer = value; 30 | } 31 | 32 | private int? _amountOfPlayers; 33 | 34 | [MemoryAddress("[[]+0x4]+0xC")] 35 | public int? AmountOfPlayers 36 | { 37 | get => _amountOfPlayers; 38 | set 39 | { 40 | _amountOfPlayers = value; 41 | if (value.HasValue && value.Value > 0) 42 | Players = _players.GetRange(0, Math.Clamp(value.Value, 0, AmountOfPlayerSlots)); 43 | else 44 | Players.Clear(); 45 | } 46 | } 47 | private List _players; 48 | [MemoryAddress("[]+0x4")] 49 | private List RawPlayers 50 | { 51 | //toggle reading of players depending on HasLeaderboard value 52 | get => HasLeaderBoard ? _players : null; 53 | set 54 | { 55 | _players = value; 56 | } 57 | } 58 | public List Players { get; private set; } = new List(); 59 | } 60 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryModels/Direct/MainPlayer.cs: -------------------------------------------------------------------------------- 1 | using OsuMemoryDataProvider.OsuMemoryModels.Abstract; 2 | using ProcessMemoryDataFinder.Structured; 3 | 4 | namespace OsuMemoryDataProvider.OsuMemoryModels.Direct 5 | { 6 | [MemoryAddress("[[[[CurrentRuleset]+0x7C]+0x24]+0x10]")] 7 | public class MainPlayer : MultiplayerPlayer 8 | { 9 | [MemoryAddress("[+0x24]+0x20")] 10 | public bool IsLeaderboardVisible { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryModels/Direct/Player.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using OsuMemoryDataProvider.OsuMemoryModels.Abstract; 3 | using ProcessMemoryDataFinder.Structured; 4 | 5 | namespace OsuMemoryDataProvider.OsuMemoryModels.Direct 6 | { 7 | [MemoryAddress("[[CurrentRuleset] + 0x68]")] 8 | public class Player : RulesetPlayData 9 | { 10 | //[MemoryAddress("")] 11 | //public int Retries { get; set; } 12 | 13 | [MemoryAddress("[+0x40]+0x14")] 14 | public double HPSmooth { get; set; } 15 | [MemoryAddress("[+0x40]+0x1C")] 16 | public double HP { get; set; } 17 | [MemoryAddress("[+0x48]+0xC")] 18 | public double Accuracy { get; set; } 19 | [MemoryAddress("[+0x38]+0x38")] 20 | public List HitErrors { get; set; } 21 | 22 | [MemoryAddress("IsReplay")] 23 | public bool IsReplay { get; set; } 24 | } 25 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryModels/Direct/ResultsScreen.cs: -------------------------------------------------------------------------------- 1 | using OsuMemoryDataProvider.OsuMemoryModels.Abstract; 2 | using ProcessMemoryDataFinder.Structured; 3 | 4 | namespace OsuMemoryDataProvider.OsuMemoryModels.Direct 5 | { 6 | [MemoryAddress("[CurrentRuleset]")] 7 | public class ResultsScreen : RulesetPlayData 8 | { 9 | [MemoryAddress("[+0x38]+0x78")] 10 | public override int Score { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryModels/Direct/Skin.cs: -------------------------------------------------------------------------------- 1 | using ProcessMemoryDataFinder.Structured; 2 | 3 | namespace OsuMemoryDataProvider.OsuMemoryModels.Direct 4 | { 5 | [MemoryAddress("[CurrentSkinData]")] 6 | public class Skin 7 | { 8 | [MemoryAddress("+0x44")] 9 | public string Folder { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryModels/Direct/SongSelectionScores.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using OsuMemoryDataProvider.OsuMemoryModels.Abstract; 5 | using ProcessMemoryDataFinder.Structured; 6 | 7 | namespace OsuMemoryDataProvider.OsuMemoryModels.Direct 8 | { 9 | [MemoryAddress("[CurrentBeatmap]", false, true, nameof(RawHasScores))] 10 | public class SongSelectionScores 11 | { 12 | //single player: top50 + player top score + current score. 13 | protected const int AmountOfPlayerSlots = 52; 14 | public SongSelectionScores() 15 | { 16 | RawScores = Enumerable.Range(0, AmountOfPlayerSlots).Select(x => new PlayerScore()).ToList(); 17 | } 18 | 19 | [MemoryAddress("+0x104")] 20 | private int RawRankingType { get; set; } 21 | public RankingType RankingType => 22 | Enum.IsDefined(typeof(RankingType), RawRankingType) 23 | ? (RankingType)RawRankingType 24 | : RankingType.Unknown; 25 | [MemoryAddress("+0xD0")] 26 | public int TotalScores { get; set; } 27 | 28 | [MemoryAddress("+0x98")] 29 | private int? RawHasMainPlayerScore { get; set; } 30 | private MainPlayerScore _mainPlayerScore = new MainPlayerScore(); 31 | [MemoryAddress("[+0x98]")] 32 | public MainPlayerScore MainPlayerScore 33 | { 34 | get => RawHasMainPlayerScore.HasValue && RawHasMainPlayerScore != 0 ? _mainPlayerScore : null; 35 | set => _mainPlayerScore = value; 36 | } 37 | 38 | private int? _amountOfScores; 39 | [MemoryAddress("[+0x9C]+0xC")] 40 | public int? AmountOfScores 41 | { 42 | get => _amountOfScores; 43 | set 44 | { 45 | _amountOfScores = value; 46 | if (value.HasValue && value.Value > 0) 47 | Scores = _scores.GetRange(0, Math.Clamp(value.Value, 0, AmountOfPlayerSlots)); 48 | else 49 | Scores.Clear(); 50 | } 51 | } 52 | 53 | [MemoryAddress("+0x9C")] 54 | private int? RawHasScores { get; set; } 55 | private List _scores; 56 | [MemoryAddress("+0x9C")] 57 | private List RawScores 58 | { 59 | //toggle reading of players 60 | get => RawHasScores.HasValue && RawHasScores != 0 ? _scores : null; 61 | set 62 | { 63 | _scores = value; 64 | } 65 | } 66 | public List Scores { get; private set; } = new List(); 67 | 68 | } 69 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryModels/Direct/readme.txt: -------------------------------------------------------------------------------- 1 | Classes contained in this folder: 2 | provide absolute/constant base MemoryAddress; 3 | and/or all props inside use non-relative MemoryAdresses. 4 | 5 | Classes contained in this folder can be read by passing their reference to IStructuredMemoryReader.TryRead(). -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryModels/RankingType.cs: -------------------------------------------------------------------------------- 1 | namespace OsuMemoryDataProvider.OsuMemoryModels 2 | { 3 | public enum RankingType 4 | { 5 | Local, 6 | Top, 7 | SelectedMods, 8 | Friends, 9 | Country, 10 | Unknown = 10 11 | } 12 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/OsuMemoryStatus.cs: -------------------------------------------------------------------------------- 1 | namespace OsuMemoryDataProvider 2 | { 3 | public enum OsuMemoryStatus 4 | { 5 | NotRunning = -1, 6 | MainMenu = 0, 7 | EditingMap = 1, 8 | Playing = 2, 9 | GameShutdownAnimation = 3, 10 | SongSelectEdit = 4, 11 | SongSelect = 5, 12 | WIP_NoIdeaWhatThisIs =6, 13 | ResultsScreen = 7, 14 | GameStartupAnimation = 10, 15 | MultiplayerRooms = 11, 16 | MultiplayerRoom = 12, 17 | MultiplayerSongSelect = 13, 18 | MultiplayerResultsscreen = 14, 19 | OsuDirect = 15, 20 | RankingTagCoop = 17, 21 | RankingTeam = 18, 22 | ProcessingBeatmaps = 19, 23 | Tourney = 22, 24 | 25 | /// 26 | /// Indicates that status read in osu memory is not defined in 27 | /// 28 | Unknown = -2, 29 | } 30 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/PlayContainer.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace OsuMemoryDataProvider 4 | { 5 | public class PlayContainer 6 | { 7 | public double Acc { get; set; } 8 | public ushort C300 { get; set; } 9 | public ushort C100 { get; set; } 10 | public ushort C50 { get; set; } 11 | public ushort CGeki { get; set; } 12 | public ushort CKatsu { get; set; } 13 | public ushort CMiss { get; set; } 14 | public ushort MaxCombo { get; set; } 15 | public ushort Combo { get; set; } 16 | public double Hp { get; set; } 17 | 18 | public int Score { get; set; } 19 | 20 | [Obfuscation(Exclude = true)] 21 | public void Reset() 22 | { 23 | Acc = 0; 24 | C300 = 0; 25 | C100 = 0; 26 | C50 = 0; 27 | CGeki = 0; 28 | CKatsu = 0; 29 | CMiss = 0; 30 | Combo = 0; 31 | MaxCombo = 0; 32 | Hp = 0; 33 | Score = 0; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | 5 | // Setting ComVisible to false makes the types in this assembly not visible 6 | // to COM components. If you need to access a type in this assembly from 7 | // COM, set the ComVisible attribute to true on that type. 8 | [assembly: ComVisible(false)] 9 | 10 | // The following GUID is for the ID of the typelib if this project is exposed to COM 11 | [assembly: Guid("d117800f-072d-4ae4-9679-3e2a129a1a3c")] 12 | -------------------------------------------------------------------------------- /OsuMemoryDataProvider/SignatureNames.cs: -------------------------------------------------------------------------------- 1 | namespace OsuMemoryDataProvider 2 | { 3 | 4 | internal enum SignatureNames 5 | { 6 | OsuBase, 7 | CurrentBeatmapData, 8 | 9 | GameMode, 10 | MapId, 11 | MapSetId, 12 | MapCs, 13 | MapHp, 14 | MapOd, 15 | MapAr, 16 | MapMd5, 17 | MapString, 18 | MapFolderName, 19 | MapOsuFileName, 20 | CurrentSkinData, 21 | CurrentSkinFolder, 22 | OsuStatus, 23 | PlayContainer, 24 | CurrentRuleset, 25 | Mods, 26 | PlayingMods, 27 | Combo, 28 | ComboMax, 29 | Hit300c, 30 | Hit100c, 31 | Hit50c, 32 | HitGeki, 33 | HitKatsu, 34 | HitMissc, 35 | Acc, 36 | Score, 37 | ScoreV2, 38 | PlayingGameMode, 39 | PlayerHp, 40 | PlayerHpSmoothed, 41 | PlayTime, 42 | Retrys, 43 | IsReplay, 44 | 45 | PlayerName, 46 | HitErrors, 47 | 48 | // tourney shit 49 | TourneyBase, 50 | TourneyIpcState, 51 | TourneyLeftStars, 52 | TourneyRightStars, 53 | TourneyBO, 54 | TourneyStarsVisible, 55 | TourneyScoreVisible, 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/StructuredOsuMemoryReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using OsuMemoryDataProvider.OsuMemoryModels; 6 | using OsuMemoryDataProvider.OsuMemoryModels.Abstract; 7 | using ProcessMemoryDataFinder.Structured; 8 | using ProcessMemoryDataFinder; 9 | 10 | namespace OsuMemoryDataProvider 11 | { 12 | public class StructuredOsuMemoryReader : IStructuredMemoryReader, IDisposable 13 | { 14 | private StructuredMemoryReader _memoryReader; 15 | private static readonly ConcurrentDictionary Instances = []; 16 | private static StructuredOsuMemoryReader instance; 17 | 18 | public OsuBaseAddresses OsuMemoryAddresses { get; } = new OsuBaseAddresses(); 19 | /// 20 | /// It is strongly encouraged to use single instance in order to not have to duplicate 21 | /// find-pattern-location work 22 | /// 23 | public static StructuredOsuMemoryReader Instance => instance ??= new StructuredOsuMemoryReader(new("osu!")); 24 | 25 | public bool WithTimes 26 | { 27 | get => _memoryReader.WithTimes; 28 | set => _memoryReader.WithTimes = value; 29 | } 30 | public Dictionary ReadTimes => _memoryReader.ReadTimes; 31 | public bool CanRead => _memoryReader.CanRead; 32 | public bool AbortReadOnInvalidValue 33 | { 34 | get => _memoryReader.AbortReadOnInvalidValue; 35 | set => _memoryReader.AbortReadOnInvalidValue = value; 36 | } 37 | public event EventHandler<(object readObject, string propPath)> InvalidRead 38 | { 39 | add => _memoryReader.InvalidRead += value; 40 | remove => _memoryReader.InvalidRead -= value; 41 | } 42 | public int ProcessWatcherDelayMs 43 | { 44 | get => _memoryReader.ProcessWatcherDelayMs; 45 | set => _memoryReader.ProcessWatcherDelayMs = value; 46 | } 47 | 48 | public static StructuredOsuMemoryReader GetInstance(ProcessTargetOptions processTargetOptions) 49 | { 50 | if (processTargetOptions is null) 51 | { 52 | return Instance; 53 | } 54 | 55 | return Instances.GetOrAdd(processTargetOptions, s => new StructuredOsuMemoryReader(processTargetOptions)); 56 | } 57 | 58 | public Dictionary BaseAddresses { get; } = new Dictionary 59 | { 60 | //class pointers 61 | {"Base", "F80174048365"}, 62 | {"CurrentBeatmap","[Base-0xC]"}, 63 | {"CurrentSkinData","[75218B1D+0x4]"}, 64 | {"CurrentRuleset","[C7864801000001000000A1+0xB]+0x4"},// or backup: 7D15A1????????85C0-B]+4 //TourneyBase 65 | {"Settings", "[83E02085C07E2F+0x8]"}, 66 | {"TotalAudioTimeBase", "[83E4F8575683EC38+0xA]" }, 67 | {"UserPanel", "[FFFF895070+0x6]" }, 68 | 69 | //static values 70 | {"OsuStatus", "[4883F804731E-0x4]"},//[Base-0x3C] 71 | {"GameMode", "[Base-0x33]"}, 72 | {"Retries", "[Base-0x33]+0x8"}, 73 | {"AudioTime","[Base+0x64]-0x10"}, 74 | {"Mods","[C8FF??????????810D????????00080000+0x9]"}, 75 | {"IsReplay","[8BFAB801000000+0x2A]"}, 76 | {"ChatIsExpanded","0AD7233C0000??01-0x20"}, 77 | {"IsLoggedIn", "[B80B00008B35-0xB]" } 78 | }; 79 | 80 | public StructuredOsuMemoryReader(ProcessTargetOptions processTargetOptions) 81 | { 82 | _memoryReader = new MultiplayerPlayerStructuredMemoryReader("osu!", BaseAddresses, processTargetOptions); 83 | } 84 | public bool TryRead(T readObj) where T : class 85 | => _memoryReader.TryRead(readObj); 86 | 87 | public bool TryReadProperty(object readObj, string propertyName, out object result) 88 | => _memoryReader.TryReadProperty(readObj, propertyName, out result); 89 | protected virtual void Dispose(bool disposing) 90 | { 91 | if (disposing) 92 | { 93 | _memoryReader?.Dispose(); 94 | } 95 | } 96 | 97 | public void Dispose() 98 | { 99 | Dispose(true); 100 | GC.SuppressFinalize(this); 101 | } 102 | 103 | protected class MultiplayerPlayerStructuredMemoryReader : StructuredMemoryReader 104 | { 105 | public MultiplayerPlayerStructuredMemoryReader(string processName, Dictionary baseAdresses, ProcessTargetOptions processTargetOptions) : base(processName, baseAdresses, processTargetOptions) 106 | { 107 | ObjectReader.IntPtrSize = _addressFinder.IntPtrSize = _memoryReader.IntPtrSize = 4; 108 | AddReadHandlers(new Dictionary 109 | { 110 | { typeof(List), ReadList }, 111 | { typeof(List), ReadList } 112 | }); 113 | } 114 | 115 | private IList ReadList(IntPtr finalAddress, PropInfo propInfo) 116 | { 117 | if (finalAddress == IntPtr.Zero) 118 | return null; 119 | 120 | var classPointers = ObjectReader.ReadUIntList(finalAddress); 121 | var propListValue = (IList)propInfo.Getter(); 122 | if (classPointers == null || classPointers.Count == 0 || classPointers.Count > propListValue.Count) 123 | return propListValue; 124 | 125 | var rootPath = $"{propInfo.Path}*{classPointers.Count}"; 126 | for (int i = 0; i < classPointers.Count; i++) 127 | { 128 | if (classPointers[i] > IntPtrExtensions.MaxValue) 129 | return propListValue; 130 | 131 | TryInternalRead(propListValue[i], new IntPtr(classPointers[i]), $"{rootPath}[{i}]"); 132 | } 133 | 134 | return propListValue; 135 | } 136 | } 137 | } 138 | } -------------------------------------------------------------------------------- /OsuMemoryDataProvider/TourneyIpcState.cs: -------------------------------------------------------------------------------- 1 | namespace OsuMemoryDataProvider 2 | { 3 | public enum TourneyIpcState 4 | { 5 | Initialising, 6 | Idle, 7 | WaitingForClients, 8 | Playing, 9 | Ranking, 10 | 11 | /// 12 | /// Indicates that state read in osu memory is not defined in 13 | /// 14 | Unknown = -1, 15 | } 16 | } -------------------------------------------------------------------------------- /OsuMemoryDataProviderTester/CompilerServices.cs: -------------------------------------------------------------------------------- 1 | #if NET48 2 | namespace System.Runtime.CompilerServices 3 | { 4 | [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] 5 | internal static class IsExternalInit { } 6 | } 7 | #endif -------------------------------------------------------------------------------- /OsuMemoryDataProviderTester/Form1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Diagnostics; 5 | using System.Globalization; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using System.Windows.Forms; 9 | using OsuMemoryDataProvider; 10 | using OsuMemoryDataProvider.OsuMemoryModels; 11 | 12 | namespace OsuMemoryDataProviderTester 13 | { 14 | public partial class Form1 : Form 15 | { 16 | private readonly string _osuWindowTitleHint; 17 | private int _readDelay = 33; 18 | private readonly object _minMaxLock = new object(); 19 | private double _memoryReadTimeMin = double.PositiveInfinity; 20 | private double _memoryReadTimeMax = double.NegativeInfinity; 21 | private readonly ISet _patternsToSkip = new HashSet(); 22 | #pragma warning disable CS0618 // Type or member is obsolete 23 | private readonly IOsuMemoryReader _reader; 24 | #pragma warning restore CS0618 // Type or member is obsolete 25 | private readonly StructuredOsuMemoryReader _sreader; 26 | private CancellationTokenSource cts = new CancellationTokenSource(); 27 | public Form1(string osuWindowTitleHint) 28 | { 29 | _osuWindowTitleHint = osuWindowTitleHint; 30 | InitializeComponent(); 31 | #pragma warning disable CS0618 // Type or member is obsolete 32 | _reader = OsuMemoryReader.GetInstance(new("osu!", osuWindowTitleHint)); 33 | #pragma warning restore CS0618 // Type or member is obsolete 34 | _sreader = StructuredOsuMemoryReader.GetInstance(new("osu!", osuWindowTitleHint)); 35 | Shown += OnShown; 36 | Closing += OnClosing; 37 | numericUpDown_readDelay.ValueChanged += NumericUpDownReadDelayOnValueChanged; 38 | } 39 | 40 | private void NumericUpDownReadDelayOnValueChanged(object sender, EventArgs eventArgs) 41 | { 42 | if (int.TryParse(numericUpDown_readDelay.Value.ToString(CultureInfo.InvariantCulture), out var value)) 43 | { 44 | _readDelay = value; 45 | } 46 | else 47 | { 48 | numericUpDown_readDelay.Value = 33; 49 | } 50 | } 51 | 52 | private void OnClosing(object sender, CancelEventArgs cancelEventArgs) 53 | { 54 | cts.Cancel(); 55 | } 56 | 57 | private void OnShown(object sender, EventArgs eventArgs) 58 | { 59 | if (!string.IsNullOrEmpty(_osuWindowTitleHint)) 60 | Text += $": {_osuWindowTitleHint}"; 61 | _ = Task.Run(async () => 62 | { 63 | try 64 | { 65 | Stopwatch stopwatch; 66 | double readTimeMs, readTimeMsMin, readTimeMsMax; 67 | var playContainer = new PlayContainerEx(); 68 | var playReseted = false; 69 | var baseAddresses = new OsuBaseAddresses(); 70 | while (true) 71 | { 72 | if (cts.IsCancellationRequested) 73 | return; 74 | 75 | var patternsToRead = GetPatternsToRead(); 76 | 77 | stopwatch = Stopwatch.StartNew(); 78 | 79 | #region OsuBase 80 | 81 | var mapId = -1; 82 | var mapSetId = -1; 83 | var songString = string.Empty; 84 | var mapMd5 = string.Empty; 85 | var mapFolderName = string.Empty; 86 | var osuFileName = string.Empty; 87 | var retrys = -1; 88 | var gameMode = -1; 89 | var mapData = string.Empty; 90 | var status = OsuMemoryStatus.Unknown; 91 | var statusNum = -1; 92 | var playTime = -1; 93 | if (patternsToRead.OsuBase) 94 | { 95 | mapId = _reader.GetMapId(); 96 | mapSetId = _reader.GetMapSetId(); 97 | songString = _reader.GetSongString(); 98 | mapMd5 = _reader.GetMapMd5(); 99 | mapFolderName = _reader.GetMapFolderName(); 100 | osuFileName = _reader.GetOsuFileName(); 101 | retrys = _reader.GetRetrys(); 102 | gameMode = _reader.ReadSongSelectGameMode(); 103 | mapData = 104 | $"HP:{_reader.GetMapHp()} OD:{_reader.GetMapOd()}, CS:{_reader.GetMapCs()}, AR:{_reader.GetMapAr()}, setId:{_reader.GetMapSetId()}"; 105 | status = _reader.GetCurrentStatus(out statusNum); 106 | playTime = _reader.ReadPlayTime(); 107 | } 108 | 109 | #endregion 110 | 111 | #region Mods 112 | 113 | var mods = -1; 114 | if (patternsToRead.Mods) 115 | { 116 | mods = _reader.GetMods(); 117 | } 118 | 119 | #endregion 120 | 121 | #region CurrentSkinData 122 | 123 | var skinFolderName = string.Empty; 124 | if (patternsToRead.CurrentSkinData) 125 | { 126 | skinFolderName = _reader.GetSkinFolderName(); 127 | } 128 | 129 | #endregion 130 | 131 | #region IsReplay 132 | 133 | bool isReplay = false; 134 | if (status == OsuMemoryStatus.Playing && patternsToRead.IsReplay) 135 | { 136 | isReplay = _reader.IsReplay(); 137 | } 138 | 139 | #endregion 140 | 141 | #region PlayContainer 142 | 143 | double hp = 0; 144 | var playerName = string.Empty; 145 | var hitErrorCount = -1; 146 | var playingMods = -1; 147 | double displayedPlayerHp = 0; 148 | int scoreV2 = -1; 149 | if (status == OsuMemoryStatus.Playing && patternsToRead.PlayContainer) 150 | { 151 | playReseted = false; 152 | _reader.GetPlayData(playContainer); 153 | hp = _reader.ReadPlayerHp(); 154 | playerName = _reader.PlayerName(); 155 | hitErrorCount = _reader.HitErrors()?.Count ?? -2; 156 | playingMods = _reader.GetPlayingMods(); 157 | displayedPlayerHp = _reader.ReadDisplayedPlayerHp(); 158 | scoreV2 = _reader.ReadScoreV2(); 159 | } 160 | else if (!playReseted) 161 | { 162 | playReseted = true; 163 | playContainer.Reset(); 164 | } 165 | 166 | #endregion 167 | 168 | #region TourneyBase 169 | 170 | // TourneyBase 171 | var tourneyIpcState = TourneyIpcState.Unknown; 172 | var tourneyIpcStateNumber = -1; 173 | var tourneyLeftStars = -1; 174 | var tourneyRightStars = -1; 175 | var tourneyBO = -1; 176 | var tourneyStarsVisible = false; 177 | var tourneyScoreVisible = false; 178 | if (status == OsuMemoryStatus.Tourney && patternsToRead.TourneyBase) 179 | { 180 | tourneyIpcState = _reader.GetTourneyIpcState(out tourneyIpcStateNumber); 181 | tourneyLeftStars = _reader.ReadTourneyLeftStars(); 182 | tourneyRightStars = _reader.ReadTourneyRightStars(); 183 | tourneyBO = _reader.ReadTourneyBO(); 184 | tourneyStarsVisible = _reader.ReadTourneyStarsVisible(); 185 | tourneyScoreVisible = _reader.ReadTourneyScoreVisible(); 186 | } 187 | 188 | #endregion 189 | 190 | stopwatch.Stop(); 191 | 192 | readTimeMs = stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond; 193 | lock (_minMaxLock) 194 | { 195 | if (readTimeMs < _memoryReadTimeMin) 196 | _memoryReadTimeMin = readTimeMs; 197 | if (readTimeMs > _memoryReadTimeMax) 198 | _memoryReadTimeMax = readTimeMs; 199 | // copy value since we're inside lock 200 | readTimeMsMin = _memoryReadTimeMin; 201 | readTimeMsMax = _memoryReadTimeMax; 202 | } 203 | 204 | Invoke((MethodInvoker)(() => 205 | { 206 | textBox_readTime.Text = $" ReadTimeMS: {readTimeMs}{Environment.NewLine}" + 207 | $" Min ReadTimeMS: {readTimeMsMin}{Environment.NewLine}" + 208 | $"Max ReadTimeMS: {readTimeMsMax}{Environment.NewLine}"; 209 | 210 | textBox_mapId.Text = $"Id:{mapId} setId:{mapSetId}"; 211 | textBox_strings.Text = $"songString: \"{songString}\" {Environment.NewLine}" + 212 | $"md5: \"{mapMd5}\" {Environment.NewLine}" + 213 | $"mapFolder: \"{mapFolderName}\" {Environment.NewLine}" + 214 | $"fileName: \"{osuFileName}\" {Environment.NewLine}" + 215 | $"Retrys:{retrys} {Environment.NewLine}" + 216 | $"mods:{(Mods)mods}({mods}) {Environment.NewLine}" + 217 | $"SkinName: \"{skinFolderName}\""; 218 | textBox_time.Text = playTime.ToString(); 219 | textBox_mapData.Text = mapData; 220 | textBox_Status.Text = status + " " + statusNum + " " + gameMode; 221 | 222 | textBox_CurrentPlayData.Text = 223 | playContainer + Environment.NewLine + 224 | $"scoreV2: {scoreV2} {Environment.NewLine}" + 225 | $"IsReplay: {isReplay} {Environment.NewLine}" + 226 | $"hp________: {hp:00.##} {Environment.NewLine}" + 227 | $"displayedHp: {displayedPlayerHp:00.##} {Environment.NewLine}" + 228 | $"playingMods:{(Mods)playingMods} ({playingMods}) " + 229 | $"PlayerName: {playerName}{Environment.NewLine}" + 230 | $"HitErrorCount: {hitErrorCount} "; 231 | 232 | if (status == OsuMemoryStatus.Tourney) 233 | { 234 | textBox_TourneyStuff.Text = 235 | $"IPC-State: {tourneyIpcState} ({tourneyIpcStateNumber}) | BO {tourneyBO}{Environment.NewLine}" + 236 | $"Stars: {tourneyLeftStars} | {tourneyRightStars}{Environment.NewLine}" + 237 | $"Warmup/Stars State: {(tourneyStarsVisible ? "stars visible, warmup disabled" : "stars hidden, warmup enabled")}{Environment.NewLine}" + 238 | $"Score/Chat state: {(tourneyScoreVisible ? "chat hidden, score visible or no lobby joined" : "chat visible, score hidden")}{Environment.NewLine}"; 239 | } 240 | else 241 | { 242 | textBox_TourneyStuff.Text = "no data since not in tourney mode"; 243 | } 244 | })); 245 | await Task.Delay(_readDelay); 246 | } 247 | } 248 | catch (ThreadAbortException) 249 | { 250 | } 251 | }); 252 | } 253 | 254 | public class PlayContainerEx : PlayContainer 255 | { 256 | public override string ToString() 257 | { 258 | var nl = Environment.NewLine; 259 | return $"{C300}/{C100}/{C50}/{CMiss} : {CGeki},{CKatsu}" + nl + 260 | $"acc:{Acc}, combo: {Combo}, maxCombo {MaxCombo}" + nl + 261 | $"score: {Score}"; 262 | } 263 | } 264 | 265 | private void button_ResetReadTimeMinMax_Click(object sender, EventArgs e) 266 | { 267 | lock (_minMaxLock) 268 | { 269 | _memoryReadTimeMin = double.PositiveInfinity; 270 | _memoryReadTimeMax = double.NegativeInfinity; 271 | } 272 | } 273 | 274 | private void checkBox_CheckedChanged(object sender, EventArgs e) 275 | { 276 | var cb = (CheckBox)sender; 277 | var name = (string)cb.Tag; 278 | var shouldInclude = cb.Checked; 279 | lock (_patternsToSkip) 280 | { 281 | // we store inverted state, easier since default is all on, so we can have a default of empty set to check :D 282 | if (shouldInclude) 283 | { 284 | _patternsToSkip.Remove(name); 285 | } 286 | else 287 | { 288 | _patternsToSkip.Add(name); 289 | } 290 | } 291 | } 292 | 293 | private PatternsToRead GetPatternsToRead() 294 | { 295 | lock (_patternsToSkip) 296 | { 297 | return new PatternsToRead(_patternsToSkip); 298 | } 299 | } 300 | } 301 | 302 | internal struct PatternsToRead 303 | { 304 | public readonly bool OsuBase; 305 | public readonly bool Mods; 306 | public readonly bool IsReplay; 307 | public readonly bool CurrentSkinData; 308 | public readonly bool TourneyBase; 309 | public readonly bool PlayContainer; 310 | 311 | public PatternsToRead(ISet patternsToSkip) 312 | { 313 | OsuBase = !patternsToSkip.Contains(nameof(OsuBase)); 314 | Mods = !patternsToSkip.Contains(nameof(Mods)); 315 | IsReplay = !patternsToSkip.Contains(nameof(IsReplay)); 316 | CurrentSkinData = !patternsToSkip.Contains(nameof(CurrentSkinData)); 317 | TourneyBase = !patternsToSkip.Contains(nameof(TourneyBase)); 318 | PlayContainer = !patternsToSkip.Contains(nameof(PlayContainer)); 319 | } 320 | } 321 | } -------------------------------------------------------------------------------- /OsuMemoryDataProviderTester/Form1.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | -------------------------------------------------------------------------------- /OsuMemoryDataProviderTester/Mods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OsuMemoryDataProviderTester 4 | { 5 | [Flags] 6 | public enum Mods 7 | { 8 | Omod = 0, 9 | Nf = 1 << 0, 10 | Ez = 1 << 1, 11 | Td = 1 << 2, //Touch device 12 | Hd = 1 << 3, 13 | Hr = 1 << 4, 14 | Sd = 1 << 5, 15 | Dt = 1 << 6, 16 | RX = 1 << 7, 17 | Ht = 1 << 8, 18 | Nc = 1 << 9, 19 | Fl = 1 << 10, 20 | Au = 1 << 11, //auto play 21 | So = 1 << 12, 22 | Ap = 1 << 13, //Auto pilot 23 | Pf = 1 << 14, 24 | K4 = 1 << 15, 25 | K5 = 1 << 16, 26 | K6 = 1 << 17, 27 | K7 = 1 << 18, 28 | K8 = 1 << 19, 29 | Fi = 1 << 20, 30 | Rn = 1 << 21, 31 | Cm = 1 << 22, 32 | Tp = 1 << 23, 33 | K9 = 1 << 24, 34 | Coop = 1 << 25, 35 | K1 = 1 << 26, 36 | K3 = 1 << 27, 37 | K2 = 1 << 28, 38 | Sv2 = 1 << 29, 39 | Lm = 1 << 30, 40 | SpeedChanging = Dt | Ht | Nc, 41 | MapChanging = Hr | Ez | SpeedChanging 42 | } 43 | } -------------------------------------------------------------------------------- /OsuMemoryDataProviderTester/OsuMemoryDataProviderTester.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net48;net8.0-windows 4 | x86;x64;AnyCPU 5 | WinExe 6 | false 7 | true 8 | latest 9 | 10 | 11 | bin\x86\Debug\ 12 | MinimumRecommendedRules.ruleset 13 | 14 | 15 | bin\AnyCPU\Debug\ 16 | MinimumRecommendedRules.ruleset 17 | 18 | 19 | bin\x64\Debug\ 20 | MinimumRecommendedRules.ruleset 21 | 22 | 23 | bin\x86\Release\ 24 | MinimumRecommendedRules.ruleset 25 | 26 | 27 | bin\AnyCPU\Release\ 28 | MinimumRecommendedRules.ruleset 29 | 30 | 31 | bin\x86\Release\ 32 | MinimumRecommendedRules.ruleset 33 | 34 | 35 | 36 | Always 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /OsuMemoryDataProviderTester/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Windows.Forms; 4 | 5 | namespace OsuMemoryDataProviderTester 6 | { 7 | static class Program 8 | { 9 | /// 10 | /// The main entry point for the application. 11 | /// 12 | [STAThread] 13 | static void Main(string[] args) 14 | { 15 | Application.EnableVisualStyles(); 16 | Application.SetCompatibleTextRenderingDefault(false); 17 | Application.Run(new Form1(args.FirstOrDefault())); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /OsuMemoryDataProviderTester/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Tester")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Tester")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("2450525d-627d-4aba-acf0-fa1aa76ce4a0")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /OsuMemoryDataProviderTester/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace OsuMemoryDataProviderTester.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("OsuMemoryDataProviderTester.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /OsuMemoryDataProviderTester/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /OsuMemoryDataProviderTester/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace OsuMemoryDataProviderTester.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.6.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /OsuMemoryDataProviderTester/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /OsuMemoryDataProviderTester/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /OsuMemoryDataProviderTester/tourney-client.bat: -------------------------------------------------------------------------------- 1 | REM the manager window 2 | start OsuMemoryDataProviderTester.exe "Tournament Manager" 3 | 4 | REM all the individual client windows, repeat this line for however many clients there are 5 | start OsuMemoryDataProviderTester.exe "Tournament Client 0" 6 | start OsuMemoryDataProviderTester.exe "Tournament Client 1" -------------------------------------------------------------------------------- /ProcessMemoryDataFinder.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29009.5 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProcessMemoryDataFinder", "ProcessMemoryDataFinder\ProcessMemoryDataFinder.csproj", "{9960D641-300A-432C-AA04-C351A99B9ADB}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OsuMemoryDataProvider", "OsuMemoryDataProvider\OsuMemoryDataProvider.csproj", "{D117800F-072D-4AE4-9679-3E2A129A1A3C}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OsuMemoryDataProviderTester", "OsuMemoryDataProviderTester\OsuMemoryDataProviderTester.csproj", "{2450525D-627D-4ABA-ACF0-FA1AA76CE4A0}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "osu!", "osu!", "{FD3B44E6-66B3-4C58-B590-FB11B9CF6FF7}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StructuredOsuMemoryProviderTester", "StructuredOsuMemoryProviderTester\StructuredOsuMemoryProviderTester.csproj", "{2965C7BF-B5DD-4345-9DFD-4E6B7D93DC0A}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Debug|x64 = Debug|x64 20 | Debug|x86 = Debug|x86 21 | Release|Any CPU = Release|Any CPU 22 | Release|x64 = Release|x64 23 | Release|x86 = Release|x86 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {9960D641-300A-432C-AA04-C351A99B9ADB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {9960D641-300A-432C-AA04-C351A99B9ADB}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {9960D641-300A-432C-AA04-C351A99B9ADB}.Debug|x64.ActiveCfg = Debug|x64 29 | {9960D641-300A-432C-AA04-C351A99B9ADB}.Debug|x64.Build.0 = Debug|x64 30 | {9960D641-300A-432C-AA04-C351A99B9ADB}.Debug|x86.ActiveCfg = Debug|x86 31 | {9960D641-300A-432C-AA04-C351A99B9ADB}.Debug|x86.Build.0 = Debug|x86 32 | {9960D641-300A-432C-AA04-C351A99B9ADB}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {9960D641-300A-432C-AA04-C351A99B9ADB}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {9960D641-300A-432C-AA04-C351A99B9ADB}.Release|x64.ActiveCfg = Release|x64 35 | {9960D641-300A-432C-AA04-C351A99B9ADB}.Release|x64.Build.0 = Release|x64 36 | {9960D641-300A-432C-AA04-C351A99B9ADB}.Release|x86.ActiveCfg = Release|x86 37 | {9960D641-300A-432C-AA04-C351A99B9ADB}.Release|x86.Build.0 = Release|x86 38 | {D117800F-072D-4AE4-9679-3E2A129A1A3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {D117800F-072D-4AE4-9679-3E2A129A1A3C}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {D117800F-072D-4AE4-9679-3E2A129A1A3C}.Debug|x64.ActiveCfg = Debug|x64 41 | {D117800F-072D-4AE4-9679-3E2A129A1A3C}.Debug|x86.ActiveCfg = Debug|x86 42 | {D117800F-072D-4AE4-9679-3E2A129A1A3C}.Debug|x86.Build.0 = Debug|x86 43 | {D117800F-072D-4AE4-9679-3E2A129A1A3C}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {D117800F-072D-4AE4-9679-3E2A129A1A3C}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {D117800F-072D-4AE4-9679-3E2A129A1A3C}.Release|x64.ActiveCfg = Release|x64 46 | {D117800F-072D-4AE4-9679-3E2A129A1A3C}.Release|x64.Build.0 = Release|x64 47 | {D117800F-072D-4AE4-9679-3E2A129A1A3C}.Release|x86.ActiveCfg = Release|x86 48 | {D117800F-072D-4AE4-9679-3E2A129A1A3C}.Release|x86.Build.0 = Release|x86 49 | {2450525D-627D-4ABA-ACF0-FA1AA76CE4A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {2450525D-627D-4ABA-ACF0-FA1AA76CE4A0}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {2450525D-627D-4ABA-ACF0-FA1AA76CE4A0}.Debug|x64.ActiveCfg = Debug|x64 52 | {2450525D-627D-4ABA-ACF0-FA1AA76CE4A0}.Debug|x86.ActiveCfg = Debug|x86 53 | {2450525D-627D-4ABA-ACF0-FA1AA76CE4A0}.Debug|x86.Build.0 = Debug|x86 54 | {2450525D-627D-4ABA-ACF0-FA1AA76CE4A0}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {2450525D-627D-4ABA-ACF0-FA1AA76CE4A0}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {2450525D-627D-4ABA-ACF0-FA1AA76CE4A0}.Release|x64.ActiveCfg = Release|x64 57 | {2450525D-627D-4ABA-ACF0-FA1AA76CE4A0}.Release|x64.Build.0 = Release|x64 58 | {2450525D-627D-4ABA-ACF0-FA1AA76CE4A0}.Release|x86.ActiveCfg = Release|x86 59 | {2450525D-627D-4ABA-ACF0-FA1AA76CE4A0}.Release|x86.Build.0 = Release|x86 60 | {2965C7BF-B5DD-4345-9DFD-4E6B7D93DC0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 61 | {2965C7BF-B5DD-4345-9DFD-4E6B7D93DC0A}.Debug|Any CPU.Build.0 = Debug|Any CPU 62 | {2965C7BF-B5DD-4345-9DFD-4E6B7D93DC0A}.Debug|x64.ActiveCfg = Debug|x64 63 | {2965C7BF-B5DD-4345-9DFD-4E6B7D93DC0A}.Debug|x86.ActiveCfg = Debug|x86 64 | {2965C7BF-B5DD-4345-9DFD-4E6B7D93DC0A}.Debug|x86.Build.0 = Debug|x86 65 | {2965C7BF-B5DD-4345-9DFD-4E6B7D93DC0A}.Release|Any CPU.ActiveCfg = Release|Any CPU 66 | {2965C7BF-B5DD-4345-9DFD-4E6B7D93DC0A}.Release|Any CPU.Build.0 = Release|Any CPU 67 | {2965C7BF-B5DD-4345-9DFD-4E6B7D93DC0A}.Release|x64.ActiveCfg = Release|x64 68 | {2965C7BF-B5DD-4345-9DFD-4E6B7D93DC0A}.Release|x64.Build.0 = Release|x64 69 | {2965C7BF-B5DD-4345-9DFD-4E6B7D93DC0A}.Release|x86.ActiveCfg = Release|x86 70 | {2965C7BF-B5DD-4345-9DFD-4E6B7D93DC0A}.Release|x86.Build.0 = Release|x86 71 | EndGlobalSection 72 | GlobalSection(SolutionProperties) = preSolution 73 | HideSolutionNode = FALSE 74 | EndGlobalSection 75 | GlobalSection(NestedProjects) = preSolution 76 | {D117800F-072D-4AE4-9679-3E2A129A1A3C} = {FD3B44E6-66B3-4C58-B590-FB11B9CF6FF7} 77 | {2450525D-627D-4ABA-ACF0-FA1AA76CE4A0} = {FD3B44E6-66B3-4C58-B590-FB11B9CF6FF7} 78 | {2965C7BF-B5DD-4345-9DFD-4E6B7D93DC0A} = {FD3B44E6-66B3-4C58-B590-FB11B9CF6FF7} 79 | EndGlobalSection 80 | GlobalSection(ExtensibilityGlobals) = postSolution 81 | SolutionGuid = {2FBAFFCF-2E42-43DD-B5D2-EFB906701CBF} 82 | EndGlobalSection 83 | EndGlobal 84 | -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/API/Internals.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace ProcessMemoryDataFinder.API 6 | { 7 | internal class X64MemoryProcessAddressFinder : MemoryProcessAddressFinder 8 | { 9 | protected override IntPtr SumIntPtrs(IntPtr first, IntPtr second) => 10 | new IntPtr(first.ToInt64() + second.ToInt64()); 11 | } 12 | internal class X86ProcessX86RuntimeAddressFinder : MemoryProcessAddressFinder 13 | { 14 | protected override IntPtr SumIntPtrs(IntPtr first, IntPtr second) 15 | => IntPtr.Add(first, second.ToInt32()); 16 | } 17 | 18 | internal class X86ProcessX64RuntimeAddressFinder : MemoryProcessAddressFinder 19 | { 20 | protected override IntPtr SumIntPtrs(IntPtr first, IntPtr second) 21 | => new IntPtr(first.ToInt64() + second.ToInt64()); 22 | } 23 | 24 | internal abstract class MemoryProcessAddressFinder 25 | { 26 | public int IntPtrSize { get; set; } = IntPtr.Size; 27 | 28 | [StructLayout(LayoutKind.Sequential)] 29 | // ReSharper disable once InconsistentNaming 30 | public struct MEMORY_BASIC_INFORMATION 31 | { 32 | public IntPtr BaseAddress; 33 | public IntPtr AllocationBase; 34 | public uint AllocationProtect; 35 | public IntPtr RegionSize; 36 | public uint State; 37 | public uint Protect; 38 | public uint Type; 39 | } 40 | 41 | [DllImport("kernel32.dll", SetLastError = true)] 42 | protected static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, int dwLength); 43 | 44 | /// 45 | /// Finds process fragmented memory information 46 | /// 47 | /// process handle 48 | public List MemInfo(IntPtr pHandle) 49 | { 50 | IntPtr addy = new IntPtr(); 51 | var result = new List(); 52 | 53 | while (true) 54 | { 55 | MEMORY_BASIC_INFORMATION memInfo = new MEMORY_BASIC_INFORMATION(); 56 | int memDump = VirtualQueryEx(pHandle, addy, out memInfo, Marshal.SizeOf(memInfo)); 57 | if (memDump == 0) break; 58 | if ((memInfo.State & 0x1000) != 0 && (memInfo.Protect & 0x100) == 0) 59 | result.Add(memInfo); 60 | 61 | addy = SumIntPtrs(memInfo.BaseAddress, memInfo.RegionSize); 62 | } 63 | 64 | return result; 65 | } 66 | 67 | protected abstract IntPtr SumIntPtrs(IntPtr first, IntPtr second); 68 | } 69 | } -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/API/MemoryReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Diagnostics; 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.Linq; 7 | using System.Runtime.InteropServices; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using ProcessMemoryDataFinder.Misc; 11 | 12 | namespace ProcessMemoryDataFinder.API 13 | { 14 | public class MemoryReader : IDisposable 15 | { 16 | public delegate IntPtr FindPatternF(byte[] btPattern, string strMask, int nOffset, bool useMask); 17 | public delegate byte[] ReadDataF(IntPtr adress, uint size); 18 | 19 | private MemoryProcessAddressFinder _internals; 20 | private readonly ProcessTargetOptions _processTargetOptions; 21 | private readonly ProcessMemoryReader _reader = new ProcessMemoryReader(); 22 | private readonly SigScan _sigScan = new SigScan(); 23 | private Process _currentProcess; 24 | private Task ProcessWatcher; 25 | public int ProcessWatcherDelayMs { get; set; } = 1000; 26 | private CancellationTokenSource cts = new CancellationTokenSource(); 27 | private int _intPtrSize = IntPtr.Size; 28 | public event EventHandler ProcessChanged; 29 | protected virtual IntPtr CurrentProcessHandle { get; set; } = IntPtr.Zero; 30 | public virtual Process CurrentProcess 31 | { 32 | get => _currentProcess; 33 | private set 34 | { 35 | _currentProcess = value; 36 | _sigScan.Process = value; 37 | _reader.ReadProcess = value; 38 | ProcessChanged?.Invoke(null, EventArgs.Empty); 39 | _reader.OpenProcess(); 40 | try 41 | { 42 | CurrentProcessHandle = value?.Handle ?? IntPtr.Zero; 43 | } 44 | catch (InvalidOperationException) 45 | { 46 | CurrentProcessHandle = IntPtr.Zero; 47 | } 48 | } 49 | } 50 | 51 | public int IntPtrSize 52 | { 53 | get => _intPtrSize; 54 | set 55 | { 56 | _intPtrSize = value; 57 | if (value == 4) 58 | { 59 | if (Environment.Is64BitProcess) 60 | _internals = new X86ProcessX64RuntimeAddressFinder(); 61 | else 62 | _internals = new X86ProcessX86RuntimeAddressFinder(); 63 | } 64 | else 65 | _internals = new X64MemoryProcessAddressFinder(); 66 | } 67 | } 68 | 69 | public MemoryReader(ProcessTargetOptions processTargetOptions) 70 | { 71 | //Initialize process address finder 72 | IntPtrSize = IntPtrSize; 73 | 74 | _processTargetOptions = processTargetOptions; 75 | ProcessWatcher = Task.Run(MonitorProcess, cts.Token); 76 | } 77 | 78 | protected async Task MonitorProcess() 79 | { 80 | while (true) 81 | { 82 | if (cts.IsCancellationRequested) 83 | return; 84 | 85 | if (CurrentProcess == null || CurrentProcess.SafeHasExited()) 86 | { 87 | OpenProcess(); 88 | } 89 | if (CurrentProcess != null) 90 | { 91 | while (!CurrentProcess.WaitForExit(1000)) 92 | { 93 | if (cts.IsCancellationRequested) 94 | return; 95 | } 96 | 97 | CurrentProcess = null; 98 | } 99 | else 100 | await Task.Delay(ProcessWatcherDelayMs); 101 | } 102 | } 103 | 104 | 105 | public IntPtr FindPattern(byte[] btPattern, string strMask, int nOffset, bool useMask) 106 | { 107 | if (CurrentProcess == null) 108 | return IntPtr.Zero; 109 | 110 | var pageExecuteRead = (uint)MemoryProtectionOptions.PAGE_EXECUTE_READ; 111 | IntPtr result; 112 | foreach (var memoryAdress in GetMemoryAddresses()) 113 | { 114 | if ((memoryAdress.Protect & pageExecuteRead) != 0) 115 | { 116 | continue; 117 | } 118 | 119 | _sigScan.ResetRegion(); 120 | _sigScan.Address = memoryAdress.BaseAddress; 121 | _sigScan.Size = (int)memoryAdress.RegionSize; 122 | if (useMask) 123 | { 124 | result = _sigScan.FindPattern(btPattern, strMask, nOffset); 125 | } 126 | else 127 | { 128 | result = _sigScan.FindPattern(btPattern, nOffset); 129 | } 130 | 131 | if (result != IntPtr.Zero) 132 | { 133 | return result; 134 | } 135 | } 136 | 137 | return IntPtr.Zero; 138 | } 139 | 140 | public byte[] ReadData(IntPtr address, uint size) 141 | { 142 | if (address == IntPtr.Zero || CurrentProcess == null) 143 | { 144 | return null; 145 | } 146 | 147 | var bytesRead = 148 | _reader.ReadProcessMemory(address, size, 149 | out var bytesNumber); 150 | if (bytesNumber == size) 151 | { 152 | return bytesRead; 153 | } 154 | 155 | return null; 156 | } 157 | 158 | private void OpenProcess() 159 | { 160 | try 161 | { 162 | IEnumerable processes = Process.GetProcessesByName(_processTargetOptions.ProcessName); 163 | 164 | if (!string.IsNullOrEmpty(_processTargetOptions.MainWindowTitleHint)) 165 | { 166 | processes = processes.Where(process => process.MainWindowTitle.IndexOf(_processTargetOptions.MainWindowTitleHint, StringComparison.Ordinal) >= 0); 167 | } 168 | 169 | if (_processTargetOptions.Target64Bit.HasValue) 170 | { 171 | if (_processTargetOptions.Target64Bit.Value) 172 | { 173 | processes = processes.Where(process => IsWow64Process(process.SafeHandle, out var isWow64Process) && !isWow64Process); 174 | } 175 | else 176 | { 177 | processes = processes.Where(process => IsWow64Process(process.SafeHandle, out var isWow64Process) && isWow64Process); 178 | } 179 | } 180 | 181 | var resolvedProcess = processes.FirstOrDefault(); 182 | 183 | if (resolvedProcess is not null || CurrentProcess is not null) 184 | { 185 | CurrentProcess = resolvedProcess; 186 | } 187 | } 188 | catch (Win32Exception) 189 | { 190 | CurrentProcess = null; 191 | } 192 | } 193 | 194 | private IEnumerable GetMemoryAddresses() 195 | { 196 | var memInfoList = _internals.MemInfo(CurrentProcessHandle); 197 | 198 | foreach (var memoryInfo in memInfoList) 199 | { 200 | yield return memoryInfo; 201 | } 202 | } 203 | 204 | [SuppressMessage("ReSharper", "InconsistentNaming")] 205 | [Flags] 206 | private enum MemoryProtectionOptions 207 | { 208 | PAGE_EXECUTE = 0x10, 209 | PAGE_EXECUTE_READ = 0x20, 210 | PAGE_EXECUTE_READWRITE = 0x40, 211 | PAGE_EXECUTE_WRITECOPY = 0x80, 212 | PAGE_NOACCESS = 0x01, 213 | PAGE_READONLY = 0x02, 214 | PAGE_READWRITE = 0x04, 215 | PAGE_WRITECOPY = 0x08, 216 | PAGE_TARGETS_INVALID = 0x40000000, 217 | PAGE_TARGETS_NO_UPDATE = 0x40000000 218 | } 219 | 220 | protected virtual void Dispose(bool disposing) 221 | { 222 | if (disposing) 223 | { 224 | cts?.Cancel(); 225 | 226 | ProcessWatcher?.Dispose(); 227 | _currentProcess?.Dispose(); 228 | cts?.Dispose(); 229 | } 230 | } 231 | 232 | public void Dispose() 233 | { 234 | Dispose(true); 235 | GC.SuppressFinalize(this); 236 | } 237 | 238 | [DllImport("kernel32.dll", SetLastError = true)] 239 | private static extern bool IsWow64Process( 240 | [In] Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid hProcess, 241 | [Out, MarshalAs(UnmanagedType.Bool)] out bool wow64Process 242 | ); 243 | } 244 | } -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/API/ProcessMemoryReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | using System.Runtime.InteropServices; 5 | using ProcessMemoryDataFinder.Misc; 6 | 7 | namespace ProcessMemoryDataFinder.API 8 | { 9 | internal class ProcessMemoryReader 10 | { 11 | private Process _mReadProcess; 12 | public IntPtr m_hProcess = IntPtr.Zero; 13 | 14 | private Process m_ReadProcess 15 | { 16 | get => _mReadProcess; 17 | set 18 | { 19 | _mReadProcess = value; 20 | if (m_hProcess != IntPtr.Zero) 21 | CloseHandle(); 22 | m_hProcess = IntPtr.Zero; 23 | } 24 | } 25 | 26 | public Process ReadProcess 27 | { 28 | get => m_ReadProcess; 29 | set => m_ReadProcess = value; 30 | } 31 | 32 | /// 33 | /// Closes the process handle if it was open. 34 | /// 35 | /// CloseHandle failed 36 | public void CloseHandle() 37 | { 38 | if (ProcessMemoryReaderApi.CloseHandle(m_hProcess) == 0) throw new Exception("CloseHandle failed"); 39 | } 40 | 41 | /// 42 | /// 43 | /// IntPtr of the opened process or IntPtr. Zero if open failed. 44 | public IntPtr OpenProcess() 45 | { 46 | if (m_ReadProcess != null) 47 | if (!_mReadProcess.SafeHasExited() && m_hProcess == IntPtr.Zero) 48 | try 49 | { 50 | m_hProcess = ProcessMemoryReaderApi.OpenProcess(0x38, 1, (uint) m_ReadProcess.Id); 51 | } 52 | catch (Win32Exception) 53 | { 54 | // Most likely "Access denied" 55 | //TODO: stop swallowing this exception and throw it like a man(and handle it elsewhere as necessary) 56 | return IntPtr.Zero; 57 | } 58 | 59 | return m_hProcess; 60 | } 61 | 62 | /// 63 | /// Reads the process memory. 64 | /// 65 | /// The memory address. 66 | /// The bytes to read. 67 | /// The bytes read. 68 | /// 69 | /// 70 | public byte[] ReadProcessMemory(IntPtr memoryAddress, uint bytesToRead, out uint bytesRead) 71 | { 72 | try 73 | { 74 | if (m_hProcess == IntPtr.Zero) 75 | { 76 | bytesRead = 0; 77 | return null; 78 | } 79 | 80 | var buffer = new byte[bytesToRead]; 81 | if ( 82 | ProcessMemoryReaderApi.ReadProcessMemory(m_hProcess, memoryAddress, buffer, bytesToRead, 83 | out var lpNumberOfBytesRead) == 0) 84 | { 85 | bytesRead = 0; 86 | return null; 87 | } 88 | 89 | // lpNumberOfBytesRead is an IntPtr here so technically if we are 32bit platform we should be calling ToInt32() 90 | // but that doesnt matter here since we just want a uint value, which on a 32bit platform will work just fine (just tiny bit of overhead of first going to a int64) 91 | // and on a 64bit platform we dont have to worry about the value being larger then uint max, since we gave it a uint bytesToRead 92 | // so this cast should never fail 93 | bytesRead = (uint)lpNumberOfBytesRead.ToInt64(); 94 | return buffer; 95 | } 96 | catch 97 | { 98 | bytesRead = 0; 99 | return null; 100 | } 101 | } 102 | 103 | private class ProcessMemoryReaderApi 104 | { 105 | public const uint PROCESS_VM_OPERATION = 8; 106 | public const uint PROCESS_VM_READ = 0x10; 107 | public const uint PROCESS_VM_WRITE = 0x20; 108 | 109 | [DllImport("kernel32.dll")] 110 | public static extern int CloseHandle(IntPtr hObject); 111 | 112 | [DllImport("kernel32.dll")] 113 | public static extern IntPtr OpenProcess(uint dwDesiredAccess, int bInheritHandle, uint dwProcessId); 114 | 115 | [DllImport("kernel32.dll", SetLastError = true)] 116 | public static extern int ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [In] [Out] byte[] buffer, 117 | uint size, out IntPtr lpNumberOfBytesRead); 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/API/Sig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace ProcessMemoryDataFinder.API 7 | { 8 | public class Sig 9 | { 10 | /// 11 | /// Address found by pattern searcher + offset 12 | /// 13 | public IntPtr Address = IntPtr.Zero; 14 | 15 | public string Mask; 16 | public int Offset; 17 | public byte[] Pattern; 18 | } 19 | 20 | public class SigEx : Sig 21 | { 22 | private MemoryReader.FindPatternF _findPatternFunc; 23 | private MemoryReader.ReadDataF _readDataFunc; 24 | private IObjectReader _objectReader; 25 | 26 | public string Name { get; set; } 27 | /// 28 | /// Final address - after traversing pointers. 29 | /// 30 | private IntPtr _resolvedAddress = IntPtr.Zero; 31 | 32 | /// 33 | /// Resolved address of parent will be used instead of pattern. 34 | /// 35 | public SigEx ParentSig; 36 | 37 | 38 | /// 39 | /// If false, won't be used to find specified address and much 40 | /// quicker way of scanning memory will be used. 41 | /// 42 | public bool UseMask { get; set; } = true; 43 | 44 | /// 45 | /// Pointer offsets needed to resolve final address 46 | /// 47 | public List PointerOffsets { get; set; } = new List(); 48 | 49 | public void SetFindPatternF(MemoryReader.FindPatternF f) 50 | { 51 | _findPatternFunc = f; 52 | ParentSig?.SetFindPatternF(f); 53 | } 54 | 55 | public void SetReadDataF(MemoryReader.ReadDataF f) 56 | { 57 | _readDataFunc = f; 58 | ParentSig?.SetReadDataF(f); 59 | } 60 | 61 | public void SetObjectReader(IObjectReader objectReader) 62 | { 63 | _objectReader = objectReader; 64 | ParentSig?.SetObjectReader(objectReader); 65 | } 66 | 67 | /// 68 | /// Resets this instance tree. 69 | /// 70 | public void Reset() 71 | { 72 | Address = IntPtr.Zero; 73 | _resolvedAddress = IntPtr.Zero; 74 | ParentSig?.Reset(); 75 | } 76 | 77 | /// 78 | /// Resets the final resolved address of this tree, forcing signatures to re-traverse pointers. 79 | /// 80 | public void ResetPointer() 81 | { 82 | _resolvedAddress = IntPtr.Zero; 83 | ParentSig?.ResetPointer(); 84 | } 85 | 86 | protected IntPtr ResolveAddress() 87 | { 88 | if (_resolvedAddress != IntPtr.Zero) 89 | return _resolvedAddress; 90 | var addr = IntPtr.Zero; 91 | if (ParentSig != null) 92 | { 93 | addr = ParentSig.ResolveAddress(); 94 | addr += Offset; 95 | } 96 | 97 | if (Pattern != null) 98 | { 99 | if (Address != IntPtr.Zero) 100 | { 101 | addr = Address; 102 | } 103 | else 104 | { 105 | try 106 | { 107 | Address = _findPatternFunc(Pattern, Mask, Offset, UseMask); 108 | } 109 | catch (InvalidOperationException) 110 | { 111 | return IntPtr.Zero; 112 | } 113 | 114 | addr = Address; 115 | } 116 | } 117 | 118 | if (addr != IntPtr.Zero) _resolvedAddress = ResolveChainOfPointers(addr); 119 | return _resolvedAddress; 120 | } 121 | 122 | private IntPtr ResolveChainOfPointers(IntPtr baseAddress) 123 | { 124 | if (PointerOffsets?.Count == 0) return baseAddress; 125 | 126 | var pointer = baseAddress; 127 | pointer = ReadPointer(pointer); 128 | 129 | for (var i = 0; i < PointerOffsets.Count - 1; i++) 130 | { 131 | var offset = PointerOffsets[i]; 132 | pointer = ReadPointer(pointer + offset); 133 | } 134 | 135 | pointer = pointer + PointerOffsets[PointerOffsets.Count - 1]; 136 | return pointer; 137 | } 138 | 139 | private byte[] GetValue(uint size) 140 | { 141 | var address = ResolveAddress(); 142 | return _readDataFunc(address, size); 143 | } 144 | 145 | private IntPtr ReadPointer(IntPtr baseAddr) => _objectReader.ReadPointer(baseAddr); 146 | 147 | public IntPtr GetPointer() => _objectReader.ReadPointer(ResolveAddress()); 148 | 149 | public bool GetBoolean() 150 | { 151 | var data = GetValue(1); 152 | if (data != null) 153 | return BitConverter.ToBoolean(data, 0); 154 | return false; 155 | } 156 | 157 | public int GetInt() 158 | { 159 | var data = GetValue(4); 160 | if (data != null) 161 | return BitConverter.ToInt32(data, 0); 162 | return -1; 163 | } 164 | 165 | public ushort GetUShort() 166 | { 167 | var data = GetValue(2); 168 | if (data != null) 169 | return BitConverter.ToUInt16(data, 0); 170 | return 0; 171 | } 172 | 173 | public byte GetByte() 174 | { 175 | var data = GetValue(1); 176 | if (data != null) 177 | return data[0]; 178 | return 0; 179 | } 180 | 181 | public double GetDouble() 182 | { 183 | var data = GetValue(8); 184 | if (data != null) 185 | return BitConverter.ToDouble(data, 0); 186 | return 0; 187 | } 188 | 189 | public float GetFloat() 190 | { 191 | var data = GetValue(4); 192 | if (data != null) 193 | return BitConverter.ToSingle(data, 0); 194 | return -1; 195 | } 196 | 197 | public List GetIntList() => _objectReader.ReadIntList(ResolveAddress()); 198 | 199 | public int[] GetIntArray() => _objectReader.ReadIntArray(ResolveAddress()); 200 | 201 | public string GetString() => _objectReader.ReadUnicodeString(ResolveAddress()); 202 | 203 | public override string ToString() 204 | { 205 | return 206 | $"Name:\"{Name}\", Pattern:\"{string.Join("", Pattern.Select(x => x.ToString("x2")))}\", " + 207 | $"Mask:\"{Mask}\", Offsets: \"{string.Join(",", PointerOffsets.Select(x => x.ToString()))}\", Parent: \"{ParentSig}\""; 208 | } 209 | } 210 | } -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/API/SigMemoryReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ProcessMemoryDataFinder.API 5 | { 6 | public abstract class SigMemoryReader : MemoryReader 7 | { 8 | protected Dictionary Signatures = new Dictionary(); 9 | protected IObjectReader ObjectReader; 10 | protected SigMemoryReader(ProcessTargetOptions processTargetOptions) : base(processTargetOptions) 11 | { 12 | ObjectReader = new ObjectReader(this); 13 | ProcessChanged += (_, __) => ResetAllSignatures(); 14 | } 15 | 16 | 17 | public void ResetAllSignatures() 18 | { 19 | foreach (var signature in Signatures) 20 | { 21 | signature.Value.Reset(); 22 | } 23 | } 24 | 25 | protected virtual void ResetPointer(int signatureId) 26 | { 27 | var sig = Signatures[signatureId]; 28 | sig.ResetPointer(); 29 | } 30 | 31 | protected virtual void Reset(int signatureId, bool fowardToParent = true) 32 | { 33 | var sig = Signatures[signatureId]; 34 | sig.Reset(); 35 | } 36 | 37 | protected virtual SigEx InitSignature(int signatureId) 38 | { 39 | if (!Signatures.ContainsKey(signatureId)) 40 | { 41 | throw new KeyNotFoundException(); 42 | } 43 | 44 | var sig = Signatures[signatureId]; 45 | sig.SetFindPatternF(FindPattern); 46 | sig.SetReadDataF(ReadData); 47 | sig.SetObjectReader(ObjectReader); 48 | return sig; 49 | } 50 | 51 | #region Signature readers 52 | 53 | protected virtual bool GetBoolean(int signatureId) 54 | { 55 | var sig = InitSignature(signatureId); 56 | return sig.GetBoolean(); 57 | } 58 | 59 | protected virtual int GetInt(int signatureId) 60 | { 61 | var sig = InitSignature(signatureId); 62 | return sig.GetInt(); 63 | } 64 | 65 | protected virtual float GetFloat(int signatureId) 66 | { 67 | var sig = InitSignature(signatureId); 68 | return sig.GetFloat(); 69 | } 70 | 71 | protected virtual IntPtr GetPointer(int signatureId) 72 | { 73 | var sig = InitSignature(signatureId); 74 | return sig.GetPointer(); 75 | } 76 | 77 | protected virtual ushort GetUShort(int signatureId) 78 | { 79 | var sig = InitSignature(signatureId); 80 | return sig.GetUShort(); 81 | } 82 | 83 | protected virtual byte GetByte(int signatureId) 84 | { 85 | var sig = InitSignature(signatureId); 86 | return sig.GetByte(); 87 | } 88 | 89 | protected virtual double GetDouble(int signatureId) 90 | { 91 | var sig = InitSignature(signatureId); 92 | return sig.GetDouble(); 93 | } 94 | 95 | protected virtual string GetString(int signatureId) 96 | { 97 | var sig = InitSignature(signatureId); 98 | return sig.GetString(); 99 | } 100 | 101 | 102 | protected virtual List GetIntList(int signatureId) 103 | { 104 | var sig = InitSignature(signatureId); 105 | return sig.GetIntList(); 106 | } 107 | 108 | protected virtual int[] GetIntArray(int signatureId) 109 | { 110 | var sig = InitSignature(signatureId); 111 | return sig.GetIntArray(); 112 | } 113 | 114 | #endregion 115 | } 116 | } -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/API/SigScan.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Runtime.InteropServices; 5 | using ProcessMemoryDataFinder.Misc; 6 | 7 | namespace ProcessMemoryDataFinder.API 8 | { 9 | //Slightly modified (by Piotrekol) signature scanner (added ability of scanning memory without need of mask usage) 10 | // 11 | //Original changelog and credits follow: 12 | // 13 | // sigScan C# Implementation - Written by atom0s [aka Wiccaan] 14 | // Class Version: 2.0.0 15 | // 16 | // [ CHANGE LOG ] ------------------------------------------------------------------------- 17 | // 18 | // 2.0.0 19 | // - Updated to no longer require unsafe or fixed code. 20 | // - Removed unneeded methods and code. 21 | // 22 | // 1.0.0 23 | // - First version written and release. 24 | // 25 | // [ CREDITS ] ---------------------------------------------------------------------------- 26 | // 27 | // sigScan is based on the FindPattern code written by 28 | // dom1n1k and Patrick at GameDeception.net 29 | // 30 | // Full credit to them for the purpose of this code. I, atom0s, simply 31 | // take credit for converting it to C#. 32 | internal class SigScan 33 | { 34 | /// 35 | /// ReadProcessMemory 36 | /// 37 | /// API import definition for ReadProcessMemory. 38 | /// 39 | /// Handle to the process we want to read from. 40 | /// The base address to start reading from. 41 | /// The return buffer to write the read data to. 42 | /// The size of data we wish to read. 43 | /// The number of bytes successfully read. 44 | /// 45 | [DllImport("kernel32.dll", SetLastError = true)] 46 | public static extern bool ReadProcessMemory( 47 | IntPtr hProcess, 48 | IntPtr lpBaseAddress, 49 | [Out] byte[] lpBuffer, 50 | int dwSize, 51 | out int lpNumberOfBytesRead 52 | ); 53 | 54 | /// 55 | /// m_vDumpedRegion 56 | /// 57 | /// The memory dumped from the external process. 58 | /// 59 | private byte[] m_vDumpedRegion; 60 | 61 | /// 62 | /// m_vProcess 63 | /// 64 | /// The process we want to read the memory of. 65 | /// 66 | private Process m_vProcess; 67 | 68 | /// 69 | /// m_vAddress 70 | /// 71 | /// The starting address we want to begin reading at. 72 | /// 73 | private IntPtr m_vAddress; 74 | 75 | /// 76 | /// m_vSize 77 | /// 78 | /// The number of bytes we wish to read from the process. 79 | /// 80 | private Int32 m_vSize; 81 | 82 | private List m_SigQueue; 83 | 84 | 85 | #region "sigScan Class Construction" 86 | /// 87 | /// SigScan 88 | /// 89 | /// Main class constructor that uses no params. 90 | /// Simply initializes the class properties and 91 | /// expects the user to set them later. 92 | /// 93 | public SigScan() 94 | { 95 | m_vProcess = null; 96 | m_vAddress = IntPtr.Zero; 97 | m_vSize = 0; 98 | m_vDumpedRegion = null; 99 | m_SigQueue = new List(); 100 | } 101 | /// 102 | /// SigScan 103 | /// 104 | /// Overloaded class constructor that sets the class 105 | /// properties during construction. 106 | /// 107 | /// The process to dump the memory from. 108 | /// The started address to begin the dump. 109 | /// The size of the dump. 110 | public SigScan(Process proc, IntPtr addr, int size) 111 | { 112 | m_vProcess = proc; 113 | m_vAddress = addr; 114 | m_vSize = size; 115 | m_SigQueue = new List(); 116 | } 117 | #endregion 118 | 119 | #region "sigScan Class Private Methods" 120 | 121 | /// 122 | /// DumpMemory 123 | /// 124 | /// Internal memory dump function that uses the set class 125 | /// properties to dump a memory region. 126 | /// 127 | /// Boolean based on RPM results and valid properties. 128 | private bool DumpMemory() 129 | { 130 | try 131 | { 132 | // Checks to ensure we have valid data. 133 | if (m_vProcess == null) 134 | return false; 135 | if (m_vProcess.SafeHasExited()) 136 | return false; 137 | if (m_vAddress == IntPtr.Zero) 138 | return false; 139 | if (m_vSize == 0) 140 | return false; 141 | 142 | // Create the region space to dump into. 143 | m_vDumpedRegion = new byte[m_vSize]; 144 | 145 | bool bReturn = false; 146 | int nBytesRead = 0; 147 | 148 | // Dump the memory. 149 | bReturn = ReadProcessMemory( 150 | m_vProcess.Handle, m_vAddress, m_vDumpedRegion, m_vSize, out nBytesRead 151 | ); 152 | 153 | // Validation checks. 154 | if (bReturn == false || nBytesRead != m_vSize) 155 | return false; 156 | return true; 157 | } 158 | catch (Exception) 159 | { 160 | return false; 161 | } 162 | } 163 | 164 | /// 165 | /// MaskCheck 166 | /// 167 | /// Compares the current pattern byte to the current memory dump 168 | /// byte to check for a match. Uses wildcards to skip bytes that 169 | /// are deemed unneeded in the compares. 170 | /// 171 | /// Offset in the dump to start at. 172 | /// Pattern to scan for. 173 | /// Mask to compare against. 174 | /// Boolean depending on if the pattern was found. 175 | private bool MaskCheck(int nOffset, byte[] btPattern, string strMask) 176 | { 177 | // Loop the pattern and compare to the mask and dump. 178 | for (int x = 0; x < btPattern.Length; x++) 179 | { 180 | // If the mask char is a wildcard, just continue. 181 | if (strMask[x] == '?') 182 | continue; 183 | 184 | // If the mask char is not a wildcard, ensure a match is made in the pattern. 185 | if ((strMask[x] == 'x') && (btPattern[x] != m_vDumpedRegion[nOffset + x])) 186 | return false; 187 | } 188 | 189 | // The loop was successful so we found the pattern. 190 | return true; 191 | } 192 | #endregion 193 | 194 | #region "sigScan Class Public Methods" 195 | /// 196 | /// FindPattern 197 | /// 198 | /// Attempts to locate the given pattern inside the dumped memory region 199 | /// compared against the given mask. If the pattern is found, the offset 200 | /// is added to the located address and returned to the user. 201 | /// 202 | /// Byte pattern to look for in the dumped region. 203 | /// The mask string to compare against. 204 | /// The offset added to the result address. 205 | /// IntPtr - zero if not found, address if found. 206 | public IntPtr FindPattern(byte[] btPattern, string strMask, int nOffset) 207 | { 208 | try 209 | { 210 | // Dump the memory region if we have not dumped it yet. 211 | if (m_vDumpedRegion == null || m_vDumpedRegion.Length == 0) 212 | { 213 | if (!DumpMemory()) 214 | return IntPtr.Zero; 215 | } 216 | 217 | // Ensure the mask and pattern lengths match. 218 | if (strMask.Length != btPattern.Length) 219 | return IntPtr.Zero; 220 | 221 | // Loop the region and look for the pattern. 222 | for (int x = 0; x < m_vDumpedRegion.Length; x++) 223 | { 224 | if (MaskCheck(x, btPattern, strMask)) 225 | { 226 | // The pattern was found, return it. 227 | return m_vAddress + (x + nOffset); 228 | } 229 | } 230 | 231 | // Pattern was not found. 232 | return IntPtr.Zero; 233 | } 234 | catch (Exception) 235 | { 236 | return IntPtr.Zero; 237 | } 238 | } 239 | 240 | /// 241 | /// FindPattern 242 | /// 243 | /// Attempts to locate all the patterns in SigQueue inside the dumped 244 | /// memory region compared against the given mask. If the pattern is 245 | /// found, the offset is added to the located address and the Sig's 246 | /// Address will be set. 247 | /// 248 | public void FindPattern() 249 | { 250 | try 251 | { 252 | // Dump the memory region if we have not dumped it yet. 253 | if (m_vDumpedRegion == null || m_vDumpedRegion.Length == 0) 254 | { 255 | if (!DumpMemory()) 256 | return; 257 | } 258 | 259 | // Ensure the mask and pattern lengths match. 260 | foreach (var sig in SigQueue) 261 | { 262 | if(sig.Mask.Length != sig.Pattern.Length) 263 | return; 264 | } 265 | 266 | 267 | // Loop the region and look for the patterns. 268 | for (int x = 0; x < m_vDumpedRegion.Length; x++) 269 | { 270 | foreach (var sig in m_SigQueue) 271 | { 272 | if (MaskCheck(x, sig.Pattern, sig.Mask)) 273 | { 274 | // The pattern was found, set it. 275 | sig.Address = m_vAddress + (x + sig.Offset); 276 | } 277 | } 278 | } 279 | 280 | } 281 | catch (Exception) 282 | { 283 | } 284 | } 285 | 286 | /// 287 | /// ResetRegion 288 | /// 289 | /// Resets the memory dump array to nothing to allow 290 | /// the class to redump the memory. 291 | /// 292 | public void ResetRegion() 293 | { 294 | m_vDumpedRegion = null; 295 | } 296 | #endregion 297 | 298 | #region "sigScan Class Properties" 299 | public Process Process 300 | { 301 | get { return m_vProcess; } 302 | set { m_vProcess = value; } 303 | } 304 | public IntPtr Address 305 | { 306 | get { return m_vAddress; } 307 | set { m_vAddress = value; } 308 | } 309 | public Int32 Size 310 | { 311 | get { return m_vSize; } 312 | set { m_vSize = value; } 313 | } 314 | public List SigQueue 315 | { 316 | get { return m_SigQueue; } 317 | set { m_SigQueue = value; } 318 | } 319 | #endregion 320 | /// 321 | /// Faster FindPattern implementation that doesn't use mask 322 | /// Instead of 4~5s scans with mask this can do same byte length scan in about half a second 323 | /// 324 | public IntPtr FindPattern(byte[] patternBytes, int nOffset) 325 | { 326 | // Dump the memory region if we have not dumped it yet. 327 | if (m_vDumpedRegion == null || m_vDumpedRegion.Length == 0) 328 | { 329 | if (!DumpMemory()) 330 | return IntPtr.Zero; 331 | } 332 | var result = Scan(m_vDumpedRegion, patternBytes); 333 | if (result == -1) 334 | return IntPtr.Zero; 335 | 336 | return m_vAddress + (nOffset + result); 337 | } 338 | 339 | private static int Scan(byte[] sIn, byte[] sFor) 340 | { 341 | if (sIn == null) 342 | { 343 | return -1; 344 | } 345 | 346 | int[] sBytes = new int[256]; 347 | int end = sFor.Length - 1; 348 | for (int i = 0; i < 256; i++) 349 | { 350 | sBytes[i] = sFor.Length; 351 | } 352 | 353 | for (int i = 0; i < end; i++) 354 | { 355 | sBytes[sFor[i]] = end - i; 356 | } 357 | 358 | int pool = 0; 359 | while (pool <= sIn.Length - sFor.Length) 360 | { 361 | for (int i = end; (sIn[pool + i] == sFor[i]); i--) 362 | { 363 | if (i == 0) 364 | { 365 | return pool; 366 | } 367 | } 368 | 369 | pool += sBytes[sIn[pool + end]]; 370 | } 371 | return -1; 372 | } 373 | } 374 | 375 | 376 | } -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/CompilerServices.cs: -------------------------------------------------------------------------------- 1 | #if NET48 2 | namespace System.Runtime.CompilerServices 3 | { 4 | [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] 5 | internal static class IsExternalInit { } 6 | } 7 | #endif -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/IntPtrExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace ProcessMemoryDataFinder 6 | { 7 | public static class IntPtrExtensions 8 | { 9 | public static long MaxValue = 0; 10 | 11 | private static uint x86IntPtrMax = 0x7fffffff; 12 | private static long x64IntPtrMax = 0x7fffffffffffffff; 13 | static IntPtrExtensions() 14 | { 15 | #if NET5_0_OR_GREATER 16 | MaxValue = IntPtr.MaxValue.ToInt64(); 17 | #else 18 | MaxValue = Environment.Is64BitProcess 19 | ? x64IntPtrMax 20 | : x86IntPtrMax; 21 | #endif 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/ObjectReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using ProcessMemoryDataFinder.API; 5 | 6 | namespace ProcessMemoryDataFinder 7 | { 8 | public interface IObjectReader 9 | { 10 | List ReadIntList(IntPtr baseAddress); 11 | List ReadUIntList(IntPtr baseAddress); 12 | int[] ReadIntArray(IntPtr baseAddress); 13 | string ReadUnicodeString(IntPtr baseAddress); 14 | byte[] ReadStringBytes(IntPtr baseAddress, int bytesPerCharacter = 2); 15 | IntPtr ReadPointer(IntPtr baseAddr); 16 | int IntPtrSize { get; set; } 17 | } 18 | 19 | public class ObjectReader : IObjectReader 20 | { 21 | private readonly MemoryReader _memoryReader; 22 | 23 | /// 24 | /// Size of pointers in searched process 25 | /// 26 | public int IntPtrSize { get; set; } = IntPtr.Size; 27 | 28 | private bool IsX64 => IntPtrSize == 8; 29 | 30 | public ObjectReader(MemoryReader memoryReader) 31 | { 32 | _memoryReader = memoryReader; 33 | } 34 | 35 | public List ReadUIntList(IntPtr baseAddress) 36 | { 37 | var readResult = ReadListBytes(baseAddress, 4); 38 | if (readResult.Bytes == null) return null; 39 | 40 | var list = new List(readResult.NumberOfElements); 41 | for (var offset = 0; offset < readResult.NumberOfElements * 4; offset += 4) 42 | { 43 | list.Add(BitConverter.ToUInt32(readResult.Bytes, offset)); 44 | } 45 | 46 | return list; 47 | } 48 | 49 | public List ReadIntList(IntPtr baseAddress) 50 | { 51 | var readResult = ReadListBytes(baseAddress, 4); 52 | if (readResult.Bytes == null) return null; 53 | 54 | var list = new List(readResult.NumberOfElements); 55 | for (var offset = 0; offset < readResult.NumberOfElements * 4; offset += 4) 56 | { 57 | list.Add(BitConverter.ToInt32(readResult.Bytes, offset)); 58 | } 59 | 60 | return list; 61 | } 62 | 63 | protected (int NumberOfElements, byte[] Bytes) ReadListBytes(IntPtr baseAddress, int entrySize) 64 | { 65 | var (numberOfElements, firstElementPtr) = GetArrayLikeHeader(true, false, baseAddress); 66 | if (numberOfElements < 0) return (-1, null); 67 | if (numberOfElements == 0) return (0, Array.Empty()); 68 | 69 | var totalByteCount = entrySize * numberOfElements; 70 | var bytes = _memoryReader.ReadData(firstElementPtr, (uint)totalByteCount); 71 | if (bytes == null || bytes.Length != totalByteCount) return (-1, null); 72 | 73 | return (numberOfElements, bytes); 74 | } 75 | 76 | public int[] ReadIntArray(IntPtr baseAddress) 77 | { 78 | var (numberOfElements, firstElementPtr) = GetArrayLikeHeader(false, false, baseAddress); 79 | if (numberOfElements < 0) return null; 80 | if (numberOfElements == 0) return Array.Empty(); 81 | 82 | var totalByteCount = 4 * numberOfElements; 83 | var bytes = _memoryReader.ReadData(firstElementPtr, (uint)totalByteCount); 84 | if (bytes == null || bytes.Length != totalByteCount) return null; 85 | 86 | var arr = new int[numberOfElements]; 87 | for (int offset = 0, i = 0; i < numberOfElements; offset += 4, ++i) 88 | { 89 | arr[i] = BitConverter.ToInt32(bytes, offset); 90 | } 91 | 92 | return arr; 93 | } 94 | 95 | public string ReadUnicodeString(IntPtr baseAddress) 96 | { 97 | var bytes = ReadStringBytes(baseAddress); 98 | if (bytes == null) return null; 99 | if (bytes.Length == 0) return string.Empty; 100 | 101 | return Encoding.Unicode.GetString(bytes); 102 | } 103 | 104 | public byte[] ReadStringBytes(IntPtr baseAddress, int bytesPerCharacter = 2) 105 | { 106 | var (numberOfElements, firstElementPtr) = GetArrayLikeHeader(false, true, baseAddress); 107 | if (numberOfElements <= 0) return null; 108 | 109 | /* 110 | * If there's a case where string can be longer than 2^18, this should get adjusted to bigger value. 111 | * In my testing in StreamCompanion(across whole userbase) this value can be [incorrectly] set to ridiculous value, sometimes even causing OutOfMemoryException 112 | */ 113 | if (numberOfElements > 262144) return null; 114 | 115 | var totalByteCount = bytesPerCharacter * numberOfElements; 116 | var bytes = _memoryReader.ReadData(firstElementPtr, (uint)totalByteCount); 117 | if (bytes == null || bytes.Length != totalByteCount) return null; 118 | 119 | return bytes; //Encoding.Unicode.GetString(bytes); 120 | } 121 | 122 | /// 123 | /// Get the number of elements and a pointer to the first element of an array like structure, supported structures at the moment are: a simple array, a List object, and a string. 124 | /// 125 | /// An string and a simple array are very similar but every so slightly different (the size of the number of elements fields) 126 | /// A List object has its own number of elements field and an internal array containing the elements itself 127 | /// 128 | /// 129 | /// 130 | /// 131 | /// 132 | protected (int numberOfElements, IntPtr firstElementPtr) GetArrayLikeHeader(bool isList, bool isString, 133 | IntPtr baseAddress) 134 | { 135 | //var pointer = ReadPointer(baseAddress); 136 | //if (pointer == IntPtr.Zero) return (-1, IntPtr.Zero); 137 | 138 | var address = ReadPointer(baseAddress); 139 | if (address == IntPtr.Zero) return (-1, IntPtr.Zero); 140 | 141 | int numberOfElements; 142 | IntPtr firstElementPtr, numberOfElementsAddr; 143 | 144 | if (isList) 145 | { 146 | // its a list, not an array, read the length from the list object and get the firstElementPtr from the internal array 147 | numberOfElementsAddr = address + 3 * IntPtrSize; //(IsX64 ? 0x18 : 0x0C); 148 | 149 | // in a list, regardless of platform, the size element is always 4 bytes 150 | var numberOfElementBytes = _memoryReader.ReadData(numberOfElementsAddr, 4); 151 | if (numberOfElementBytes == null || numberOfElementBytes.Length != 4) return (-1, IntPtr.Zero); 152 | numberOfElements = BitConverter.ToInt32(numberOfElementBytes, 0); 153 | 154 | // lets point to the first element in the internal array: 155 | // 1. skip VTable of list structure (4 or 8 bytes depending on platform) 156 | // 2. resolve pinter to internal array 157 | // 3. skip VTable and internal number of elements (both 4 or 8 bytes depending on platform) 158 | var internalArray = ReadPointer(address + IntPtrSize); 159 | firstElementPtr = internalArray + 2 * IntPtrSize;// IsX64 ? internalArray +16 : internalArray + 8 160 | } 161 | else 162 | { 163 | // normal array, first element in the structure is the length, skip VTable 164 | numberOfElementsAddr = address + IntPtrSize; 165 | 166 | // in an array structure the size of of the numberOfElements field depends on the platform (4 bytes or 8 bytes) 167 | // except for a string, then its always 4 bytes 168 | // first element in the array is after the numberOfElements field 169 | if (IsX64) 170 | { 171 | // 64bit platform, unless string size bytes is 8 172 | if (isString) 173 | { 174 | var numberOfElementBytes = _memoryReader.ReadData(numberOfElementsAddr, 4); 175 | if (numberOfElementBytes == null || numberOfElementBytes.Length != 4) return (-1, IntPtr.Zero); 176 | numberOfElements = BitConverter.ToInt32(numberOfElementBytes, 0); 177 | firstElementPtr = numberOfElementsAddr + 4; 178 | } 179 | else 180 | { 181 | var numberOfElementBytes = _memoryReader.ReadData(numberOfElementsAddr, 8); 182 | if (numberOfElementBytes == null || numberOfElementBytes.Length != 8) return (-1, IntPtr.Zero); 183 | 184 | var numberOfElementsLong = BitConverter.ToInt64(numberOfElementBytes, 0); 185 | // ok, realistically we're probably never gonna get an array of more then 2^32-1 elements, so lets cast this down to an int 186 | if (numberOfElementsLong > int.MaxValue) 187 | { 188 | // unable to read, lets just return nothing 189 | return (-1, IntPtr.Zero); 190 | } 191 | numberOfElements = (int)numberOfElementsLong; 192 | firstElementPtr = numberOfElementsAddr + 8; 193 | } 194 | } 195 | else 196 | { 197 | // 32bit platform, its always 4 bytes, regardless of the value of isString 198 | var numberOfElementBytes = _memoryReader.ReadData(numberOfElementsAddr, 4); 199 | if (numberOfElementBytes == null || numberOfElementBytes.Length != 4) return (-1, IntPtr.Zero); 200 | numberOfElements = BitConverter.ToInt32(numberOfElementBytes, 0); 201 | firstElementPtr = numberOfElementsAddr + 4; 202 | } 203 | } 204 | 205 | return (numberOfElements, firstElementPtr); 206 | } 207 | 208 | public IntPtr ReadPointer(IntPtr baseAddr) 209 | { 210 | var data = _memoryReader.ReadData(baseAddr, (uint)IntPtrSize); 211 | if (data == null) 212 | return IntPtr.Zero; 213 | 214 | if (IsX64) 215 | return new IntPtr(BitConverter.ToInt64(data, 0)); 216 | 217 | var rawPtr = BitConverter.ToUInt32(data, 0); 218 | if (rawPtr > IntPtrExtensions.MaxValue) 219 | return IntPtr.Zero; 220 | 221 | return new IntPtr(rawPtr); 222 | } 223 | } 224 | } -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/PatternHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ProcessMemoryDataFinder 4 | { 5 | public static class PatternHelpers 6 | { 7 | public static byte[] UnpackStr(string str) 8 | { 9 | return StringToBytePatternArray(str).Bytes; 10 | } 11 | 12 | public static (byte[] Bytes, string Mask) StringToBytePatternArray(string hex) 13 | { 14 | var numberChars = hex.Length; 15 | var mask = string.Empty; 16 | var bytes = new byte[numberChars / 2]; 17 | for (var i = 0; i < numberChars; i += 2) 18 | { 19 | var substring = hex.Substring(i, 2); 20 | if (substring == "??") 21 | { 22 | bytes[i / 2] = 0; 23 | mask += "?"; 24 | } 25 | else 26 | { 27 | bytes[i / 2] = Convert.ToByte(substring, 16); 28 | mask += "x"; 29 | } 30 | } 31 | 32 | return (bytes, mask); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/ProcessExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace ProcessMemoryDataFinder.Misc 4 | { 5 | internal static class ProcessExtensions 6 | { 7 | internal static bool SafeHasExited(this Process process) 8 | { 9 | 10 | try 11 | { 12 | return process.HasExited; 13 | } 14 | catch 15 | { 16 | return true; 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/ProcessMemoryDataFinder.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net48;net6.0;net8.0 4 | x64;x86;AnyCPU 5 | ProcessMemoryDataFinder 6 | ProcessMemoryDataFinder 7 | 2019-2020 Piotr Partyka 8 | true 9 | Piotrekol 10 | ProcessMemoryDataFinder 11 | Find and read data in processes based on pre-made memory signatures(patterns) 12 | GPL-3.0-or-later 13 | https://github.com/Piotrekol/ProcessMemoryDataFinder/tree/master/ProcessMemoryDataFinder 14 | https://github.com/Piotrekol/ProcessMemoryDataFinder/tree/master/ProcessMemoryDataFinder 15 | ProcessMemoryDataFinder 16 | Find and read data in processes based on pre-made memory signatures(patterns) 17 | 0.9.0 18 | true 19 | snupkg 20 | latest 21 | 22 | 23 | 24 | true 25 | GPL-3.0-or-later 26 | 27 | 28 | bin\x64\Debug\ 29 | TRACE;DEBUG;x64 30 | MinimumRecommendedRules.ruleset 31 | 32 | 33 | bin\AnyCPU\Debug\ 34 | TRACE;DEBUG;x64 35 | MinimumRecommendedRules.ruleset 36 | 37 | 38 | bin\x64\Release\ 39 | TRACE;x64 40 | MinimumRecommendedRules.ruleset 41 | 42 | 43 | bin\AnyCPU\Release\ 44 | TRACE;x64 45 | MinimumRecommendedRules.ruleset 46 | 47 | 48 | bin\x86\Debug\ 49 | MinimumRecommendedRules.ruleset 50 | 51 | 52 | bin\x86\Release\ 53 | MinimumRecommendedRules.ruleset 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/ProcessTargetOptions.cs: -------------------------------------------------------------------------------- 1 | namespace ProcessMemoryDataFinder; 2 | 3 | /// 4 | /// 5 | /// 6 | /// Target process name, without extension. 7 | /// Target main process window title to target. 8 | /// Whenever memory reader should target x64 processes or x86. Setting this to null will skip this check. 9 | public record ProcessTargetOptions( 10 | string ProcessName, 11 | string MainWindowTitleHint = null, 12 | bool? Target64Bit = false); -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // Setting ComVisible to false makes the types in this assembly not visible 5 | // to COM components. If you need to access a type in this assembly from 6 | // COM, set the ComVisible attribute to true on that type. 7 | [assembly: ComVisible(false)] 8 | 9 | // The following GUID is for the ID of the typelib if this project is exposed to COM 10 | [assembly: Guid("9960d641-300a-432c-aa04-c351a99b9adb")] 11 | -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/Structured/AddressFinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using ProcessMemoryDataFinder.API; 4 | using ProcessMemoryDataFinder.Structured.Tokenizer; 5 | 6 | namespace ProcessMemoryDataFinder.Structured 7 | { 8 | public class AddressFinder 9 | { 10 | private readonly MemoryReader _memoryReader; 11 | private readonly IObjectReader _objectReader; 12 | private readonly Dictionary _constantAddresses; 13 | private readonly Dictionary _constantAddressesCache; 14 | private readonly Dictionary<(IReadOnlyList tokens, IntPtr baseAddress), IntPtr> _groupReadAddressesCache; 15 | 16 | private readonly AddressTokenizer _addressTokenizer = new AddressTokenizer(); 17 | /// 18 | /// Size of pointers in searched process 19 | /// 20 | public int IntPtrSize { get; set; } = IntPtr.Size; 21 | 22 | public AddressFinder(MemoryReader memoryReader, IObjectReader objectReader, Dictionary constantAddresses) 23 | { 24 | _memoryReader = memoryReader; 25 | _memoryReader.ProcessChanged += MemoryReaderOnProcessChanged; 26 | _objectReader = objectReader; 27 | _constantAddresses = constantAddresses; 28 | _constantAddressesCache = new Dictionary(); 29 | _groupReadAddressesCache = new Dictionary<(IReadOnlyList tokens, IntPtr baseAddress), IntPtr>(64); 30 | } 31 | 32 | private void MemoryReaderOnProcessChanged(object sender, EventArgs e) 33 | { 34 | lock (_groupReadAddressesCache) 35 | ResetCache(); 36 | } 37 | 38 | private void ResetCache() 39 | { 40 | _constantAddressesCache.Clear(); 41 | ResetGroupReadCache(); 42 | } 43 | 44 | public void ResetGroupReadCache() => _groupReadAddressesCache.Clear(); 45 | 46 | public IntPtr FindAddress(IReadOnlyList tokens, IntPtr baseAddress) 47 | { 48 | lock (_groupReadAddressesCache) 49 | { 50 | return InternalFindAddress(tokens, baseAddress); 51 | } 52 | } 53 | 54 | private IntPtr InternalFindAddress(IReadOnlyList tokens, IntPtr baseAddress) 55 | { 56 | if (_groupReadAddressesCache.TryGetValue((tokens, baseAddress), out var address)) 57 | return address; 58 | 59 | var lastToken = TokenType.SequenceTerminator; 60 | foreach (var token in tokens) 61 | { 62 | switch (token.TokenType) 63 | { 64 | case TokenType.StringValue: 65 | 66 | if (!_constantAddresses.ContainsKey(token.Value)) 67 | throw new UnknownConstantAddressException(); 68 | if (_constantAddressesCache.TryGetValue(token.Value, out var constantAdress)) 69 | baseAddress = constantAdress; 70 | else 71 | { 72 | var findResult = FindAddress(_addressTokenizer.Tokenize(_constantAddresses[token.Value]), IntPtr.Zero); 73 | if (findResult == IntPtr.Zero) 74 | return IntPtr.Zero; 75 | baseAddress = _constantAddressesCache[token.Value] = findResult; 76 | } 77 | 78 | break; 79 | case TokenType.HexPatternValue: 80 | var pattern = PatternHelpers.StringToBytePatternArray(token.Value); 81 | baseAddress = _memoryReader.FindPattern(pattern.Bytes, pattern.Mask, 0, pattern.Mask.Contains("?")); 82 | break; 83 | case TokenType.CloseBracket: 84 | baseAddress = _objectReader.ReadPointer(baseAddress); 85 | if (baseAddress == IntPtr.Zero) 86 | return _groupReadAddressesCache[(tokens, baseAddress)] = IntPtr.Zero; 87 | 88 | break; 89 | case TokenType.HexValue when baseAddress == IntPtr.Zero: 90 | case TokenType.NumberValue when baseAddress == IntPtr.Zero: 91 | return IntPtr.Zero; 92 | case TokenType.HexValue when lastToken == TokenType.Add || lastToken == TokenType.Subtract: 93 | ProcessValue(Convert.ToInt32(token.Value, 16)); 94 | break; 95 | case TokenType.NumberValue when lastToken == TokenType.Add || lastToken == TokenType.Subtract: 96 | ProcessValue(Convert.ToInt32(token.Value)); 97 | break; 98 | case TokenType.OpenBracket: 99 | case TokenType.Add: 100 | case TokenType.Subtract: 101 | case TokenType.SequenceTerminator: 102 | break; 103 | default: 104 | throw new InvalidAddressPatternException(); 105 | } 106 | 107 | lastToken = token.TokenType; 108 | } 109 | 110 | return _groupReadAddressesCache[(tokens, baseAddress)] = baseAddress; 111 | 112 | bool AllZeros(byte[] bytes) 113 | { 114 | //Intentionally not using linq 115 | //bytes.All(b=>b==0); 116 | foreach (var b in bytes) 117 | { 118 | if (b != 0) 119 | return false; 120 | } 121 | 122 | return true; 123 | } 124 | void ProcessValue(int value) 125 | { 126 | baseAddress += lastToken == TokenType.Add 127 | ? value 128 | : -value; 129 | } 130 | } 131 | public class UnknownConstantAddressException : Exception { } 132 | public class InvalidAddressPatternException : Exception { } 133 | 134 | } 135 | } -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/Structured/IStructuredMemoryReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ProcessMemoryDataFinder.Structured 5 | { 6 | public interface IStructuredMemoryReader : IDisposable 7 | { 8 | /// 9 | /// Should memory read times be tracked and saved in ? 10 | /// 11 | bool WithTimes { get; set; } 12 | 13 | /// 14 | /// When is true, stores per-prop read times 15 | /// 16 | Dictionary ReadTimes { get; } 17 | 18 | /// 19 | /// Is current reader instance ready for read calls (is there valid process available) 20 | /// Reads started while this is false will always fail 21 | /// 22 | bool CanRead { get; } 23 | 24 | /// 25 | /// How often should reader check if valid process is available on the system. 26 | /// 27 | int ProcessWatcherDelayMs { get; set; } 28 | 29 | /// 30 | /// Should an attempt at reading invalid address in abort read call(and return false)? 31 | /// 32 | bool AbortReadOnInvalidValue { get; set; } 33 | 34 | /// 35 | /// Triggers whenever memory read results in invalid address or null pointer 36 | /// 37 | event EventHandler<(object readObject, string propPath)> InvalidRead; 38 | 39 | /// 40 | /// Recursively reads all props in hierarchy marked with 41 | /// 42 | /// class to read 43 | /// 44 | /// 45 | /// false if only part of read completed, otherwise true 46 | bool TryRead(T readObj) where T : class; 47 | 48 | /// 49 | /// Reads single prop value in 50 | /// 51 | /// base object with contains property that is going to be read 52 | /// property name to be read (use nameof()) 53 | /// new value of the prop from memory 54 | /// false if only part of read completed, otherwise true 55 | bool TryReadProperty(object readObj, string propertyName, out object result); 56 | } 57 | } -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/Structured/MemoryAddressAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ProcessMemoryDataFinder.Structured 4 | { 5 | /// 6 | /// Marks property to be filled by 7 | /// 8 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false)] 9 | public class MemoryAddressAttribute : Attribute 10 | { 11 | public string RelativePath { get; } 12 | public bool IgnoreNullPtr { get; } 13 | public bool CheckClassAddress { get; } 14 | public string CheckClassAddressPropName { get; } 15 | 16 | /// 17 | /// Marks property to be filled by 18 | /// 19 | /// 20 | /// Relative or absolute memory path in the target process to property value 21 | /// Relative path is valid only for props and uses current class as base address for navigation. For example: "[+0xC]" would result in (pseudo-code, not valid)"[CurrentClassAddress+0xC]" 22 | /// Absolute path uses either predefined constant AoB pattern to search for OR static cached address defined in baseAdresses Dictionary supplied in 23 | /// 24 | /// 25 | /// Should null-pointer reads for this property be ignored?(read won't be marked as invalid) 26 | /// for nullable value-types and strings this will result in prop being assigned null 27 | /// classes however won't be null'ed as to not disable future attempts at reading this value 28 | /// This can be instead achieved manually by first reading class address in a separate (int?) prop and refactoring actual prop value to be dependent on address value being non-null/zero 29 | /// 30 | /// 31 | /// Should base class address be checked for non-0 memory address before attempting reading of any props inside? 32 | /// 33 | /// 34 | /// Optional (int?) prop name to fill with class memory address after read is finished 35 | /// 36 | public MemoryAddressAttribute(string relativePath = null, bool ignoreNullPtr = false, bool checkClassAddress = false, string checkClassAddressPropName = null) 37 | { 38 | CheckClassAddressPropName = checkClassAddressPropName; 39 | RelativePath = relativePath; 40 | IgnoreNullPtr = ignoreNullPtr; 41 | CheckClassAddress = checkClassAddress; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/Structured/PropInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace ProcessMemoryDataFinder.Structured 5 | { 6 | public class PropInfo 7 | { 8 | public string Path { get; } 9 | public PropertyInfo PropertyInfo { get; } 10 | public Type PropType { get; } 11 | public Type UnderlyingPropType { get; } 12 | public bool IsClass { get; } 13 | public bool IsStringOrArrayOrList { get; } 14 | public bool IsNullable { get; } 15 | public string MemoryPath { get; } 16 | public bool IgnoreNullPtr { get; } 17 | public Action Setter { get; } 18 | public Func Getter { get; } 19 | public StructuredMemoryReader.ReadObject Reader { get; } 20 | 21 | public PropInfo(string path, PropertyInfo propertyInfo, Type propType, Type underlyingPropType, bool isClass, bool isStringOrArrayOrList, 22 | bool isNullable, string memoryPath, bool ignoreNullPtr, Action setter, Func getter, StructuredMemoryReader.ReadObject reader) 23 | { 24 | Path = path; 25 | PropertyInfo = propertyInfo; 26 | PropType = propType; 27 | UnderlyingPropType = underlyingPropType; 28 | IsClass = isClass; 29 | IsStringOrArrayOrList = isStringOrArrayOrList; 30 | IsNullable = isNullable; 31 | MemoryPath = memoryPath; 32 | IgnoreNullPtr = ignoreNullPtr; 33 | Setter = setter; 34 | Getter = getter; 35 | Reader = reader; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/Structured/StructuredMemoryReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Reflection.Emit; 7 | using ProcessMemoryDataFinder.API; 8 | using ProcessMemoryDataFinder.Structured.Tokenizer; 9 | 10 | namespace ProcessMemoryDataFinder.Structured 11 | { 12 | public class StructuredMemoryReader : IDisposable, IStructuredMemoryReader 13 | { 14 | protected MemoryReader _memoryReader; 15 | protected AddressFinder _addressFinder; 16 | private AddressTokenizer _addressTokenizer = new AddressTokenizer(); 17 | protected IObjectReader ObjectReader; 18 | 19 | /// 20 | /// Should memory read times be tracked and saved in ? 21 | /// 22 | public bool WithTimes { get; set; } 23 | /// 24 | /// When is true, stores per-prop read times 25 | /// 26 | public Dictionary ReadTimes { get; } = new Dictionary(); 27 | 28 | public bool CanRead => _memoryReader.CurrentProcess != null; 29 | 30 | public event EventHandler<(object readObject, string propPath)> InvalidRead; 31 | public event EventHandler<(object readObject, string propPath)> IgnoredInvalidRead; 32 | 33 | public bool AbortReadOnInvalidValue { get; set; } = true; 34 | 35 | public int ProcessWatcherDelayMs 36 | { 37 | get => _memoryReader.ProcessWatcherDelayMs; 38 | set => _memoryReader.ProcessWatcherDelayMs = value; 39 | } 40 | 41 | private class DummyAddressChecker 42 | { 43 | [MemoryAddress("")] public int? AddressProp { get; set; } 44 | } 45 | 46 | protected class TypeCacheEntry 47 | { 48 | public Type Type { get; } 49 | public string ClassPath { get; } 50 | public List Props { get; } 51 | public PropInfo ReadCheckerPropertyInfo { get; set; } 52 | 53 | public TypeCacheEntry(Type type, string classPath, List props) 54 | { 55 | Type = type; 56 | ClassPath = classPath; 57 | Props = props; 58 | } 59 | } 60 | 61 | private Dictionary typeCache = 62 | new Dictionary(); 63 | protected Dictionary DefaultValues = new Dictionary 64 | { 65 | { typeof(int) , -1 }, 66 | { typeof(short) , (short)-1 }, 67 | { typeof(ushort) , (ushort)0 }, 68 | { typeof(float) , -1f }, 69 | { typeof(double) , -1d }, 70 | { typeof(bool) , false }, 71 | { typeof(string) , null }, 72 | { typeof(int[]) , null }, 73 | { typeof(List) , null }, 74 | }; 75 | protected Dictionary SizeDictionary = new Dictionary 76 | { 77 | { typeof(int) , 4 }, 78 | { typeof(short) , 2 }, 79 | { typeof(ushort) , 2 }, 80 | { typeof(float) , 8 }, 81 | { typeof(double) , 8 }, 82 | { typeof(long) , 8 }, 83 | { typeof(bool) , 1 }, 84 | 85 | { typeof(string) , 0 }, 86 | { typeof(int[]) , 0 }, 87 | { typeof(List) , 0 }, 88 | }; 89 | protected Dictionary ReadHandlers; 90 | 91 | public StructuredMemoryReader(string processName, Dictionary baseAdresses, ProcessTargetOptions processTargetOptions, MemoryReader memoryReader = null, IObjectReader objectReader = null) 92 | { 93 | _memoryReader = memoryReader ?? new MemoryReader(processTargetOptions); 94 | ObjectReader = objectReader ?? new ObjectReader(_memoryReader); 95 | _addressFinder = new AddressFinder(_memoryReader, ObjectReader, baseAdresses); 96 | 97 | ReadHandlers = new Dictionary() 98 | { 99 | {typeof(int), (address,prop)=>ReadValueObject(address,prop, v => BitConverter.ToInt32(v, 0)) }, 100 | {typeof(float), (address,prop)=>ReadValueObject(address,prop,v=> BitConverter.ToSingle(v, 0)) }, 101 | {typeof(double), (address,prop)=>ReadValueObject(address,prop,v=> BitConverter.ToDouble(v, 0)) }, 102 | {typeof(short), (address,prop)=>ReadValueObject(address,prop,v=> BitConverter.ToInt16(v, 0)) }, 103 | {typeof(ushort), (address,prop)=>ReadValueObject(address,prop,v=> BitConverter.ToUInt16(v, 0)) }, 104 | {typeof(bool), (address,prop)=>ReadValueObject(address,prop,v=> BitConverter.ToBoolean(v, 0)) }, 105 | {typeof(long), (address,prop)=>ReadValueObject(address,prop,v=> BitConverter.ToInt64(v, 0)) }, 106 | {typeof(string), (address,prop)=>ObjectReader.ReadUnicodeString(address) }, 107 | {typeof(int[]), (address,prop)=>ObjectReader.ReadIntArray(address) }, 108 | {typeof(List), (address,prop)=>ObjectReader.ReadIntList(address) }, 109 | }; 110 | } 111 | 112 | public void AddReadHandlers(Dictionary readHandlers) 113 | { 114 | if (readHandlers != null) 115 | { 116 | foreach (var handler in readHandlers) 117 | ReadHandlers.Add(handler.Key, handler.Value); 118 | } 119 | } 120 | 121 | public bool TryReadProperty(object readObj, string propertyName, out object result) 122 | { 123 | var cacheEntry = typeCache[PrepareReadObject(readObj, string.Empty)]; 124 | var propInfo = cacheEntry.Props.FirstOrDefault(p => p.PropertyInfo.Name == propertyName); 125 | if (propInfo == null) 126 | { 127 | result = null; 128 | return false; 129 | } 130 | 131 | var resolvedProp = ResolveProp(readObj, IntPtr.Zero, propInfo, cacheEntry); 132 | result = resolvedProp.PropValue; 133 | return !resolvedProp.InvalidRead; 134 | } 135 | 136 | public bool TryRead(T readObj) where T : class 137 | { 138 | if (readObj == null) 139 | throw new NullReferenceException(); 140 | 141 | _addressFinder.ResetGroupReadCache(); 142 | return TryInternalRead(readObj); 143 | } 144 | 145 | protected bool TryInternalRead(T readObj, IntPtr? classAddress = null, string classPath = null) where T : class 146 | { 147 | var cacheEntry = typeCache[PrepareReadObject(readObj, classPath)]; 148 | 149 | if (cacheEntry.ReadCheckerPropertyInfo != null) 150 | { 151 | var resolvedProp = ResolveProp(readObj, classAddress, cacheEntry.ReadCheckerPropertyInfo, cacheEntry); 152 | SetPropValue(cacheEntry.ReadCheckerPropertyInfo, resolvedProp.PropValue); 153 | classAddress = resolvedProp.ClassAdress; 154 | if (resolvedProp.InvalidRead || resolvedProp.PropValue == null || (int?)resolvedProp.PropValue == 0) 155 | return false; 156 | } 157 | 158 | foreach (var prop in cacheEntry.Props) 159 | { 160 | Stopwatch readStopwatch = null; 161 | if (WithTimes) 162 | readStopwatch = Stopwatch.StartNew(); 163 | 164 | var result = ResolveProp(readObj, classAddress, prop, cacheEntry); 165 | classAddress = result.ClassAdress; 166 | if (result.InvalidRead) 167 | { 168 | if (prop.IgnoreNullPtr) 169 | IgnoredInvalidRead?.Invoke(this, (readObj, prop.Path)); 170 | else if (AbortReadOnInvalidValue) 171 | { 172 | readStopwatch?.Stop(); 173 | InvalidRead?.Invoke(this, (readObj, prop.Path)); 174 | return false; 175 | } 176 | } 177 | 178 | SetPropValue(prop, result.PropValue); 179 | if (WithTimes && readStopwatch != null) 180 | { 181 | readStopwatch.Stop(); 182 | var readTimeMs = readStopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond; 183 | ReadTimes[prop.Path] = readTimeMs; 184 | } 185 | } 186 | 187 | return true; 188 | } 189 | 190 | private (IntPtr? ClassAdress, bool InvalidRead, object PropValue) ResolveProp(T readObj, IntPtr? classAddress, PropInfo prop, TypeCacheEntry cacheEntry) where T : class 191 | { 192 | if (prop.IsClass && !prop.IsStringOrArrayOrList) 193 | { 194 | var propValue = prop.PropertyInfo.GetValue(readObj); 195 | if (propValue == null) 196 | return (classAddress, false, null); 197 | 198 | IntPtr? address = prop.MemoryPath == null 199 | ? (IntPtr?)null 200 | : ResolvePath(cacheEntry.ClassPath, prop.MemoryPath, classAddress).FinalAddress; 201 | 202 | if (address == IntPtr.Zero) 203 | return (classAddress, !prop.IgnoreNullPtr, propValue); 204 | 205 | var readSuccessful = TryInternalRead(propValue, address, prop.Path); 206 | return (classAddress, !readSuccessful, propValue); 207 | } 208 | 209 | var result = ReadValueForPropInMemory(classAddress, prop, cacheEntry); 210 | return (result.ClassAddress, result.InvalidRead, result.Result); 211 | } 212 | 213 | private (object Result, IntPtr ClassAddress, bool InvalidRead) ReadValueForPropInMemory(IntPtr? classAddress, PropInfo prop, TypeCacheEntry cacheEntry) 214 | { 215 | var (propAddress, newClassAddress) = ResolvePath(cacheEntry.ClassPath, prop.MemoryPath, classAddress); 216 | var result = propAddress == IntPtr.Zero 217 | ? null 218 | : prop.Reader(propAddress, prop); 219 | 220 | return (result, newClassAddress, (prop.IsClass || prop.IsNullable) ? false : result == null); 221 | } 222 | 223 | private void SetPropValue(PropInfo prop, object result) 224 | { 225 | if (result != null || prop.IsNullable) 226 | prop.Setter(result); 227 | else if (DefaultValues.ContainsKey(prop.PropType)) 228 | prop.Setter(DefaultValues[prop.PropType]); 229 | } 230 | 231 | private T PrepareReadObject(T readObject, string classPath) 232 | { 233 | if (typeCache.ContainsKey(readObject)) return readObject; 234 | 235 | var type = readObject.GetType(); 236 | var classProps = type.GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); 237 | var classMemoryAddressAttribute = type.GetCustomAttribute(typeof(MemoryAddressAttribute), true) as MemoryAddressAttribute; 238 | 239 | typeCache[readObject] = new TypeCacheEntry(type, classMemoryAddressAttribute?.RelativePath, new List()); 240 | 241 | if (classMemoryAddressAttribute != null && classMemoryAddressAttribute.CheckClassAddress) 242 | { 243 | var dummyProp = typeof(DummyAddressChecker).GetProperty(nameof(DummyAddressChecker.AddressProp)); 244 | if (string.IsNullOrEmpty(classMemoryAddressAttribute.CheckClassAddressPropName)) 245 | typeCache[readObject].ReadCheckerPropertyInfo = CreatePropInfo(readObject, dummyProp, classPath, type.Name, true); 246 | else 247 | typeCache[readObject].ReadCheckerPropertyInfo = CreatePropInfo(readObject, classProps.First(x => x.Name == classMemoryAddressAttribute.CheckClassAddressPropName), classPath, type.Name); 248 | } 249 | 250 | foreach (var prop in classProps.Where(x => x.Name != classMemoryAddressAttribute?.CheckClassAddressPropName)) 251 | { 252 | var propInfo = CreatePropInfo(readObject, prop, classPath, type.Name); 253 | if (propInfo != null) 254 | typeCache[readObject].Props.Add(propInfo); 255 | } 256 | 257 | return readObject; 258 | } 259 | 260 | private PropInfo CreatePropInfo(T readObject, PropertyInfo propertyInfo, string classPath, string parentTypeName, bool classAddressGetter = false) 261 | { 262 | if (!(propertyInfo.GetCustomAttribute(typeof(MemoryAddressAttribute), true) is MemoryAddressAttribute memoryAddressAttribute)) 263 | return null; 264 | 265 | string finalPath = $"{parentTypeName}.{propertyInfo.Name}"; 266 | if (!string.IsNullOrEmpty(classPath)) 267 | finalPath = $"{classPath}.{finalPath}"; 268 | 269 | var underlyingType = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType; 270 | 271 | var isString = propertyInfo.PropertyType == typeof(string); 272 | 273 | if (classAddressGetter) 274 | return new PropInfo(finalPath, propertyInfo, propertyInfo.PropertyType, underlyingType, propertyInfo.PropertyType.IsClass, 275 | isString || (propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(List<>)), 276 | Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null, "", false, (v) => { }, () => null, ReadHandlers[underlyingType]); 277 | 278 | return new PropInfo(finalPath, propertyInfo, propertyInfo.PropertyType, underlyingType, propertyInfo.PropertyType.IsClass, 279 | isString || (propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(List<>)), 280 | Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null, memoryAddressAttribute.RelativePath, memoryAddressAttribute.IgnoreNullPtr, (v) => propertyInfo.SetValue(readObject, v), 281 | () => propertyInfo.GetValue(readObject), ReadHandlers.ContainsKey(underlyingType) ? ReadHandlers[underlyingType] : (propertyInfo.PropertyType.IsClass ? null : throw new NotImplementedException($"Reading of {underlyingType.FullName} is not implemented. Add read handler for this type."))); 282 | } 283 | 284 | private (IntPtr FinalAddress, IntPtr ClassAddress) ResolvePath(string classMemoryPath, string propMemoryPath, 285 | IntPtr? providedClassAddress) 286 | { 287 | IntPtr classAddress = IntPtr.Zero; 288 | if (providedClassAddress.HasValue && providedClassAddress.Value != IntPtr.Zero) 289 | classAddress = providedClassAddress.Value; 290 | else if (classMemoryPath != null) 291 | { 292 | var tokenizedClass = _addressTokenizer.Tokenize(classMemoryPath); 293 | classAddress = _addressFinder.FindAddress(tokenizedClass, IntPtr.Zero); 294 | if (classAddress == IntPtr.Zero) return (IntPtr.Zero, IntPtr.Zero); 295 | } 296 | 297 | IntPtr finalAddress = IntPtr.Zero; 298 | if (propMemoryPath != null) 299 | { 300 | var tokenizedProp = _addressTokenizer.Tokenize(propMemoryPath); 301 | finalAddress = _addressFinder.FindAddress(tokenizedProp, classAddress); 302 | } 303 | 304 | return (finalAddress, classAddress); 305 | } 306 | 307 | protected virtual void Dispose(bool disposing) 308 | { 309 | if (disposing) 310 | { 311 | _memoryReader?.Dispose(); 312 | } 313 | } 314 | 315 | public void Dispose() 316 | { 317 | Dispose(true); 318 | GC.SuppressFinalize(this); 319 | } 320 | 321 | private object ReadValueObject(IntPtr finalAddress, PropInfo propInfo, Func converter) 322 | { 323 | var propValue = _memoryReader.ReadData(finalAddress, SizeDictionary[propInfo.UnderlyingPropType]); 324 | if (propValue == null) 325 | return null; 326 | 327 | return converter(propValue); 328 | } 329 | 330 | public delegate object ReadObject(IntPtr finalAddress, PropInfo propInfo); 331 | } 332 | } -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/Structured/Tokenizer/AddressTokenizer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace ProcessMemoryDataFinder.Structured.Tokenizer 5 | { 6 | public class AddressTokenizer 7 | { 8 | private readonly List _tokens; 9 | private readonly Dictionary> _dslCache = new Dictionary>(); 10 | 11 | public AddressTokenizer() 12 | { 13 | _tokens = new List 14 | { 15 | new TokenDefinition(TokenType.CloseBracket,"\\]",1), 16 | new TokenDefinition(TokenType.OpenBracket,"\\[",1), 17 | new TokenDefinition(TokenType.HexPatternValue,"([ABCDEF0-9?]){2,}",1), 18 | new TokenDefinition(TokenType.NumberValue,"\\d+",2), 19 | new TokenDefinition(TokenType.HexValue,"0x[ABCDEF0-9]+",1), 20 | new TokenDefinition(TokenType.StringValue,"\\w+",2), 21 | new TokenDefinition(TokenType.Add,"\\+",1), 22 | new TokenDefinition(TokenType.Subtract,"\\-",1) 23 | }; 24 | } 25 | 26 | public List Tokenize(string pattern) 27 | { 28 | if (_dslCache.ContainsKey(pattern)) 29 | return _dslCache[pattern]; 30 | 31 | var result = new List(); 32 | var tokenMatches = FindTokenMatches(pattern); 33 | var groupedByIndex = tokenMatches.GroupBy(x => x.StartIndex) 34 | .OrderBy(x => x.Key) 35 | .ToList(); 36 | TokenMatch lastMatch = null; 37 | for (int i = 0; i < groupedByIndex.Count; i++) 38 | { 39 | var bestMatch = groupedByIndex[i].OrderBy(x => x.Precedence).First(); 40 | if (lastMatch != null && bestMatch.StartIndex < lastMatch.EndIndex) 41 | continue; 42 | 43 | result.Add(new DslToken(bestMatch.TokenType, bestMatch.Value)); 44 | lastMatch = bestMatch; 45 | } 46 | 47 | result.Add(new DslToken(TokenType.SequenceTerminator)); 48 | return _dslCache[pattern] = result; 49 | } 50 | 51 | private List FindTokenMatches(string errorMessage) 52 | { 53 | var tokenMatches = new List(); 54 | foreach (var tokenDefinition in _tokens) 55 | tokenMatches.AddRange(tokenDefinition.FindMatches(errorMessage).ToList()); 56 | 57 | return tokenMatches; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/Structured/Tokenizer/DslToken.cs: -------------------------------------------------------------------------------- 1 | namespace ProcessMemoryDataFinder.Structured.Tokenizer 2 | { 3 | public class DslToken 4 | { 5 | public TokenType TokenType { get; set; } 6 | public string Value { get; set; } 7 | 8 | public DslToken(TokenType tokenType) 9 | { 10 | TokenType = tokenType; 11 | Value = string.Empty; 12 | } 13 | 14 | public DslToken(TokenType tokenType, string value) 15 | { 16 | TokenType = tokenType; 17 | Value = value; 18 | } 19 | 20 | public DslToken Clone() 21 | { 22 | return new DslToken(TokenType, Value); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/Structured/Tokenizer/TokenDefinition.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace ProcessMemoryDataFinder.Structured.Tokenizer 5 | { 6 | public class TokenDefinition 7 | { 8 | private Regex _regex; 9 | private readonly TokenType _returnsToken; 10 | private readonly int _precedence; 11 | 12 | public TokenDefinition(TokenType returnsToken, string regexPattern, int precedence) 13 | { 14 | _regex = new Regex(regexPattern, RegexOptions.Compiled); 15 | _returnsToken = returnsToken; 16 | _precedence = precedence; 17 | } 18 | 19 | public IEnumerable FindMatches(string inputString) 20 | { 21 | var matches = _regex.Matches(inputString); 22 | for (int i = 0; i < matches.Count; i++) 23 | { 24 | yield return new TokenMatch() 25 | { 26 | StartIndex = matches[i].Index, 27 | EndIndex = matches[i].Index + matches[i].Length, 28 | TokenType = _returnsToken, 29 | Value = matches[i].Value, 30 | Precedence = _precedence 31 | }; 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/Structured/Tokenizer/TokenMatch.cs: -------------------------------------------------------------------------------- 1 | namespace ProcessMemoryDataFinder.Structured.Tokenizer 2 | { 3 | public class TokenMatch 4 | { 5 | public TokenType TokenType { get; set; } 6 | public string Value { get; set; } 7 | public int StartIndex { get; set; } 8 | public int EndIndex { get; set; } 9 | public int Precedence { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /ProcessMemoryDataFinder/Structured/Tokenizer/TokenType.cs: -------------------------------------------------------------------------------- 1 | namespace ProcessMemoryDataFinder.Structured.Tokenizer 2 | { 3 | public enum TokenType 4 | { 5 | /// 6 | /// Start of nested read 7 | /// 8 | OpenBracket, 9 | /// 10 | /// End of nested read, results in pointer read 11 | /// 12 | CloseBracket, 13 | /// 14 | /// AoB value, results in non-cached memory search 15 | /// 16 | HexPatternValue, 17 | /// 18 | /// Constant hex value used in memory calculations 19 | /// 20 | HexValue, 21 | /// 22 | /// Constant numerical value used in memory calculations 23 | /// 24 | NumberValue, 25 | /// 26 | /// Constant address with predefined AoB pattern, results in memory search. Final address will be cached 27 | /// 28 | StringValue, 29 | /// 30 | /// Add value to effective address of parent 31 | /// 32 | Add, 33 | /// 34 | /// Subtract value from effective address of parent 35 | /// 36 | Subtract, 37 | /// 38 | /// End of the sequence 39 | /// 40 | SequenceTerminator 41 | } 42 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ProcessMemoryDataFinder nuget | nuget 2 | 3 | Provides means to find data based on pre-made memory signatures and read that data in windows processes. 4 | 5 | It was mainly created to be able to read various values from the game called [osu!](https://osu.ppy.sh) but it was written in a way that should allow use for any windows program. 6 | 7 | # Structure: 8 | * ProcessMemoryDataFinder - main implementation, does not have any application-specifc code. 9 | * OsuMemoryDataProvider / OsuMemoryDataProviderTester - example usage for the previously mentioned osu! game 10 | 11 | 12 | ## License 13 | This software is licensed under GNU GPLv3. You can find the full text of the license [here](https://github.com/Piotrekol/ProcessMemoryDataFinder/blob/master/LICENSE). 14 | -------------------------------------------------------------------------------- /StructuredOsuMemoryProviderTester/CompilerServices.cs: -------------------------------------------------------------------------------- 1 | #if NET48 2 | namespace System.Runtime.CompilerServices 3 | { 4 | [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] 5 | internal static class IsExternalInit { } 6 | } 7 | #endif -------------------------------------------------------------------------------- /StructuredOsuMemoryProviderTester/Form1.Designer.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace StructuredOsuMemoryProviderTester 3 | { 4 | partial class Form1 5 | { 6 | /// 7 | /// Required designer variable. 8 | /// 9 | private System.ComponentModel.IContainer components = null; 10 | 11 | /// 12 | /// Clean up any resources being used. 13 | /// 14 | /// true if managed resources should be disposed; otherwise, false. 15 | protected override void Dispose(bool disposing) 16 | { 17 | if (disposing && (components != null)) 18 | { 19 | components.Dispose(); 20 | } 21 | base.Dispose(disposing); 22 | } 23 | 24 | #region Windows Form Designer generated code 25 | 26 | /// 27 | /// Required method for Designer support - do not modify 28 | /// the contents of this method with the code editor. 29 | /// 30 | private void InitializeComponent() 31 | { 32 | this.button_ResetReadTimeMinMax = new System.Windows.Forms.Button(); 33 | this.textBox_readTime = new System.Windows.Forms.TextBox(); 34 | this.numericUpDown_readDelay = new System.Windows.Forms.NumericUpDown(); 35 | this.label6 = new System.Windows.Forms.Label(); 36 | this.panel1 = new System.Windows.Forms.Panel(); 37 | this.textBox_Data = new System.Windows.Forms.TextBox(); 38 | this.textBox_ReadTimes = new System.Windows.Forms.TextBox(); 39 | this.listBox_logs = new System.Windows.Forms.ListBox(); 40 | ((System.ComponentModel.ISupportInitialize)(this.numericUpDown_readDelay)).BeginInit(); 41 | this.panel1.SuspendLayout(); 42 | this.SuspendLayout(); 43 | // 44 | // button_ResetReadTimeMinMax 45 | // 46 | this.button_ResetReadTimeMinMax.Location = new System.Drawing.Point(178, 86); 47 | this.button_ResetReadTimeMinMax.Name = "button_ResetReadTimeMinMax"; 48 | this.button_ResetReadTimeMinMax.Size = new System.Drawing.Size(75, 23); 49 | this.button_ResetReadTimeMinMax.TabIndex = 21; 50 | this.button_ResetReadTimeMinMax.Text = "Reset"; 51 | this.button_ResetReadTimeMinMax.UseVisualStyleBackColor = true; 52 | this.button_ResetReadTimeMinMax.Click += new System.EventHandler(this.button_ResetReadTimeMinMax_Click); 53 | // 54 | // textBox_readTime 55 | // 56 | this.textBox_readTime.Location = new System.Drawing.Point(4, 56); 57 | this.textBox_readTime.Multiline = true; 58 | this.textBox_readTime.Name = "textBox_readTime"; 59 | this.textBox_readTime.Size = new System.Drawing.Size(168, 53); 60 | this.textBox_readTime.TabIndex = 20; 61 | // 62 | // numericUpDown_readDelay 63 | // 64 | this.numericUpDown_readDelay.Location = new System.Drawing.Point(4, 20); 65 | this.numericUpDown_readDelay.Maximum = new decimal(new int[] { 66 | 10000, 67 | 0, 68 | 0, 69 | 0}); 70 | this.numericUpDown_readDelay.Name = "numericUpDown_readDelay"; 71 | this.numericUpDown_readDelay.Size = new System.Drawing.Size(178, 23); 72 | this.numericUpDown_readDelay.TabIndex = 19; 73 | this.numericUpDown_readDelay.Value = new decimal(new int[] { 74 | 33, 75 | 0, 76 | 0, 77 | 0}); 78 | this.numericUpDown_readDelay.ValueChanged += new System.EventHandler(this.numericUpDown_readDelay_ValueChanged); 79 | // 80 | // label6 81 | // 82 | this.label6.AutoSize = true; 83 | this.label6.Location = new System.Drawing.Point(1, 2); 84 | this.label6.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); 85 | this.label6.Name = "label6"; 86 | this.label6.Size = new System.Drawing.Size(200, 15); 87 | this.label6.TabIndex = 18; 88 | this.label6.Text = "Read delay in ms (set 0 for no delay):"; 89 | // 90 | // panel1 91 | // 92 | this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 93 | this.panel1.Controls.Add(this.label6); 94 | this.panel1.Controls.Add(this.button_ResetReadTimeMinMax); 95 | this.panel1.Controls.Add(this.numericUpDown_readDelay); 96 | this.panel1.Controls.Add(this.textBox_readTime); 97 | this.panel1.Location = new System.Drawing.Point(550, 12); 98 | this.panel1.Name = "panel1"; 99 | this.panel1.Size = new System.Drawing.Size(264, 121); 100 | this.panel1.TabIndex = 22; 101 | // 102 | // textBox_Data 103 | // 104 | this.textBox_Data.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 105 | | System.Windows.Forms.AnchorStyles.Left))); 106 | this.textBox_Data.Font = new System.Drawing.Font("Segoe UI", 6F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); 107 | this.textBox_Data.Location = new System.Drawing.Point(0, 0); 108 | this.textBox_Data.Multiline = true; 109 | this.textBox_Data.Name = "textBox_Data"; 110 | this.textBox_Data.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; 111 | this.textBox_Data.Size = new System.Drawing.Size(544, 578); 112 | this.textBox_Data.TabIndex = 23; 113 | // 114 | // textBox_ReadTimes 115 | // 116 | this.textBox_ReadTimes.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 117 | | System.Windows.Forms.AnchorStyles.Left) 118 | | System.Windows.Forms.AnchorStyles.Right))); 119 | this.textBox_ReadTimes.Location = new System.Drawing.Point(550, 139); 120 | this.textBox_ReadTimes.Multiline = true; 121 | this.textBox_ReadTimes.Name = "textBox_ReadTimes"; 122 | this.textBox_ReadTimes.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; 123 | this.textBox_ReadTimes.Size = new System.Drawing.Size(407, 358); 124 | this.textBox_ReadTimes.TabIndex = 24; 125 | // 126 | // listBox_logs 127 | // 128 | this.listBox_logs.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) 129 | | System.Windows.Forms.AnchorStyles.Right))); 130 | this.listBox_logs.FormattingEnabled = true; 131 | this.listBox_logs.ItemHeight = 15; 132 | this.listBox_logs.Location = new System.Drawing.Point(550, 503); 133 | this.listBox_logs.Name = "listBox_logs"; 134 | this.listBox_logs.Size = new System.Drawing.Size(407, 79); 135 | this.listBox_logs.TabIndex = 25; 136 | // 137 | // Form1 138 | // 139 | this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); 140 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 141 | this.ClientSize = new System.Drawing.Size(957, 578); 142 | this.Controls.Add(this.listBox_logs); 143 | this.Controls.Add(this.textBox_ReadTimes); 144 | this.Controls.Add(this.textBox_Data); 145 | this.Controls.Add(this.panel1); 146 | this.Name = "Form1"; 147 | this.Text = "Form1"; 148 | ((System.ComponentModel.ISupportInitialize)(this.numericUpDown_readDelay)).EndInit(); 149 | this.panel1.ResumeLayout(false); 150 | this.panel1.PerformLayout(); 151 | this.ResumeLayout(false); 152 | this.PerformLayout(); 153 | 154 | } 155 | 156 | #endregion 157 | 158 | private System.Windows.Forms.Button button_ResetReadTimeMinMax; 159 | private System.Windows.Forms.TextBox textBox_readTime; 160 | private System.Windows.Forms.NumericUpDown numericUpDown_readDelay; 161 | private System.Windows.Forms.Label label6; 162 | private System.Windows.Forms.Panel panel1; 163 | private System.Windows.Forms.TextBox textBox_Data; 164 | private System.Windows.Forms.TextBox textBox_ReadTimes; 165 | private System.Windows.Forms.ListBox listBox_logs; 166 | } 167 | } 168 | 169 | -------------------------------------------------------------------------------- /StructuredOsuMemoryProviderTester/Form1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Diagnostics; 5 | using System.Globalization; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using System.Windows.Forms; 9 | using Newtonsoft.Json; 10 | using OsuMemoryDataProvider; 11 | using OsuMemoryDataProvider.OsuMemoryModels; 12 | using OsuMemoryDataProvider.OsuMemoryModels.Abstract; 13 | using OsuMemoryDataProvider.OsuMemoryModels.Direct; 14 | 15 | namespace StructuredOsuMemoryProviderTester 16 | { 17 | 18 | public partial class Form1 : Form 19 | { 20 | private readonly string _osuWindowTitleHint; 21 | private int _readDelay = 33; 22 | private readonly object _minMaxLock = new object(); 23 | private double _memoryReadTimeMin = double.PositiveInfinity; 24 | private double _memoryReadTimeMax = double.NegativeInfinity; 25 | 26 | private readonly StructuredOsuMemoryReader _sreader; 27 | private CancellationTokenSource cts = new CancellationTokenSource(); 28 | 29 | public Form1(string osuWindowTitleHint) 30 | { 31 | InitializeComponent(); 32 | _sreader = StructuredOsuMemoryReader.GetInstance(new("osu!", osuWindowTitleHint)); 33 | Shown += OnShown; 34 | Closing += OnClosing; 35 | } 36 | 37 | private void OnClosing(object sender, CancelEventArgs cancelEventArgs) 38 | { 39 | cts.Cancel(); 40 | } 41 | 42 | private void numericUpDown_readDelay_ValueChanged(object sender, EventArgs e) 43 | { 44 | if (int.TryParse(numericUpDown_readDelay.Value.ToString(CultureInfo.InvariantCulture), out var value)) 45 | { 46 | _readDelay = value; 47 | } 48 | else 49 | { 50 | numericUpDown_readDelay.Value = 33; 51 | } 52 | } 53 | 54 | private T ReadProperty(object readObj, string propName, T defaultValue = default) where T : struct 55 | { 56 | if (_sreader.TryReadProperty(readObj, propName, out var readResult)) 57 | return (T)readResult; 58 | 59 | return defaultValue; 60 | } 61 | 62 | private T ReadClassProperty(object readObj, string propName, T defaultValue = default) where T : class 63 | { 64 | if (_sreader.TryReadProperty(readObj, propName, out var readResult)) 65 | return (T)readResult; 66 | 67 | return defaultValue; 68 | } 69 | 70 | private int ReadInt(object readObj, string propName) 71 | => ReadProperty(readObj, propName, -5); 72 | private short ReadShort(object readObj, string propName) 73 | => ReadProperty(readObj, propName, -5); 74 | 75 | private float ReadFloat(object readObj, string propName) 76 | => ReadProperty(readObj, propName, -5f); 77 | 78 | private string ReadString(object readObj, string propName) 79 | => ReadClassProperty(readObj, propName, "INVALID READ"); 80 | 81 | private async void OnShown(object sender, EventArgs eventArgs) 82 | { 83 | if (!string.IsNullOrEmpty(_osuWindowTitleHint)) Text += $": {_osuWindowTitleHint}"; 84 | Text += $" ({(Environment.Is64BitProcess ? "x64" : "x86")})"; 85 | _sreader.InvalidRead += SreaderOnInvalidRead; 86 | await Task.Run(async () => 87 | { 88 | Stopwatch stopwatch; 89 | double readTimeMs, readTimeMsMin, readTimeMsMax; 90 | _sreader.WithTimes = true; 91 | var readUsingProperty = false; 92 | var baseAddresses = new OsuBaseAddresses(); 93 | while (true) 94 | { 95 | if (cts.IsCancellationRequested) 96 | return; 97 | 98 | if (!_sreader.CanRead) 99 | { 100 | Invoke((MethodInvoker)(() => 101 | { 102 | textBox_Data.Text = "osu! process not found"; 103 | })); 104 | await Task.Delay(_readDelay); 105 | continue; 106 | } 107 | 108 | stopwatch = Stopwatch.StartNew(); 109 | if (readUsingProperty) 110 | { 111 | baseAddresses.Beatmap.Id = ReadInt(baseAddresses.Beatmap, nameof(CurrentBeatmap.Id)); 112 | baseAddresses.Beatmap.SetId = ReadInt(baseAddresses.Beatmap, nameof(CurrentBeatmap.SetId)); 113 | baseAddresses.Beatmap.MapString = ReadString(baseAddresses.Beatmap, nameof(CurrentBeatmap.MapString)); 114 | baseAddresses.Beatmap.FolderName = ReadString(baseAddresses.Beatmap, nameof(CurrentBeatmap.FolderName)); 115 | baseAddresses.Beatmap.OsuFileName = ReadString(baseAddresses.Beatmap, nameof(CurrentBeatmap.OsuFileName)); 116 | baseAddresses.Beatmap.Md5 = ReadString(baseAddresses.Beatmap, nameof(CurrentBeatmap.Md5)); 117 | baseAddresses.Beatmap.Ar = ReadFloat(baseAddresses.Beatmap, nameof(CurrentBeatmap.Ar)); 118 | baseAddresses.Beatmap.Cs = ReadFloat(baseAddresses.Beatmap, nameof(CurrentBeatmap.Cs)); 119 | baseAddresses.Beatmap.Hp = ReadFloat(baseAddresses.Beatmap, nameof(CurrentBeatmap.Hp)); 120 | baseAddresses.Beatmap.Od = ReadFloat(baseAddresses.Beatmap, nameof(CurrentBeatmap.Od)); 121 | baseAddresses.Beatmap.Status = ReadShort(baseAddresses.Beatmap, nameof(CurrentBeatmap.Status)); 122 | baseAddresses.Skin.Folder = ReadString(baseAddresses.Skin, nameof(Skin.Folder)); 123 | baseAddresses.GeneralData.RawStatus = ReadInt(baseAddresses.GeneralData, nameof(GeneralData.RawStatus)); 124 | baseAddresses.GeneralData.GameMode = ReadInt(baseAddresses.GeneralData, nameof(GeneralData.GameMode)); 125 | baseAddresses.GeneralData.Retries = ReadInt(baseAddresses.GeneralData, nameof(GeneralData.Retries)); 126 | baseAddresses.GeneralData.AudioTime = ReadInt(baseAddresses.GeneralData, nameof(GeneralData.AudioTime)); 127 | baseAddresses.GeneralData.Mods = ReadInt(baseAddresses.GeneralData, nameof(GeneralData.Mods)); 128 | } 129 | else 130 | { 131 | _sreader.TryRead(baseAddresses.Beatmap); 132 | _sreader.TryRead(baseAddresses.Skin); 133 | _sreader.TryRead(baseAddresses.GeneralData); 134 | _sreader.TryRead(baseAddresses.BanchoUser); 135 | } 136 | 137 | if (baseAddresses.GeneralData.OsuStatus == OsuMemoryStatus.SongSelect) 138 | _sreader.TryRead(baseAddresses.SongSelectionScores); 139 | else 140 | baseAddresses.SongSelectionScores.Scores.Clear(); 141 | 142 | if (baseAddresses.GeneralData.OsuStatus == OsuMemoryStatus.ResultsScreen) 143 | _sreader.TryRead(baseAddresses.ResultsScreen); 144 | 145 | if (baseAddresses.GeneralData.OsuStatus == OsuMemoryStatus.Playing) 146 | { 147 | _sreader.TryRead(baseAddresses.Player); 148 | //TODO: flag needed for single/multi player detection (should be read once per play in singleplayer) 149 | _sreader.TryRead(baseAddresses.LeaderBoard); 150 | _sreader.TryRead(baseAddresses.KeyOverlay); 151 | if (readUsingProperty) 152 | { 153 | //Testing reading of reference types(other than string) 154 | _sreader.TryReadProperty(baseAddresses.Player, nameof(Player.Mods), out var dummyResult); 155 | } 156 | } 157 | else 158 | { 159 | baseAddresses.LeaderBoard.Players.Clear(); 160 | } 161 | 162 | var hitErrors = baseAddresses.Player?.HitErrors; 163 | if (hitErrors != null) 164 | { 165 | var hitErrorsCount = hitErrors.Count; 166 | hitErrors.Clear(); 167 | hitErrors.Add(hitErrorsCount); 168 | } 169 | 170 | stopwatch.Stop(); 171 | readTimeMs = stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond; 172 | lock (_minMaxLock) 173 | { 174 | if (readTimeMs < _memoryReadTimeMin) _memoryReadTimeMin = readTimeMs; 175 | if (readTimeMs > _memoryReadTimeMax) _memoryReadTimeMax = readTimeMs; 176 | // copy value since we're inside lock 177 | readTimeMsMin = _memoryReadTimeMin; 178 | readTimeMsMax = _memoryReadTimeMax; 179 | } 180 | 181 | try 182 | { 183 | Invoke((MethodInvoker)(() => 184 | { 185 | textBox_readTime.Text = $" ReadTimeMS: {readTimeMs}{Environment.NewLine}" + 186 | $" Min ReadTimeMS: {readTimeMsMin}{Environment.NewLine}" + 187 | $"Max ReadTimeMS: {readTimeMsMax}{Environment.NewLine}"; 188 | textBox_Data.Text = JsonConvert.SerializeObject(baseAddresses, Formatting.Indented); 189 | textBox_ReadTimes.Text = 190 | JsonConvert.SerializeObject(_sreader.ReadTimes, Formatting.Indented); 191 | })); 192 | } 193 | catch (ObjectDisposedException) 194 | { 195 | return; 196 | } 197 | 198 | _sreader.ReadTimes.Clear(); 199 | await Task.Delay(_readDelay); 200 | } 201 | }, cts.Token); 202 | } 203 | 204 | private void SreaderOnInvalidRead(object sender, (object readObject, string propPath) e) 205 | { 206 | try 207 | { 208 | if (InvokeRequired) 209 | { 210 | //Async call to not impact memory read times(too much) 211 | BeginInvoke((MethodInvoker)(() => SreaderOnInvalidRead(sender, e))); 212 | return; 213 | } 214 | 215 | listBox_logs.Items.Add($"{DateTime.Now:T} Error reading {e.propPath}{Environment.NewLine}"); 216 | if (listBox_logs.Items.Count > 500) 217 | listBox_logs.Items.RemoveAt(0); 218 | 219 | listBox_logs.SelectedIndex = listBox_logs.Items.Count - 1; 220 | listBox_logs.ClearSelected(); 221 | } 222 | catch (ObjectDisposedException) 223 | { 224 | 225 | } 226 | } 227 | 228 | private void button_ResetReadTimeMinMax_Click(object sender, EventArgs e) 229 | { 230 | lock (_minMaxLock) 231 | { 232 | _memoryReadTimeMin = double.PositiveInfinity; 233 | _memoryReadTimeMax = double.NegativeInfinity; 234 | } 235 | 236 | listBox_logs.Items.Clear(); 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /StructuredOsuMemoryProviderTester/Form1.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | -------------------------------------------------------------------------------- /StructuredOsuMemoryProviderTester/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Windows.Forms; 6 | 7 | namespace StructuredOsuMemoryProviderTester 8 | { 9 | static class Program 10 | { 11 | /// 12 | /// The main entry point for the application. 13 | /// 14 | [STAThread] 15 | static void Main(string[] args) 16 | { 17 | Application.EnableVisualStyles(); 18 | Application.SetCompatibleTextRenderingDefault(false); 19 | Application.Run(new Form1(args.FirstOrDefault())); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /StructuredOsuMemoryProviderTester/StructuredOsuMemoryProviderTester.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net48;net8.0-windows 6 | x86;x64;AnyCPU 7 | WinExe 8 | true 9 | latest 10 | 11 | 12 | bin\x86\Debug\ 13 | MinimumRecommendedRules.ruleset 14 | 15 | 16 | bin\AnyCPU\Debug\ 17 | MinimumRecommendedRules.ruleset 18 | 19 | 20 | bin\x64\Debug\ 21 | MinimumRecommendedRules.ruleset 22 | 23 | 24 | bin\x64\Release\ 25 | MinimumRecommendedRules.ruleset 26 | 27 | 28 | bin\AnyCPU\Release\ 29 | MinimumRecommendedRules.ruleset 30 | 31 | 32 | bin\x64\Release\ 33 | MinimumRecommendedRules.ruleset 34 | 35 | 36 | x86 37 | 38 | 39 | x86 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | --------------------------------------------------------------------------------