├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── SharpMonoInjector ├── Assembler.cs ├── ExportedFunction.cs ├── Injector.cs ├── InjectorException.cs ├── Memory.cs ├── MonoImageOpenStatus.cs ├── Native.cs ├── ProcessUtils.cs ├── Properties │ ├── Settings.Designer.cs │ └── Settings.settings └── SharpMonoInjector.csproj ├── UnityInspector.Communicator ├── Communicator.cs └── UnityInspector.Communicator.csproj ├── UnityInspector.GUI ├── App.config ├── App.xaml ├── App.xaml.cs ├── Converters │ ├── BooleanConverter.cs │ ├── BooleanToVisibilityConverter.cs │ ├── ICollectionToBooleanConverter.cs │ └── ObjectMemberTemplateSelector.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Models │ ├── ArrayMember.cs │ ├── CommunicatorClient.cs │ ├── Component.cs │ ├── EnumMember.cs │ ├── GameObject.cs │ ├── MonoProcess.cs │ ├── ObjectMember.cs │ ├── Rect.cs │ ├── Vector2.cs │ └── Vector3.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── UnityInspector.GUI.csproj ├── UnityStyle.xaml ├── Utils.cs ├── ViewModels │ ├── ArrayMemberViewModel.cs │ ├── ComponentViewModel.cs │ ├── GameObjectViewModel.cs │ ├── MainWindowViewModel.cs │ ├── NumericTextBox.cs │ ├── ObjectMemberViewModel.cs │ ├── RelayCommand.cs │ └── ViewModel.cs ├── icon.ico └── packages.config ├── UnityInspector.Injector ├── CommunicatorServer.cs ├── Injector.cs └── UnityInspector.Injector.csproj └── UnityInspector.sln /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | ExternalDlls/ 13 | 14 | # Build results 15 | [Dd]ebug/ 16 | [Dd]ebugPublic/ 17 | [Rr]elease/ 18 | [Rr]eleases/ 19 | x64/ 20 | x86/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | [Ll]og/ 25 | 26 | # Visual Studio 2015 cache/options directory 27 | .vs/ 28 | # Uncomment if you have tasks that create the project's static files in wwwroot 29 | #wwwroot/ 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 | # DNX 45 | project.lock.json 46 | project.fragment.lock.json 47 | artifacts/ 48 | 49 | *_i.c 50 | *_p.c 51 | *_i.h 52 | *.ilk 53 | *.meta 54 | *.obj 55 | *.pch 56 | *.pdb 57 | *.pgc 58 | *.pgd 59 | *.rsp 60 | *.sbr 61 | *.tlb 62 | *.tli 63 | *.tlh 64 | *.tmp 65 | *.tmp_proj 66 | *.log 67 | *.vspscc 68 | *.vssscc 69 | .builds 70 | *.pidb 71 | *.svclog 72 | *.scc 73 | 74 | # Chutzpah Test files 75 | _Chutzpah* 76 | 77 | # Visual C++ cache files 78 | ipch/ 79 | *.aps 80 | *.ncb 81 | *.opendb 82 | *.opensdf 83 | *.sdf 84 | *.cachefile 85 | *.VC.db 86 | *.VC.VC.opendb 87 | 88 | # Visual Studio profiler 89 | *.psess 90 | *.vsp 91 | *.vspx 92 | *.sap 93 | 94 | # TFS 2012 Local Workspace 95 | $tf/ 96 | 97 | # Guidance Automation Toolkit 98 | *.gpState 99 | 100 | # ReSharper is a .NET coding add-in 101 | _ReSharper*/ 102 | *.[Rr]e[Ss]harper 103 | *.DotSettings.user 104 | 105 | # JustCode is a .NET coding add-in 106 | .JustCode 107 | 108 | # TeamCity is a build add-in 109 | _TeamCity* 110 | 111 | # DotCover is a Code Coverage Tool 112 | *.dotCover 113 | 114 | # NCrunch 115 | _NCrunch_* 116 | .*crunch*.local.xml 117 | nCrunchTemp_* 118 | 119 | # MightyMoose 120 | *.mm.* 121 | AutoTest.Net/ 122 | 123 | # Web workbench (sass) 124 | .sass-cache/ 125 | 126 | # Installshield output folder 127 | [Ee]xpress/ 128 | 129 | # DocProject is a documentation generator add-in 130 | DocProject/buildhelp/ 131 | DocProject/Help/*.HxT 132 | DocProject/Help/*.HxC 133 | DocProject/Help/*.hhc 134 | DocProject/Help/*.hhk 135 | DocProject/Help/*.hhp 136 | DocProject/Help/Html2 137 | DocProject/Help/html 138 | 139 | # Click-Once directory 140 | publish/ 141 | 142 | # Publish Web Output 143 | *.[Pp]ublish.xml 144 | *.azurePubxml 145 | # TODO: Comment the next line if you want to checkin your web deploy settings 146 | # but database connection strings (with potential passwords) will be unencrypted 147 | #*.pubxml 148 | *.publishproj 149 | 150 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 151 | # checkin your Azure Web App publish settings, but sensitive information contained 152 | # in these scripts will be unencrypted 153 | PublishScripts/ 154 | 155 | # NuGet Packages 156 | *.nupkg 157 | # The packages folder can be ignored because of Package Restore 158 | **/packages/* 159 | # except build/, which is used as an MSBuild target. 160 | !**/packages/build/ 161 | # Uncomment if necessary however generally it will be regenerated when needed 162 | #!**/packages/repositories.config 163 | # NuGet v3's project.json files produces more ignoreable files 164 | *.nuget.props 165 | *.nuget.targets 166 | 167 | # Microsoft Azure Build Output 168 | csx/ 169 | *.build.csdef 170 | 171 | # Microsoft Azure Emulator 172 | ecf/ 173 | rcf/ 174 | 175 | # Windows Store app package directories and files 176 | AppPackages/ 177 | BundleArtifacts/ 178 | Package.StoreAssociation.xml 179 | _pkginfo.txt 180 | 181 | # Visual Studio cache files 182 | # files ending in .cache can be ignored 183 | *.[Cc]ache 184 | # but keep track of directories ending in .cache 185 | !*.[Cc]ache/ 186 | 187 | # Others 188 | ClientBin/ 189 | ~$* 190 | *~ 191 | *.dbmdl 192 | *.dbproj.schemaview 193 | *.jfm 194 | *.pfx 195 | *.publishsettings 196 | node_modules/ 197 | orleans.codegen.cs 198 | 199 | # Since there are multiple workflows, uncomment next line to ignore bower_components 200 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 201 | #bower_components/ 202 | 203 | # RIA/Silverlight projects 204 | Generated_Code/ 205 | 206 | # Backup & report files from converting an old project file 207 | # to a newer Visual Studio version. Backup files are not needed, 208 | # because we have git ;-) 209 | _UpgradeReport_Files/ 210 | Backup*/ 211 | UpgradeLog*.XML 212 | UpgradeLog*.htm 213 | 214 | # SQL Server files 215 | *.mdf 216 | *.ldf 217 | 218 | # Business Intelligence projects 219 | *.rdl.data 220 | *.bim.layout 221 | *.bim_*.settings 222 | 223 | # Microsoft Fakes 224 | FakesAssemblies/ 225 | 226 | # GhostDoc plugin setting file 227 | *.GhostDoc.xml 228 | 229 | # Node.js Tools for Visual Studio 230 | .ntvs_analysis.dat 231 | 232 | # Visual Studio 6 build log 233 | *.plg 234 | 235 | # Visual Studio 6 workspace options file 236 | *.opt 237 | 238 | # Visual Studio LightSwitch build output 239 | **/*.HTMLClient/GeneratedArtifacts 240 | **/*.DesktopClient/GeneratedArtifacts 241 | **/*.DesktopClient/ModelManifest.xml 242 | **/*.Server/GeneratedArtifacts 243 | **/*.Server/ModelManifest.xml 244 | _Pvt_Extensions 245 | 246 | # Paket dependency manager 247 | .paket/paket.exe 248 | paket-files/ 249 | 250 | # FAKE - F# Make 251 | .fake/ 252 | 253 | # JetBrains Rider 254 | .idea/ 255 | *.sln.iml 256 | 257 | # CodeRush 258 | .cr/ 259 | 260 | # Python Tools for Visual Studio (PTVS) 261 | __pycache__/ 262 | *.pyc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Mohammed ALMadhoun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![UnityInspector Logo](https://user-images.githubusercontent.com/1688821/56691454-aba9f980-66e8-11e9-90b6-4fdf714404b2.png) UnityInspector 2 | 3 | A simple tool for inspecting and editing managed Unity3d games in real-time, using a simple layout you can see game objects in the active scene, and you can click at any game object to see its components, and click at any component to see its members and edit values. 4 | 5 |

6 | Screenshot 7 |

8 | 9 | ## How to build 10 | 1. Clone the project. 11 | 2. Add UnityEngine.dll refrence to UnityInspector.Inejctor. 12 | 3. Tadaaa. 13 | 14 | ## How UnityInspector works 15 | First I tried to build this using a direct memory read and write method and it works kinda well (I'm lying), but using the previous method will leave a lot of work to do for each unity3d engine version (but it works on managed and il2cpp games). 16 | So now this project is using the awesome [warbler/SharpMonoInjector](https://github.com/warbler/SharpMonoInjector) to inject a managed dll inside mono which communicates through TCP with the main program, well and some cool C# code. 17 |

18 | A diagram just to make it cool 19 |

20 | 21 | ## Todo 22 | - [ ] Add data locks 23 | - [ ] Some error handling 24 | - [ ] Support nested objects 25 | - [ ] Change GameObjects and components references 26 | - [ ] Browse prefabs and other GameObjects 27 | -------------------------------------------------------------------------------- /SharpMonoInjector/Assembler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace SharpMonoInjector 5 | { 6 | public class Assembler 7 | { 8 | private readonly List _asm = new List(); 9 | 10 | public void MovRax(IntPtr arg) 11 | { 12 | _asm.AddRange(new byte[] {0x48, 0xB8}); 13 | _asm.AddRange(BitConverter.GetBytes((long)arg)); 14 | } 15 | 16 | public void MovRcx(IntPtr arg) 17 | { 18 | _asm.AddRange(new byte[] {0x48, 0xB9}); 19 | _asm.AddRange(BitConverter.GetBytes((long)arg)); 20 | } 21 | 22 | public void MovRdx(IntPtr arg) 23 | { 24 | _asm.AddRange(new byte[] {0x48, 0xBA}); 25 | _asm.AddRange(BitConverter.GetBytes((long)arg)); 26 | } 27 | 28 | public void MovR8(IntPtr arg) 29 | { 30 | _asm.AddRange(new byte[] {0x49, 0xB8}); 31 | _asm.AddRange(BitConverter.GetBytes((long)arg)); 32 | } 33 | 34 | public void MovR9(IntPtr arg) 35 | { 36 | _asm.AddRange(new byte[] {0x49, 0xB9}); 37 | _asm.AddRange(BitConverter.GetBytes((long)arg)); 38 | } 39 | 40 | public void SubRsp(byte arg) 41 | { 42 | _asm.AddRange(new byte[] {0x48, 0x83, 0xEC}); 43 | _asm.Add(arg); 44 | } 45 | 46 | public void CallRax() 47 | { 48 | _asm.AddRange(new byte[] {0xFF, 0xD0}); 49 | } 50 | 51 | public void AddRsp(byte arg) 52 | { 53 | _asm.AddRange(new byte[] {0x48, 0x83, 0xC4}); 54 | _asm.Add(arg); 55 | } 56 | 57 | public void MovRaxTo(IntPtr dest) 58 | { 59 | _asm.AddRange(new byte[] { 0x48, 0xA3 }); 60 | _asm.AddRange(BitConverter.GetBytes((long)dest)); 61 | } 62 | 63 | public void Push(IntPtr arg) 64 | { 65 | _asm.Add((int)arg < 128 ? (byte)0x6A : (byte)0x68); 66 | _asm.AddRange((int)arg <= 255 ? new[] {(byte)arg} : BitConverter.GetBytes((int)arg)); 67 | } 68 | 69 | public void MovEax(IntPtr arg) 70 | { 71 | _asm.Add(0xB8); 72 | _asm.AddRange(BitConverter.GetBytes((int)arg)); 73 | } 74 | 75 | public void CallEax() 76 | { 77 | _asm.AddRange(new byte[] {0xFF, 0xD0}); 78 | } 79 | 80 | public void AddEsp(byte arg) 81 | { 82 | _asm.AddRange(new byte[] {0x83, 0xC4}); 83 | _asm.Add(arg); 84 | } 85 | 86 | public void MovEaxTo(IntPtr dest) 87 | { 88 | _asm.Add(0xA3); 89 | _asm.AddRange(BitConverter.GetBytes((int)dest)); 90 | } 91 | 92 | public void Return() 93 | { 94 | _asm.Add(0xC3); 95 | } 96 | 97 | public byte[] ToByteArray() => _asm.ToArray(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /SharpMonoInjector/ExportedFunction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SharpMonoInjector 4 | { 5 | public struct ExportedFunction 6 | { 7 | public string Name; 8 | 9 | public IntPtr Address; 10 | 11 | public ExportedFunction(string name, IntPtr address) 12 | { 13 | Name = name; 14 | Address = address; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SharpMonoInjector/Injector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.ComponentModel; 4 | using System.Collections.Generic; 5 | using System.Runtime.InteropServices; 6 | using System.Diagnostics; 7 | using System.Linq; 8 | 9 | namespace SharpMonoInjector 10 | { 11 | public class Injector : IDisposable 12 | { 13 | private const string mono_get_root_domain = "mono_get_root_domain"; 14 | 15 | private const string mono_thread_attach = "mono_thread_attach"; 16 | 17 | private const string mono_image_open_from_data = "mono_image_open_from_data"; 18 | 19 | private const string mono_assembly_load_from_full = "mono_assembly_load_from_full"; 20 | 21 | private const string mono_assembly_get_image = "mono_assembly_get_image"; 22 | 23 | private const string mono_class_from_name = "mono_class_from_name"; 24 | 25 | private const string mono_class_get_method_from_name = "mono_class_get_method_from_name"; 26 | 27 | private const string mono_runtime_invoke = "mono_runtime_invoke"; 28 | 29 | private const string mono_assembly_close = "mono_assembly_close"; 30 | 31 | private const string mono_image_strerror = "mono_image_strerror"; 32 | 33 | private const string mono_object_get_class = "mono_object_get_class"; 34 | 35 | private const string mono_class_get_name = "mono_class_get_name"; 36 | 37 | private readonly Dictionary Exports = new Dictionary 38 | { 39 | { mono_get_root_domain, IntPtr.Zero }, 40 | { mono_thread_attach, IntPtr.Zero }, 41 | { mono_image_open_from_data, IntPtr.Zero }, 42 | { mono_assembly_load_from_full, IntPtr.Zero }, 43 | { mono_assembly_get_image, IntPtr.Zero }, 44 | { mono_class_from_name, IntPtr.Zero }, 45 | { mono_class_get_method_from_name, IntPtr.Zero }, 46 | { mono_runtime_invoke, IntPtr.Zero }, 47 | { mono_assembly_close, IntPtr.Zero }, 48 | { mono_image_strerror, IntPtr.Zero }, 49 | { mono_object_get_class, IntPtr.Zero }, 50 | { mono_class_get_name, IntPtr.Zero } 51 | }; 52 | 53 | private Memory _memory; 54 | 55 | private IntPtr _rootDomain; 56 | 57 | private bool _attach; 58 | 59 | private readonly IntPtr _handle; 60 | 61 | private IntPtr _mono; 62 | 63 | public bool Is64Bit { get; private set; } 64 | 65 | public Injector(string processName) 66 | { 67 | Process process = Process.GetProcesses() 68 | .FirstOrDefault(p => p.ProcessName 69 | .Equals(processName, StringComparison.OrdinalIgnoreCase)); 70 | 71 | if (process == null) 72 | throw new InjectorException($"Could not find a process with the name {processName}"); 73 | 74 | if ((_handle = Native.OpenProcess(ProcessAccessRights.PROCESS_ALL_ACCESS, false, process.Id)) == IntPtr.Zero) 75 | throw new InjectorException("Failed to open process", new Win32Exception(Marshal.GetLastWin32Error())); 76 | 77 | Is64Bit = ProcessUtils.Is64BitProcess(_handle); 78 | 79 | if (!ProcessUtils.GetMonoModule(_handle, out _mono)) 80 | throw new InjectorException("Failed to find mono.dll in the target process"); 81 | 82 | _memory = new Memory(_handle); 83 | } 84 | 85 | public Injector(int processId) 86 | { 87 | Process process = Process.GetProcesses() 88 | .FirstOrDefault(p => p.Id == processId); 89 | 90 | if (process == null) 91 | throw new InjectorException($"Could not find a process with the id {processId}"); 92 | 93 | if ((_handle = Native.OpenProcess(ProcessAccessRights.PROCESS_ALL_ACCESS, false, process.Id)) == IntPtr.Zero) 94 | throw new InjectorException("Failed to open process", new Win32Exception(Marshal.GetLastWin32Error())); 95 | 96 | Is64Bit = ProcessUtils.Is64BitProcess(_handle); 97 | 98 | if (!ProcessUtils.GetMonoModule(_handle, out _mono)) 99 | throw new InjectorException("Failed to find mono.dll in the target process"); 100 | 101 | _memory = new Memory(_handle); 102 | } 103 | 104 | public Injector(IntPtr processHandle, IntPtr monoModule) 105 | { 106 | if ((_handle = processHandle) == IntPtr.Zero) 107 | throw new ArgumentException("Argument cannot be zero", nameof(processHandle)); 108 | 109 | if ((_mono = monoModule) == IntPtr.Zero) 110 | throw new ArgumentException("Argument cannot be zero", nameof(monoModule)); 111 | 112 | Is64Bit = ProcessUtils.Is64BitProcess(_handle); 113 | _memory = new Memory(_handle); 114 | } 115 | 116 | public void Dispose() 117 | { 118 | _memory.Dispose(); 119 | Native.CloseHandle(_handle); 120 | } 121 | 122 | private void ObtainMonoExports() 123 | { 124 | foreach (ExportedFunction ef in ProcessUtils.GetExportedFunctions(_handle, _mono)) 125 | if (Exports.ContainsKey(ef.Name)) 126 | Exports[ef.Name] = ef.Address; 127 | 128 | foreach (var kvp in Exports) 129 | if (kvp.Value == IntPtr.Zero) 130 | throw new InjectorException($"Failed to obtain the address of {kvp.Key}()"); 131 | } 132 | 133 | public IntPtr Inject(byte[] rawAssembly, string @namespace, string className, string methodName) 134 | { 135 | if (rawAssembly == null) 136 | throw new ArgumentNullException(nameof(rawAssembly)); 137 | 138 | if (rawAssembly.Length == 0) 139 | throw new ArgumentException($"{nameof(rawAssembly)} cannot be empty", nameof(rawAssembly)); 140 | 141 | if (className == null) 142 | throw new ArgumentNullException(nameof(className)); 143 | 144 | if (methodName == null) 145 | throw new ArgumentNullException(nameof(methodName)); 146 | 147 | IntPtr rawImage, assembly, image, @class, method; 148 | 149 | ObtainMonoExports(); 150 | _rootDomain = GetRootDomain(); 151 | rawImage = OpenImageFromData(rawAssembly); 152 | _attach = true; 153 | assembly = OpenAssemblyFromImage(rawImage); 154 | image = GetImageFromAssembly(assembly); 155 | @class = GetClassFromName(image, @namespace, className); 156 | method = GetMethodFromName(@class, methodName); 157 | RuntimeInvoke(method); 158 | return assembly; 159 | } 160 | 161 | public void Eject(IntPtr assembly, string @namespace, string className, string methodName) 162 | { 163 | if (assembly == IntPtr.Zero) 164 | throw new ArgumentException($"{nameof(assembly)} cannot be zero", nameof(assembly)); 165 | 166 | if (className == null) 167 | throw new ArgumentNullException(nameof(className)); 168 | 169 | if (methodName == null) 170 | throw new ArgumentNullException(nameof(methodName)); 171 | 172 | IntPtr image, @class, method; 173 | 174 | ObtainMonoExports(); 175 | _rootDomain = GetRootDomain(); 176 | _attach = true; 177 | image = GetImageFromAssembly(assembly); 178 | @class = GetClassFromName(image, @namespace, className); 179 | method = GetMethodFromName(@class, methodName); 180 | RuntimeInvoke(method); 181 | CloseAssembly(assembly); 182 | } 183 | 184 | private static void ThrowIfNull(IntPtr ptr, string methodName) 185 | { 186 | if (ptr == IntPtr.Zero) 187 | throw new InjectorException($"{methodName}() returned NULL"); 188 | } 189 | 190 | private IntPtr GetRootDomain() 191 | { 192 | IntPtr rootDomain = Execute(Exports[mono_get_root_domain]); 193 | ThrowIfNull(rootDomain, mono_get_root_domain); 194 | return rootDomain; 195 | } 196 | 197 | private IntPtr OpenImageFromData(byte[] assembly) 198 | { 199 | IntPtr statusPtr = _memory.Allocate(4); 200 | IntPtr rawImage = Execute(Exports[mono_image_open_from_data], 201 | _memory.AllocateAndWrite(assembly), (IntPtr)assembly.Length, (IntPtr)1, statusPtr); 202 | 203 | MonoImageOpenStatus status = (MonoImageOpenStatus)_memory.ReadInt(statusPtr); 204 | 205 | if (status != MonoImageOpenStatus.MONO_IMAGE_OK) { 206 | IntPtr messagePtr = Execute(Exports[mono_image_strerror], (IntPtr)status); 207 | string message = _memory.ReadString(messagePtr, 256, Encoding.UTF8); 208 | throw new InjectorException($"{mono_image_open_from_data}() failed: {message}"); 209 | } 210 | 211 | return rawImage; 212 | } 213 | 214 | private IntPtr OpenAssemblyFromImage(IntPtr image) 215 | { 216 | IntPtr statusPtr = _memory.Allocate(4); 217 | IntPtr assembly = Execute(Exports[mono_assembly_load_from_full], 218 | image, _memory.AllocateAndWrite(new byte[1]), statusPtr, IntPtr.Zero); 219 | 220 | MonoImageOpenStatus status = (MonoImageOpenStatus)_memory.ReadInt(statusPtr); 221 | 222 | if (status != MonoImageOpenStatus.MONO_IMAGE_OK) { 223 | IntPtr messagePtr = Execute(Exports[mono_image_strerror], (IntPtr)status); 224 | string message = _memory.ReadString(messagePtr, 256, Encoding.UTF8); 225 | throw new InjectorException($"{mono_assembly_load_from_full}() failed: {message}"); 226 | } 227 | 228 | return assembly; 229 | } 230 | 231 | private IntPtr GetImageFromAssembly(IntPtr assembly) 232 | { 233 | IntPtr image = Execute(Exports[mono_assembly_get_image], assembly); 234 | ThrowIfNull(image, mono_assembly_get_image); 235 | return image; 236 | } 237 | 238 | private IntPtr GetClassFromName(IntPtr image, string @namespace, string className) 239 | { 240 | IntPtr @class = Execute(Exports[mono_class_from_name], 241 | image, _memory.AllocateAndWrite(@namespace), _memory.AllocateAndWrite(className)); 242 | ThrowIfNull(@class, mono_class_from_name); 243 | return @class; 244 | } 245 | 246 | private IntPtr GetMethodFromName(IntPtr @class, string methodName) 247 | { 248 | IntPtr method = Execute(Exports[mono_class_get_method_from_name], 249 | @class, _memory.AllocateAndWrite(methodName), IntPtr.Zero); 250 | ThrowIfNull(method, mono_class_get_method_from_name); 251 | return method; 252 | } 253 | 254 | private string GetClassName(IntPtr monoObject) 255 | { 256 | IntPtr @class = Execute(Exports[mono_object_get_class], monoObject); 257 | ThrowIfNull(@class, mono_object_get_class); 258 | IntPtr className = Execute(Exports[mono_class_get_name], @class); 259 | ThrowIfNull(className, mono_class_get_name); 260 | return _memory.ReadString(className, 256, Encoding.UTF8); 261 | } 262 | 263 | private string ReadMonoString(IntPtr monoString) 264 | { 265 | int len = _memory.ReadInt(monoString + (Is64Bit ? 0x10 : 0x8)); 266 | return _memory.ReadUnicodeString(monoString + (Is64Bit ? 0x14 : 0xC), len * 2); 267 | } 268 | 269 | private void RuntimeInvoke(IntPtr method) 270 | { 271 | IntPtr excPtr = Is64Bit ? _memory.AllocateAndWrite((long)0) : _memory.AllocateAndWrite(0); 272 | 273 | IntPtr result = Execute(Exports[mono_runtime_invoke], 274 | method, IntPtr.Zero, IntPtr.Zero, excPtr); 275 | 276 | IntPtr exc = (IntPtr)_memory.ReadInt(excPtr); 277 | 278 | if (exc != IntPtr.Zero) { 279 | string className = GetClassName(exc); 280 | string message = ReadMonoString((IntPtr)_memory.ReadInt(exc + (Is64Bit ? 0x20 : 0x10))); 281 | throw new InjectorException($"The managed method threw an exception: ({className}) {message}"); 282 | } 283 | } 284 | 285 | private void CloseAssembly(IntPtr assembly) 286 | { 287 | IntPtr result = Execute(Exports[mono_assembly_close], assembly); 288 | ThrowIfNull(result, mono_assembly_close); 289 | } 290 | 291 | private IntPtr Execute(IntPtr address, params IntPtr[] args) 292 | { 293 | IntPtr retValPtr = Is64Bit 294 | ? _memory.AllocateAndWrite((long)0) 295 | : _memory.AllocateAndWrite(0); 296 | 297 | byte[] code = Assemble(address, retValPtr, args); 298 | IntPtr alloc = _memory.AllocateAndWrite(code); 299 | 300 | IntPtr thread = Native.CreateRemoteThread( 301 | _handle, IntPtr.Zero, 0, alloc, IntPtr.Zero, 0, out _); 302 | 303 | if (thread == IntPtr.Zero) 304 | throw new InjectorException("Failed to create a remote thread", new Win32Exception(Marshal.GetLastWin32Error())); 305 | 306 | WaitResult result = Native.WaitForSingleObject(thread, -1); 307 | 308 | if (result == WaitResult.WAIT_FAILED) 309 | throw new InjectorException("Failed to wait for a remote thread", new Win32Exception(Marshal.GetLastWin32Error())); 310 | 311 | IntPtr ret = Is64Bit 312 | ? (IntPtr)_memory.ReadLong(retValPtr) 313 | : (IntPtr)_memory.ReadInt(retValPtr); 314 | 315 | if ((long)ret == 0x00000000C0000005) 316 | throw new InjectorException($"An access violation occurred while executing {Exports.First(e => e.Value == address).Key}()"); 317 | 318 | return ret; 319 | } 320 | 321 | private byte[] Assemble(IntPtr functionPtr, IntPtr retValPtr, IntPtr[] args) 322 | { 323 | return Is64Bit 324 | ? Assemble64(functionPtr, retValPtr, args) 325 | : Assemble86(functionPtr, retValPtr, args); 326 | } 327 | 328 | private byte[] Assemble86(IntPtr functionPtr, IntPtr retValPtr, IntPtr[] args) 329 | { 330 | Assembler asm = new Assembler(); 331 | 332 | if (_attach) { 333 | asm.Push(_rootDomain); 334 | asm.MovEax(Exports[mono_thread_attach]); 335 | asm.CallEax(); 336 | asm.AddEsp(4); 337 | } 338 | 339 | for (int i = args.Length - 1; i >= 0; i--) 340 | asm.Push(args[i]); 341 | 342 | asm.MovEax(functionPtr); 343 | asm.CallEax(); 344 | asm.AddEsp((byte)(args.Length * 4)); 345 | asm.MovEaxTo(retValPtr); 346 | asm.Return(); 347 | 348 | return asm.ToByteArray(); 349 | } 350 | 351 | private byte[] Assemble64(IntPtr functionPtr, IntPtr retValPtr, IntPtr[] args) 352 | { 353 | Assembler asm = new Assembler(); 354 | 355 | asm.SubRsp(40); 356 | 357 | if (_attach) { 358 | asm.MovRax(Exports[mono_thread_attach]); 359 | asm.MovRcx(_rootDomain); 360 | asm.CallRax(); 361 | } 362 | 363 | asm.MovRax(functionPtr); 364 | 365 | for (int i = 0; i < args.Length; i++) { 366 | switch (i) { 367 | case 0: 368 | asm.MovRcx(args[i]); 369 | break; 370 | case 1: 371 | asm.MovRdx(args[i]); 372 | break; 373 | case 2: 374 | asm.MovR8(args[i]); 375 | break; 376 | case 3: 377 | asm.MovR9(args[i]); 378 | break; 379 | } 380 | } 381 | 382 | asm.CallRax(); 383 | asm.AddRsp(40); 384 | asm.MovRaxTo(retValPtr); 385 | asm.Return(); 386 | 387 | return asm.ToByteArray(); 388 | } 389 | } 390 | } 391 | -------------------------------------------------------------------------------- /SharpMonoInjector/InjectorException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SharpMonoInjector 4 | { 5 | public class InjectorException : Exception 6 | { 7 | public InjectorException(string message) : base(message) 8 | { 9 | } 10 | 11 | public InjectorException(string message, Exception innerException) : base(message, innerException) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /SharpMonoInjector/Memory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | 7 | namespace SharpMonoInjector 8 | { 9 | public class Memory : IDisposable 10 | { 11 | private readonly IntPtr _handle; 12 | 13 | private readonly Dictionary _allocations = new Dictionary(); 14 | 15 | public Memory(IntPtr processHandle) 16 | { 17 | _handle = processHandle; 18 | } 19 | 20 | public string ReadString(IntPtr address, int length, Encoding encoding) 21 | { 22 | List bytes = new List(); 23 | 24 | for (int i = 0; i < length; i++) { 25 | byte read = ReadBytes(address + bytes.Count, 1)[0]; 26 | 27 | if (read == 0x00) 28 | break; 29 | 30 | bytes.Add(read); 31 | } 32 | 33 | return encoding.GetString(bytes.ToArray()); 34 | } 35 | 36 | public string ReadUnicodeString(IntPtr address, int length) 37 | { 38 | return Encoding.Unicode.GetString(ReadBytes(address, length)); 39 | } 40 | 41 | public short ReadShort(IntPtr address) 42 | { 43 | return BitConverter.ToInt16(ReadBytes(address, 2), 0); 44 | } 45 | 46 | public int ReadInt(IntPtr address) 47 | { 48 | return BitConverter.ToInt32(ReadBytes(address, 4), 0); 49 | } 50 | 51 | public long ReadLong(IntPtr address) 52 | { 53 | return BitConverter.ToInt64(ReadBytes(address, 8), 0); 54 | } 55 | 56 | public byte[] ReadBytes(IntPtr address, int size) 57 | { 58 | byte[] bytes = new byte[size]; 59 | 60 | if (!Native.ReadProcessMemory(_handle, address, bytes, size)) 61 | throw new InjectorException("Failed to read process memory", new Win32Exception(Marshal.GetLastWin32Error())); 62 | 63 | return bytes; 64 | } 65 | 66 | public IntPtr AllocateAndWrite(byte[] data) 67 | { 68 | IntPtr addr = Allocate(data.Length); 69 | Write(addr, data); 70 | return addr; 71 | } 72 | 73 | public IntPtr AllocateAndWrite(string data) => AllocateAndWrite(Encoding.UTF8.GetBytes(data)); 74 | 75 | public IntPtr AllocateAndWrite(int data) => AllocateAndWrite(BitConverter.GetBytes(data)); 76 | 77 | public IntPtr AllocateAndWrite(long data) => AllocateAndWrite(BitConverter.GetBytes(data)); 78 | 79 | public IntPtr Allocate(int size) 80 | { 81 | IntPtr addr = 82 | Native.VirtualAllocEx(_handle, IntPtr.Zero, size, 83 | AllocationType.MEM_COMMIT, MemoryProtection.PAGE_EXECUTE_READWRITE); 84 | 85 | if (addr == IntPtr.Zero) 86 | throw new InjectorException("Failed to allocate process memory", new Win32Exception(Marshal.GetLastWin32Error())); 87 | 88 | _allocations.Add(addr, size); 89 | return addr; 90 | } 91 | 92 | public void Write(IntPtr addr, byte[] data) 93 | { 94 | if (!Native.WriteProcessMemory(_handle, addr, data, data.Length)) 95 | throw new InjectorException("Failed to write process memory", new Win32Exception(Marshal.GetLastWin32Error())); 96 | } 97 | 98 | public void Dispose() 99 | { 100 | foreach (var kvp in _allocations) 101 | Native.VirtualFreeEx(_handle, kvp.Key, kvp.Value, MemoryFreeType.MEM_DECOMMIT); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /SharpMonoInjector/MonoImageOpenStatus.cs: -------------------------------------------------------------------------------- 1 | namespace SharpMonoInjector 2 | { 3 | public enum MonoImageOpenStatus 4 | { 5 | MONO_IMAGE_OK, 6 | MONO_IMAGE_ERROR_ERRNO, 7 | MONO_IMAGE_MISSING_ASSEMBLYREF, 8 | MONO_IMAGE_IMAGE_INVALID 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /SharpMonoInjector/Native.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Text; 4 | 5 | namespace SharpMonoInjector 6 | { 7 | [StructLayout(LayoutKind.Sequential)] 8 | public struct MODULEINFO 9 | { 10 | public IntPtr lpBaseOfDll; 11 | 12 | public int SizeOfImage; 13 | 14 | public IntPtr EntryPoint; 15 | } 16 | 17 | public enum ModuleFilter : uint 18 | { 19 | LIST_MODULES_DEFAULT = 0x0, 20 | LIST_MODULES_32BIT = 0x01, 21 | LIST_MODULES_64BIT = 0x02, 22 | LIST_MODULES_ALL = 0x03 23 | } 24 | 25 | [Flags] 26 | public enum AllocationType 27 | { 28 | MEM_COMMIT = 0x00001000, 29 | MEM_RESERVE = 0x00002000, 30 | MEM_RESET = 0x00080000, 31 | MEM_RESET_UNDO = 0x1000000, 32 | MEM_LARGE_PAGES = 0x20000000, 33 | MEM_PHYSICAL = 0x00400000, 34 | MEM_TOP_DOWN = 0x00100000 35 | } 36 | 37 | [Flags] 38 | public enum MemoryProtection 39 | { 40 | PAGE_EXECUTE = 0x10, 41 | PAGE_EXECUTE_READ = 0x20, 42 | PAGE_EXECUTE_READWRITE = 0x40, 43 | PAGE_EXECUTE_WRITECOPY = 0x80, 44 | PAGE_NOACCESS = 0x01, 45 | PAGE_READONLY = 0x02, 46 | PAGE_READWRITE = 0x4, 47 | PAGE_WRITECOPY = 0x8, 48 | PAGE_TARGETS_INVALID = 0x40000000, 49 | PAGE_TARGETS_NO_UPDATE = 0x40000000, 50 | PAGE_GUARD = 0x100, 51 | PAGE_NOCACHE = 0x200, 52 | PAGE_WRITECOMBINE = 0x400 53 | } 54 | 55 | [Flags] 56 | public enum MemoryFreeType 57 | { 58 | MEM_DECOMMIT = 0x4000, 59 | MEM_RELEASE = 0x8000 60 | } 61 | 62 | [Flags] 63 | public enum ThreadCreationFlags 64 | { 65 | None = 0, 66 | CREATE_SUSPENDED = 0x00000004, 67 | STACK_SIZE_PARAM_IS_A_RESERVATION = 0x00010000 68 | } 69 | 70 | public enum WaitResult : uint 71 | { 72 | WAIT_ABANDONED = 0x00000080, 73 | WAIT_OBJECT_0 = 0x00000000, 74 | WAIT_TIMEOUT = 0x00000102, 75 | WAIT_FAILED = 0xFFFFFFFF 76 | } 77 | 78 | [Flags] 79 | public enum ProcessAccessRights : uint 80 | { 81 | PROCESS_ALL_ACCESS = 0x1FFFFF, 82 | PROCESS_CREATE_PROCESS = 0x0080, 83 | PROCESS_CREATE_THREAD = 0x0002, 84 | PROCESS_DUP_HANDLE = 0x0040, 85 | PROCESS_QUERY_INFORMATION = 0x0400, 86 | PROCESS_QUERY_LIMITED_INFORMATION = 0x1000, 87 | PROCESS_SET_INFORMATION = 0x0200, 88 | PROCESS_SET_QUOTA = 0x0100, 89 | PROCESS_SUSPEND_RESUME = 0x0800, 90 | PROCESS_TERMINATE = 0x0001, 91 | PROCESS_VM_OPERATION = 0x0008, 92 | PROCESS_VM_READ = 0x0010, 93 | PROCESS_VM_WRITE = 0x0020, 94 | SYNCHRONIZE = 0x00100000 95 | } 96 | 97 | public static class Native 98 | { 99 | [DllImport("kernel32.dll", SetLastError = true)] 100 | public static extern IntPtr OpenProcess(ProcessAccessRights dwDesiredAccess, bool bInheritHandle, int processId); 101 | 102 | [DllImport("kernel32.dll", SetLastError = true)] 103 | public static extern bool CloseHandle(IntPtr handle); 104 | 105 | [DllImport("kernel32.dll")] 106 | [return: MarshalAs(UnmanagedType.Bool)] 107 | public static extern bool IsWow64Process(IntPtr hProcess, out bool wow64Process); 108 | 109 | [DllImport("psapi.dll", SetLastError = true)] 110 | public static extern bool EnumProcessModulesEx(IntPtr hProcess, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U4)] [In][Out] IntPtr[] lphModule, int cb, [MarshalAs(UnmanagedType.U4)] out int lpcbNeeded, ModuleFilter dwFilterFlag); 111 | 112 | [DllImport("psapi.dll")] 113 | public static extern uint GetModuleFileNameEx(IntPtr hProcess, IntPtr hModule, [Out] StringBuilder lpBaseName, [In] [MarshalAs(UnmanagedType.U4)] uint nSize); 114 | 115 | [DllImport("psapi.dll", SetLastError = true)] 116 | public static extern bool GetModuleInformation(IntPtr hProcess, IntPtr hModule, out MODULEINFO lpmodinfo, uint cb); 117 | 118 | [DllImport("kernel32.dll", SetLastError = true)] 119 | [return: MarshalAs(UnmanagedType.Bool)] 120 | public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, int lpNumberOfBytesWritten = 0); 121 | 122 | [DllImport("kernel32.dll", SetLastError = true)] 123 | [return: MarshalAs(UnmanagedType.Bool)] 124 | public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, int lpNumberOfBytesRead = 0); 125 | 126 | [DllImport("kernel32.dll", SetLastError = true)] 127 | public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, AllocationType flAllocationType, MemoryProtection flProtect); 128 | 129 | [DllImport("kernel32.dll", SetLastError = true)] 130 | [return: MarshalAs(UnmanagedType.Bool)] 131 | public static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, MemoryFreeType dwFreeType); 132 | 133 | [DllImport("kernel32.dll", SetLastError = true)] 134 | public static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, int dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, ThreadCreationFlags dwCreationFlags, out int lpThreadId); 135 | 136 | [DllImport("kernel32.dll", SetLastError = true)] 137 | public static extern WaitResult WaitForSingleObject(IntPtr hHandle, int dwMilliseconds); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /SharpMonoInjector/ProcessUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | 8 | namespace SharpMonoInjector 9 | { 10 | public static class ProcessUtils 11 | { 12 | public static IEnumerable GetExportedFunctions(IntPtr handle, IntPtr mod) 13 | { 14 | using (Memory memory = new Memory(handle)) { 15 | int e_lfanew = memory.ReadInt(mod + 0x3C); 16 | IntPtr ntHeaders = mod + e_lfanew; 17 | IntPtr optionalHeader = ntHeaders + 0x18; 18 | IntPtr dataDirectory = optionalHeader + (Is64BitProcess(handle) ? 0x70 : 0x60); 19 | IntPtr exportDirectory = mod + memory.ReadInt(dataDirectory); 20 | IntPtr names = mod + memory.ReadInt(exportDirectory + 0x20); 21 | IntPtr ordinals = mod + memory.ReadInt(exportDirectory + 0x24); 22 | IntPtr functions = mod + memory.ReadInt(exportDirectory + 0x1C); 23 | int count = memory.ReadInt(exportDirectory + 0x18); 24 | 25 | for (int i = 0; i < count; i++) { 26 | int offset = memory.ReadInt(names + i * 4); 27 | string name = memory.ReadString(mod + offset, 32, Encoding.ASCII); 28 | short ordinal = memory.ReadShort(ordinals + i * 2); 29 | IntPtr address = mod + memory.ReadInt(functions + ordinal * 4); 30 | 31 | if (address != IntPtr.Zero) 32 | yield return new ExportedFunction(name, address); 33 | } 34 | } 35 | } 36 | 37 | public static bool GetMonoModule(IntPtr handle, out IntPtr monoModule) 38 | { 39 | int size = Is64BitProcess(handle) ? 8 : 4; 40 | 41 | IntPtr[] ptrs = new IntPtr[0]; 42 | 43 | if (!Native.EnumProcessModulesEx( 44 | handle, ptrs, 0, out int bytesNeeded, ModuleFilter.LIST_MODULES_ALL)) { 45 | throw new InjectorException("Failed to enumerate process modules", new Win32Exception(Marshal.GetLastWin32Error())); 46 | } 47 | 48 | int count = bytesNeeded / size; 49 | ptrs = new IntPtr[count]; 50 | 51 | if (!Native.EnumProcessModulesEx( 52 | handle, ptrs, bytesNeeded, out bytesNeeded, ModuleFilter.LIST_MODULES_ALL)) { 53 | throw new InjectorException("Failed to enumerate process modules", new Win32Exception(Marshal.GetLastWin32Error())); 54 | } 55 | 56 | for (int i = 0; i < count; i++) { 57 | StringBuilder path = new StringBuilder(260); 58 | Native.GetModuleFileNameEx(handle, ptrs[i], path, 260); 59 | 60 | if (path.ToString().IndexOf("mono", StringComparison.OrdinalIgnoreCase) > -1) { 61 | if (!Native.GetModuleInformation(handle, ptrs[i], out MODULEINFO info, (uint)(size * ptrs.Length))) 62 | throw new InjectorException("Failed to get module information", new Win32Exception(Marshal.GetLastWin32Error())); 63 | 64 | var funcs = GetExportedFunctions(handle, info.lpBaseOfDll); 65 | 66 | if (funcs.Any(f => f.Name == "mono_get_root_domain")) { 67 | monoModule = info.lpBaseOfDll; 68 | return true; 69 | } 70 | } 71 | } 72 | 73 | monoModule = IntPtr.Zero; 74 | return false; 75 | } 76 | 77 | public static bool Is64BitProcess(IntPtr handle) 78 | { 79 | if (!Environment.Is64BitOperatingSystem) 80 | return false; 81 | 82 | if (!Native.IsWow64Process(handle, out bool isWow64)) 83 | return IntPtr.Size == 8; // assume it's the same as the current process 84 | 85 | return !isWow64; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /SharpMonoInjector/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 SharpMonoInjector.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.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 | -------------------------------------------------------------------------------- /SharpMonoInjector/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /SharpMonoInjector/SharpMonoInjector.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461 5 | AnyCPU 6 | 7 | 8 | 9 | ..\..\build\debug 10 | 11 | 12 | 13 | ..\..\build\release 14 | 15 | 16 | 17 | 18 | True 19 | True 20 | Settings.settings 21 | 22 | 23 | 24 | 25 | 26 | SettingsSingleFileGenerator 27 | Settings.Designer.cs 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /UnityInspector.Communicator/Communicator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Net.Sockets; 5 | using System.Text; 6 | 7 | namespace UnityInspector.Communicator 8 | { 9 | public abstract class Communicator 10 | { 11 | public static Communicator Instance { get; set; } 12 | private const int Port = 23123; 13 | public enum Commands : byte 14 | { 15 | Destroy = 0, 16 | GetHierarchy = 1, 17 | GetChildren = 2, 18 | InvokeMethod = 3, 19 | GetComponents = 4, 20 | GetComponentType = 5, 21 | GetComponentMembers = 6, 22 | GetValueOfMember = 7, 23 | SetValueOfMember = 8 24 | } 25 | public enum Types : byte 26 | { 27 | Undefined = 0, 28 | Unknown = 1, 29 | Bool = 2, 30 | Byte = 3, 31 | Short = 4, 32 | Int = 5, 33 | Long = 6, 34 | Float = 7, 35 | Double = 8, 36 | String = 9, 37 | Array = 10, 38 | ArrayAsMember = 11, 39 | GameObject = 12, 40 | Vector3 = 14, 41 | Enum = 15, 42 | Color = 16, 43 | Vector2 = 17, 44 | Rect = 18, 45 | } 46 | public enum CommunicatorType 47 | { 48 | Server, 49 | Clinet 50 | } 51 | protected TcpListener server; 52 | protected TcpClient client; 53 | private NetworkStream pipe; 54 | private ASCIIEncoding stringEncoding; 55 | 56 | public Communicator (CommunicatorType communicatorType) 57 | { 58 | Instance = this; 59 | stringEncoding = new ASCIIEncoding (); 60 | try 61 | { 62 | if (communicatorType == CommunicatorType.Server) 63 | { 64 | server = new TcpListener (IPAddress.Any, Port); 65 | server.Start (); 66 | } 67 | else 68 | { 69 | client = new TcpClient ("localhost", Port); 70 | pipe = client.GetStream (); 71 | } 72 | } 73 | catch (Exception ex) 74 | { 75 | Console.WriteLine (ex); 76 | } 77 | } 78 | public abstract void Close(); 79 | public bool Connected { get { return client.Connected; } } 80 | public bool DataAvailable () 81 | { 82 | if (client != null && client.Connected) 83 | return pipe.DataAvailable; 84 | 85 | if (server.Pending ()) 86 | { 87 | client = server.AcceptTcpClient(); 88 | pipe = client.GetStream(); 89 | } 90 | return false; 91 | } 92 | 93 | public void WriteBool (bool value) 94 | { 95 | pipe.Write (BitConverter.GetBytes (value), 0, 1); 96 | } 97 | public bool ReadBool () 98 | { 99 | byte[] inBuffer = new byte[1]; 100 | pipe.Read (inBuffer, 0, 1); 101 | return BitConverter.ToBoolean (inBuffer, 0); 102 | } 103 | 104 | public void WriteByte (byte value) 105 | { 106 | pipe.WriteByte (value); 107 | } 108 | public byte ReadByte () 109 | { 110 | return (byte) pipe.ReadByte (); 111 | } 112 | 113 | public void WriteShort (short value) 114 | { 115 | pipe.Write (BitConverter.GetBytes (value), 0, 2); 116 | } 117 | public short ReadShort () 118 | { 119 | byte[] inBuffer = new byte[2]; 120 | pipe.Read (inBuffer, 0, 2); 121 | return BitConverter.ToInt16 (inBuffer, 0); 122 | } 123 | 124 | public void WriteInt (int value) 125 | { 126 | pipe.Write (BitConverter.GetBytes (value), 0, 4); 127 | } 128 | public int ReadInt () 129 | { 130 | byte[] inBuffer = new byte[4]; 131 | pipe.Read (inBuffer, 0, 4); 132 | return BitConverter.ToInt32 (inBuffer, 0); 133 | } 134 | 135 | public void WriteLong (long value) 136 | { 137 | pipe.Write (BitConverter.GetBytes (value), 0, 8); 138 | pipe.Flush (); 139 | } 140 | public long ReadLong () 141 | { 142 | byte[] inBuffer = new byte[8]; 143 | pipe.Read (inBuffer, 0, 8); 144 | return BitConverter.ToInt64 (inBuffer, 0); 145 | } 146 | 147 | public void WriteFloat (float value) 148 | { 149 | pipe.Write (BitConverter.GetBytes (value), 0, 4); 150 | pipe.Flush (); 151 | } 152 | public float ReadFloat () 153 | { 154 | byte[] inBuffer = new byte[4]; 155 | pipe.Read (inBuffer, 0, 4); 156 | return BitConverter.ToSingle (inBuffer, 0); 157 | } 158 | 159 | public void WriteDouble (double value) 160 | { 161 | pipe.Write (BitConverter.GetBytes (value), 0, 8); 162 | pipe.Flush (); 163 | } 164 | public double ReadDouble () 165 | { 166 | byte[] inBuffer = new byte[8]; 167 | pipe.Read (inBuffer, 0, 8); 168 | return BitConverter.ToDouble (inBuffer, 0); 169 | } 170 | 171 | public void WriteString (string value) 172 | { 173 | if (value.Length > 255) 174 | Console.Error.WriteLine ("The length of the string must be lower than 256"); 175 | byte len = (byte) value.Length; 176 | pipe.WriteByte (len); 177 | if (len != 0) 178 | pipe.Write (stringEncoding.GetBytes (value), 0, len); 179 | } 180 | public string ReadString () 181 | { 182 | int len = pipe.ReadByte (); 183 | if (len == 0) 184 | return ""; 185 | byte[] inBuffer = new byte[len]; 186 | pipe.Read (inBuffer, 0, len); 187 | return stringEncoding.GetString (inBuffer); 188 | } 189 | 190 | public void WriteArrayAsMember (Array value) 191 | { 192 | if (value != null && value.Length > 0) 193 | { 194 | WriteInt (value.Length); 195 | WriteByte ((byte) GetObjectType (value.GetValue (0))); 196 | } 197 | else 198 | { 199 | WriteInt (0); 200 | WriteByte ((byte) Types.Undefined); 201 | } 202 | } 203 | public abstract object ReadArrayAsMember (); 204 | 205 | public void WriteArray (Array value) 206 | { 207 | if (value != null && value.Length > 0) 208 | { 209 | Types firstElementType = GetObjectType (value.GetValue (0)); 210 | WriteInt (value.Length); 211 | WriteByte ((byte) firstElementType); 212 | for (int i = 0; i < value.Length; i++) 213 | { 214 | WriteObject (value.GetValue (i), true, firstElementType); 215 | } 216 | } 217 | else 218 | { 219 | WriteInt (0); 220 | } 221 | } 222 | public Array ReadArray () 223 | { 224 | int len = ReadInt (); 225 | Array values = new object[len]; 226 | Types knownType = Types.Undefined; 227 | if (len > 0) 228 | knownType = (Types) ReadByte (); 229 | 230 | for (int i = 0; i < len; i++) 231 | { 232 | values.SetValue(ReadObject (knownType), i); 233 | } 234 | return values; 235 | } 236 | 237 | public void WriteObjects (object[] objects) 238 | { 239 | WriteInt (objects.Length); 240 | for (int i = 0; i < objects.Length; i++) 241 | { 242 | object obj = objects[i]; 243 | WriteObject (obj); 244 | } 245 | } 246 | public object[] ReadObjects () 247 | { 248 | int len = ReadInt (); 249 | object[] objects = new object[len]; 250 | for (int i = 0; i < len; i++) 251 | { 252 | objects[i] = ReadObject (); 253 | } 254 | return objects; 255 | } 256 | 257 | public static Types GetObjectType (object obj, bool IsMember = false) 258 | { 259 | if (obj is bool) 260 | return Types.Bool; 261 | if (obj is byte) 262 | return Types.Byte; 263 | if (obj is short) 264 | return Types.Short; 265 | if (obj is int) 266 | return Types.Int; 267 | if (obj is long) 268 | return Types.Long; 269 | if (obj is float) 270 | return Types.Float; 271 | if (obj is double) 272 | return Types.Double; 273 | if (obj is string) 274 | return Types.String; 275 | if (obj is Array && IsMember) 276 | return Types.ArrayAsMember; 277 | if (obj is Array) 278 | return Types.Array; 279 | if (obj is T) 280 | return Types.GameObject; 281 | if (obj != null && obj.GetType ().Name == "Vector3") 282 | return Types.Vector3; 283 | if (obj is Enum || (obj != null && obj.GetType ().Name == "EnumMember")) 284 | return Types.Enum; 285 | if (obj != null && obj.GetType ().Name.StartsWith ("Color")) 286 | return Types.Color; 287 | if (obj != null && obj.GetType ().Name == "Vector2") 288 | return Types.Vector2; 289 | if (obj != null && obj.GetType ().Name == "Rect") 290 | return Types.Rect; 291 | return Types.Unknown; 292 | } 293 | public void WriteObject (object obj, bool IsMember = false, Types knownType = Types.Undefined) 294 | { 295 | Types objType = knownType; 296 | if (knownType == Types.Undefined) 297 | { 298 | objType = GetObjectType (obj, IsMember); 299 | WriteByte ((byte) objType); 300 | } 301 | switch (objType) 302 | { 303 | case Types.Bool: 304 | WriteBool ((bool) obj); 305 | break; 306 | case Types.Byte: 307 | WriteByte ((byte) obj); 308 | break; 309 | case Types.Short: 310 | WriteShort ((short) obj); 311 | break; 312 | case Types.Int: 313 | WriteInt ((int) obj); 314 | break; 315 | case Types.Long: 316 | WriteLong ((long) obj); 317 | break; 318 | case Types.Float: 319 | WriteFloat ((float) obj); 320 | break; 321 | case Types.Double: 322 | WriteDouble ((double) obj); 323 | break; 324 | case Types.String: 325 | WriteString ((string) obj); 326 | break; 327 | case Types.Array: 328 | WriteArray ((Array) obj); 329 | break; 330 | case Types.ArrayAsMember: 331 | WriteArrayAsMember ((Array) obj); 332 | break; 333 | case Types.GameObject: 334 | WriteGameObject ((T) obj); 335 | break; 336 | case Types.Vector3: 337 | WriteVector3 (obj); 338 | break; 339 | case Types.Enum: 340 | WriteEnum (obj); 341 | break; 342 | case Types.Color: 343 | WriteColor (obj); 344 | break; 345 | case Types.Vector2: 346 | WriteVector2 (obj); 347 | break; 348 | case Types.Rect: 349 | WriteRect (obj); 350 | break; 351 | case Types.Unknown: 352 | WriteString ((obj==null)?"null":obj.GetType().Name); 353 | break; 354 | 355 | } 356 | } 357 | public object ReadObject (Types knownType = Types.Undefined) 358 | { 359 | Types objType = knownType; 360 | if (knownType == Types.Undefined) 361 | objType = (Types) ReadByte (); 362 | 363 | switch (objType) 364 | { 365 | case Types.Bool: 366 | return ReadBool (); 367 | case Types.Byte: 368 | return ReadByte (); 369 | case Types.Short: 370 | return ReadShort (); 371 | case Types.Int: 372 | return ReadInt (); 373 | case Types.Long: 374 | return ReadLong (); 375 | case Types.Float: 376 | return ReadFloat (); 377 | case Types.Double: 378 | return ReadDouble (); 379 | case Types.String: 380 | return ReadString (); 381 | case Types.Array: 382 | return ReadArray (); 383 | case Types.ArrayAsMember: 384 | return ReadArrayAsMember (); 385 | case Types.GameObject: 386 | return ReadGameObjectByRef (); 387 | case Types.Vector3: 388 | return ReadVector3 (); 389 | case Types.Enum: 390 | return ReadEnum (); 391 | case Types.Color: 392 | return ReadColor (); 393 | case Types.Vector2: 394 | return ReadVector2 (); 395 | case Types.Rect: 396 | return ReadRect (); 397 | case Types.Unknown: 398 | return ReadString (); 399 | } 400 | return null; 401 | } 402 | 403 | public void Flush () 404 | { 405 | pipe.Flush (); 406 | } 407 | 408 | 409 | public abstract void WriteGameObject (T obj); 410 | public abstract T ReadGameObject (); 411 | public abstract void WriteGameObjectRef (T obj); 412 | public abstract T ReadGameObjectByRef (); 413 | 414 | public abstract void WriteVector3 (object value); 415 | public abstract object ReadVector3 (); 416 | 417 | public abstract void WriteVector2 (object value); 418 | public abstract object ReadVector2 (); 419 | 420 | public abstract void WriteRect (object value); 421 | public abstract object ReadRect (); 422 | 423 | public abstract void WriteEnum (object value); 424 | public abstract object ReadEnum (); 425 | 426 | public abstract void WriteColor (object value); 427 | public abstract object ReadColor (); 428 | 429 | 430 | } 431 | } 432 | -------------------------------------------------------------------------------- /UnityInspector.Communicator/UnityInspector.Communicator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {AF3B27C3-615E-46DD-80B0-76C6E106C64D} 8 | Library 9 | Properties 10 | UnityInspector.Communicator 11 | UnityInspector.Communicator 12 | v4.6.1 13 | 512 14 | true 15 | 16 | 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | false 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | false 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /UnityInspector.GUI/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /UnityInspector.GUI/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /UnityInspector.GUI/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | 9 | namespace UnityInspector.GUI 10 | { 11 | /// 12 | /// Interaction logic for App.xaml 13 | /// 14 | public partial class App : Application 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /UnityInspector.GUI/Converters/BooleanConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows.Data; 8 | 9 | namespace UnityInspector.GUI.Converters 10 | { 11 | public class BooleanConverter : IValueConverter 12 | { 13 | public BooleanConverter(T trueValue, T falseValue) 14 | { 15 | True = trueValue; 16 | False = falseValue; 17 | } 18 | 19 | public T True { get; set; } 20 | public T False { get; set; } 21 | 22 | public virtual object Convert(object value, Type targetType, object parameter, CultureInfo culture) 23 | { 24 | return value is bool && ((bool)value) ? True : False; 25 | } 26 | 27 | public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 28 | { 29 | return value is T && EqualityComparer.Default.Equals((T)value, True); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /UnityInspector.GUI/Converters/BooleanToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | 8 | namespace UnityInspector.GUI.Converters 9 | { 10 | public sealed class BooleanToVisibilityConverter : BooleanConverter 11 | { 12 | public BooleanToVisibilityConverter() : 13 | base(Visibility.Visible, Visibility.Collapsed) 14 | { } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /UnityInspector.GUI/Converters/ICollectionToBooleanConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Globalization; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Windows.Data; 9 | 10 | namespace UnityInspector.GUI.Converters 11 | { 12 | class ICollectionToBooleanConverter : IValueConverter 13 | { 14 | public object Convert (object value, Type targetType, object parameter, CultureInfo culture) 15 | { 16 | if (value == null) return false; 17 | return ((ICollection) value).Count > 0; 18 | } 19 | 20 | public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture) 21 | { 22 | throw new NotImplementedException (); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /UnityInspector.GUI/Converters/ObjectMemberTemplateSelector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using UnityInspector.GUI.Models; 9 | using UnityInspector.GUI.ViewModels; 10 | 11 | namespace UnityInspector.GUI.Converters 12 | { 13 | public class ObjectMemberTemplateSelector : DataTemplateSelector 14 | { 15 | public override DataTemplate SelectTemplate (object item, DependencyObject container) 16 | { 17 | FrameworkElement element = container as FrameworkElement; 18 | ObjectMemberViewModel member = item as ObjectMemberViewModel; 19 | if (member.Type == CommunicatorClient.Types.Bool) 20 | { 21 | return element.FindResource ("BoolMemberDataTemplate") as DataTemplate; 22 | } 23 | else if (member.Type > CommunicatorClient.Types.Bool && member.Type < CommunicatorClient.Types.String) 24 | { 25 | return element.FindResource ("NumericMemberDataTemplate") as DataTemplate; 26 | } 27 | else if (member.Type == CommunicatorClient.Types.Array) 28 | { 29 | return element.FindResource ("ArrayMemberDataTemplate") as DataTemplate; 30 | } 31 | else if (member.Type == CommunicatorClient.Types.Vector3) 32 | { 33 | return element.FindResource ("Vector3MemberDataTemplate") as DataTemplate; 34 | } 35 | else if (member.Type == CommunicatorClient.Types.Enum) 36 | { 37 | return element.FindResource ("EnumMemberDataTemplate") as DataTemplate; 38 | } 39 | else if (member.Type == CommunicatorClient.Types.Color) 40 | { 41 | return element.FindResource ("ColorMemberDataTemplate") as DataTemplate; 42 | } 43 | else if (member.Type == CommunicatorClient.Types.Vector2) 44 | { 45 | return element.FindResource ("Vector2MemberDataTemplate") as DataTemplate; 46 | } 47 | else if (member.Type == CommunicatorClient.Types.Rect) 48 | { 49 | return element.FindResource ("RectMemberDataTemplate") as DataTemplate; 50 | } 51 | else if(member.Type == CommunicatorClient.Types.GameObject) 52 | { 53 | return element.FindResource("GameObjectMemberDataTemplate") as DataTemplate; 54 | } 55 | else if(member.Type != CommunicatorClient.Types.Unknown) 56 | { 57 | return element.FindResource ("TextBoxMemberDataTemplate") as DataTemplate; 58 | } 59 | else 60 | { 61 | return element.FindResource ("UnknownMemberDataTemplate") as DataTemplate; 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /UnityInspector.GUI/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  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 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 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 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 122 | 123 | 124 | 125 | 126 | 127 | 128 |