├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md └── src ├── ExampleAssembly ├── Cheat.cs ├── ExampleAssembly.csproj ├── Loader.cs └── Properties │ └── AssemblyInfo.cs ├── SharpMonoInjector.Console ├── App.config ├── CommandLineArguments.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs └── SharpMonoInjector.Console.csproj ├── SharpMonoInjector.Gui ├── App.config ├── App.xaml ├── App.xaml.cs ├── Converters │ ├── InjectedAssemblyToStringConverter.cs │ └── MonoProcessToStringConverter.cs ├── Models │ ├── InjectedAssembly.cs │ └── MonoProcess.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── SharpMonoInjector.Gui.csproj ├── ViewModels │ ├── MainWindowViewModel.cs │ ├── RelayCommand.cs │ └── ViewModel.cs └── Views │ ├── MainWindow.xaml │ └── MainWindow.xaml.cs ├── SharpMonoInjector.sln └── SharpMonoInjector ├── Assembler.cs ├── ExportedFunction.cs ├── Injector.cs ├── InjectorException.cs ├── Memory.cs ├── MonoImageOpenStatus.cs ├── Native.cs ├── ProcessUtils.cs └── SharpMonoInjector.csproj /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | build/ 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # Benchmark Results 47 | BenchmarkDotNet.Artifacts/ 48 | 49 | # .NET Core 50 | project.lock.json 51 | project.fragment.lock.json 52 | artifacts/ 53 | **/Properties/launchSettings.json 54 | 55 | *_i.c 56 | *_p.c 57 | *_i.h 58 | *.ilk 59 | *.meta 60 | *.obj 61 | *.pch 62 | *.pdb 63 | *.pgc 64 | *.pgd 65 | *.rsp 66 | *.sbr 67 | *.tlb 68 | *.tli 69 | *.tlh 70 | *.tmp 71 | *.tmp_proj 72 | *.log 73 | *.vspscc 74 | *.vssscc 75 | .builds 76 | *.pidb 77 | *.svclog 78 | *.scc 79 | 80 | # Chutzpah Test files 81 | _Chutzpah* 82 | 83 | # Visual C++ cache files 84 | ipch/ 85 | *.aps 86 | *.ncb 87 | *.opendb 88 | *.opensdf 89 | *.sdf 90 | *.cachefile 91 | *.VC.db 92 | *.VC.VC.opendb 93 | 94 | # Visual Studio profiler 95 | *.psess 96 | *.vsp 97 | *.vspx 98 | *.sap 99 | 100 | # TFS 2012 Local Workspace 101 | $tf/ 102 | 103 | # Guidance Automation Toolkit 104 | *.gpState 105 | 106 | # ReSharper is a .NET coding add-in 107 | _ReSharper*/ 108 | *.[Rr]e[Ss]harper 109 | *.DotSettings.user 110 | 111 | # JustCode is a .NET coding add-in 112 | .JustCode 113 | 114 | # TeamCity is a build add-in 115 | _TeamCity* 116 | 117 | # DotCover is a Code Coverage Tool 118 | *.dotCover 119 | 120 | # AxoCover is a Code Coverage Tool 121 | .axoCover/* 122 | !.axoCover/settings.json 123 | 124 | # Visual Studio code coverage results 125 | *.coverage 126 | *.coveragexml 127 | 128 | # NCrunch 129 | _NCrunch_* 130 | .*crunch*.local.xml 131 | nCrunchTemp_* 132 | 133 | # MightyMoose 134 | *.mm.* 135 | AutoTest.Net/ 136 | 137 | # Web workbench (sass) 138 | .sass-cache/ 139 | 140 | # Installshield output folder 141 | [Ee]xpress/ 142 | 143 | # DocProject is a documentation generator add-in 144 | DocProject/buildhelp/ 145 | DocProject/Help/*.HxT 146 | DocProject/Help/*.HxC 147 | DocProject/Help/*.hhc 148 | DocProject/Help/*.hhk 149 | DocProject/Help/*.hhp 150 | DocProject/Help/Html2 151 | DocProject/Help/html 152 | 153 | # Click-Once directory 154 | publish/ 155 | 156 | # Publish Web Output 157 | *.[Pp]ublish.xml 158 | *.azurePubxml 159 | # Note: Comment the next line if you want to checkin your web deploy settings, 160 | # but database connection strings (with potential passwords) will be unencrypted 161 | *.pubxml 162 | *.publishproj 163 | 164 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 165 | # checkin your Azure Web App publish settings, but sensitive information contained 166 | # in these scripts will be unencrypted 167 | PublishScripts/ 168 | 169 | # NuGet Packages 170 | *.nupkg 171 | # The packages folder can be ignored because of Package Restore 172 | **/packages/* 173 | # except build/, which is used as an MSBuild target. 174 | !**/packages/build/ 175 | # Uncomment if necessary however generally it will be regenerated when needed 176 | #!**/packages/repositories.config 177 | # NuGet v3's project.json files produces more ignorable files 178 | *.nuget.props 179 | *.nuget.targets 180 | 181 | # Microsoft Azure Build Output 182 | csx/ 183 | *.build.csdef 184 | 185 | # Microsoft Azure Emulator 186 | ecf/ 187 | rcf/ 188 | 189 | # Windows Store app package directories and files 190 | AppPackages/ 191 | BundleArtifacts/ 192 | Package.StoreAssociation.xml 193 | _pkginfo.txt 194 | *.appx 195 | 196 | # Visual Studio cache files 197 | # files ending in .cache can be ignored 198 | *.[Cc]ache 199 | # but keep track of directories ending in .cache 200 | !*.[Cc]ache/ 201 | 202 | # Others 203 | ClientBin/ 204 | ~$* 205 | *~ 206 | *.dbmdl 207 | *.dbproj.schemaview 208 | *.jfm 209 | *.pfx 210 | *.publishsettings 211 | orleans.codegen.cs 212 | 213 | # Since there are multiple workflows, uncomment next line to ignore bower_components 214 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 215 | #bower_components/ 216 | 217 | # RIA/Silverlight projects 218 | Generated_Code/ 219 | 220 | # Backup & report files from converting an old project file 221 | # to a newer Visual Studio version. Backup files are not needed, 222 | # because we have git ;-) 223 | _UpgradeReport_Files/ 224 | Backup*/ 225 | UpgradeLog*.XML 226 | UpgradeLog*.htm 227 | 228 | # SQL Server files 229 | *.mdf 230 | *.ldf 231 | *.ndf 232 | 233 | # Business Intelligence projects 234 | *.rdl.data 235 | *.bim.layout 236 | *.bim_*.settings 237 | 238 | # Microsoft Fakes 239 | FakesAssemblies/ 240 | 241 | # GhostDoc plugin setting file 242 | *.GhostDoc.xml 243 | 244 | # Node.js Tools for Visual Studio 245 | .ntvs_analysis.dat 246 | node_modules/ 247 | 248 | # Typescript v1 declaration files 249 | typings/ 250 | 251 | # Visual Studio 6 build log 252 | *.plg 253 | 254 | # Visual Studio 6 workspace options file 255 | *.opt 256 | 257 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 258 | *.vbw 259 | 260 | # Visual Studio LightSwitch build output 261 | **/*.HTMLClient/GeneratedArtifacts 262 | **/*.DesktopClient/GeneratedArtifacts 263 | **/*.DesktopClient/ModelManifest.xml 264 | **/*.Server/GeneratedArtifacts 265 | **/*.Server/ModelManifest.xml 266 | _Pvt_Extensions 267 | 268 | # Paket dependency manager 269 | .paket/paket.exe 270 | paket-files/ 271 | 272 | # FAKE - F# Make 273 | .fake/ 274 | 275 | # JetBrains Rider 276 | .idea/ 277 | *.sln.iml 278 | 279 | # CodeRush 280 | .cr/ 281 | 282 | # Python Tools for Visual Studio (PTVS) 283 | __pycache__/ 284 | *.pyc 285 | 286 | # Cake - Uncomment if you are using it 287 | # tools/** 288 | # !tools/packages.config 289 | 290 | # Tabs Studio 291 | *.tss 292 | 293 | # Telerik's JustMock configuration file 294 | *.jmconfig 295 | 296 | # BizTalk build output 297 | *.btp.cs 298 | *.btm.cs 299 | *.odx.cs 300 | *.xsd.cs 301 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Biney 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SharpMonoInjector 2 | SharpMonoInjector is a tool for injecting assemblies into Mono embedded applications, commonly Unity Engine based games. The target process *usually* does not have to be restarted in order to inject an updated version of the assembly. Your unload method must to destroy all of its resources (such as game objects). 3 | 4 | SharpMonoInjector works by dynamically generating machine code, writing it to the target process and executing it using CreateRemoteThread. The code calls functions in the mono embedded API. The return value is obtained with ReadProcessMemory. 5 | 6 | Both x86 and x64 processes are supported. 7 | 8 | In order for the injector to work, the load/unload methods need to match the following method signature: 9 | 10 | static void Method() 11 | 12 | In [releases](https://github.com/warbler/SharpMonoInjector/releases), there is a console application and a GUI application available. 13 | 14 | ![The GUI application](https://i.imgur.com/mPMwlu1.png) 15 | ![The console application](https://i.imgur.com/cz8Gyxa.png) 16 | -------------------------------------------------------------------------------- /src/ExampleAssembly/Cheat.cs: -------------------------------------------------------------------------------- 1 | namespace ExampleAssembly 2 | { 3 | public class Cheat : UnityEngine.MonoBehaviour 4 | { 5 | private void OnGUI() 6 | { 7 | UnityEngine.GUI.Label(new UnityEngine.Rect(10, 10, 200, 40), "This is a very useful cheat"); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ExampleAssembly/ExampleAssembly.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {251A7FF8-4D39-4F8B-9838-61124BF62F99} 8 | Library 9 | Properties 10 | ExampleAssembly 11 | ExampleAssembly 12 | v4.6.1 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | ..\..\build\debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | AnyCPU 24 | 25 | 26 | pdbonly 27 | true 28 | ..\..\build\release\ 29 | TRACE 30 | prompt 31 | 4 32 | AnyCPU 33 | 34 | 35 | true 36 | bin\x64\Debug\ 37 | DEBUG;TRACE 38 | full 39 | x64 40 | prompt 41 | MinimumRecommendedRules.ruleset 42 | 43 | 44 | bin\x64\Release\ 45 | TRACE 46 | true 47 | pdbonly 48 | x64 49 | prompt 50 | MinimumRecommendedRules.ruleset 51 | 52 | 53 | 54 | 55 | 56 | ..\..\lib\UnityEngine.dll 57 | False 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 73 | -------------------------------------------------------------------------------- /src/ExampleAssembly/Loader.cs: -------------------------------------------------------------------------------- 1 | namespace ExampleAssembly 2 | { 3 | public class Loader 4 | { 5 | static UnityEngine.GameObject gameObject; 6 | 7 | public static void Load() 8 | { 9 | gameObject = new UnityEngine.GameObject(); 10 | gameObject.AddComponent(); 11 | UnityEngine.Object.DontDestroyOnLoad(gameObject); 12 | } 13 | 14 | public static void Unload() 15 | { 16 | UnityEngine.Object.Destroy(gameObject); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/ExampleAssembly/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("ExampleAssembly")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Biney")] 12 | [assembly: AssemblyProduct("ExampleAssembly")] 13 | [assembly: AssemblyCopyright("Copyright © Biney")] 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("251a7ff8-4d39-4f8b-9838-61124bf62f99")] 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 | -------------------------------------------------------------------------------- /src/SharpMonoInjector.Console/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/SharpMonoInjector.Console/CommandLineArguments.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Linq; 3 | 4 | namespace SharpMonoInjector.Console 5 | { 6 | public class CommandLineArguments 7 | { 8 | private readonly string[] _args; 9 | 10 | public CommandLineArguments(string[] args) 11 | { 12 | _args = args; 13 | } 14 | 15 | public bool IsSwitchPresent(string name) => _args.Any(arg => arg == name); 16 | 17 | public bool GetLongArg(string name, out long value) 18 | { 19 | if (GetStringArg(name, out string str)) 20 | return long.TryParse(str.StartsWith("0x") ? str.Substring(2) : str, NumberStyles.AllowHexSpecifier, null, out value); 21 | 22 | value = default(long); 23 | return false; 24 | } 25 | 26 | public bool GetIntArg(string name, out int value) 27 | { 28 | if (GetStringArg(name, out string str)) 29 | return int.TryParse(str.StartsWith("0x") ? str.Substring(2) : str, NumberStyles.AllowHexSpecifier, null, out value); 30 | 31 | value = default(int); 32 | return false; 33 | } 34 | 35 | public bool GetStringArg(string name, out string value) 36 | { 37 | for (int i = 0; i < _args.Length; i++) { 38 | string arg = _args[i]; 39 | 40 | if (arg == name) { 41 | if (i == _args.Length - 1) 42 | break; 43 | 44 | value = _args[i + 1]; 45 | return true; 46 | } 47 | } 48 | 49 | value = null; 50 | return false; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/SharpMonoInjector.Console/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace SharpMonoInjector.Console 5 | { 6 | internal static class Program 7 | { 8 | private static void Main(string[] args) 9 | { 10 | if (args.Length == 0) { 11 | PrintHelp(); 12 | return; 13 | } 14 | 15 | CommandLineArguments cla = new CommandLineArguments(args); 16 | 17 | bool inject = cla.IsSwitchPresent("inject"); 18 | bool eject = cla.IsSwitchPresent("eject"); 19 | 20 | if (!inject && !eject) { 21 | System.Console.WriteLine("No operation (inject/eject) specified"); 22 | return; 23 | } 24 | 25 | Injector injector; 26 | 27 | if (cla.GetIntArg("-p", out int pid)) { 28 | injector = new Injector(pid); 29 | } else if (cla.GetStringArg("-p", out string pname)) { 30 | injector = new Injector(pname); 31 | } else { 32 | System.Console.WriteLine("No process id/name specified"); 33 | return; 34 | } 35 | 36 | if (inject) 37 | Inject(injector, cla); 38 | else 39 | Eject(injector, cla); 40 | } 41 | 42 | private static void PrintHelp() 43 | { 44 | const string help = 45 | "SharpMonoInjector 2.2\r\n\r\n" + 46 | "Usage:\r\n" + 47 | "smi.exe \r\n\r\n" + 48 | "Options:\r\n" + 49 | "-p - The id or name of the target process\r\n" + 50 | "-a - When injecting, the path of the assembly to inject. When ejecting, the address of the assembly to eject\r\n" + 51 | "-n - The namespace in which the loader class resides\r\n" + 52 | "-c - The name of the loader class\r\n" + 53 | "-m - The name of the method to invoke in the loader class\r\n\r\n" + 54 | "Examples:\r\n" + 55 | "smi.exe inject -p testgame -a ExampleAssembly.dll -n ExampleAssembly -c Loader -m Load\r\n" + 56 | "smi.exe eject -p testgame -a 0x13D23A98 -n ExampleAssembly -c Loader -m Unload\r\n"; 57 | System.Console.WriteLine(help); 58 | } 59 | 60 | private static void Inject(Injector injector, CommandLineArguments args) 61 | { 62 | string assemblyPath, @namespace, className, methodName; 63 | byte[] assembly; 64 | 65 | if (args.GetStringArg("-a", out assemblyPath)) { 66 | try { 67 | assembly = File.ReadAllBytes(assemblyPath); 68 | } catch { 69 | System.Console.WriteLine("Could not read the file " + assemblyPath); 70 | return; 71 | } 72 | } else { 73 | System.Console.WriteLine("No assembly specified"); 74 | return; 75 | } 76 | 77 | args.GetStringArg("-n", out @namespace); 78 | 79 | if (!args.GetStringArg("-c", out className)) { 80 | System.Console.WriteLine("No class name specified"); 81 | return; 82 | } 83 | 84 | if (!args.GetStringArg("-m", out methodName)) { 85 | System.Console.WriteLine("No method name specified"); 86 | return; 87 | } 88 | 89 | using (injector) { 90 | IntPtr remoteAssembly = IntPtr.Zero; 91 | 92 | try { 93 | remoteAssembly = injector.Inject(assembly, @namespace, className, methodName); 94 | } catch (InjectorException ie) { 95 | System.Console.WriteLine("Failed to inject assembly: " + ie); 96 | } catch (Exception exc) { 97 | System.Console.WriteLine("Failed to inject assembly (unknown error): " + exc); 98 | } 99 | 100 | if (remoteAssembly == IntPtr.Zero) 101 | return; 102 | 103 | System.Console.WriteLine($"{Path.GetFileName(assemblyPath)}: " + 104 | (injector.Is64Bit 105 | ? $"0x{remoteAssembly.ToInt64():X16}" 106 | : $"0x{remoteAssembly.ToInt32():X8}")); 107 | } 108 | } 109 | 110 | private static void Eject(Injector injector, CommandLineArguments args) 111 | { 112 | IntPtr assembly; 113 | string @namespace, className, methodName; 114 | 115 | if (args.GetIntArg("-a", out int intPtr)) { 116 | assembly = (IntPtr)intPtr; 117 | } else if (args.GetLongArg("-a", out long longPtr)) { 118 | assembly = (IntPtr)longPtr; 119 | } else { 120 | System.Console.WriteLine("No assembly pointer specified"); 121 | return; 122 | } 123 | 124 | args.GetStringArg("-n", out @namespace); 125 | 126 | if (!args.GetStringArg("-c", out className)) { 127 | System.Console.WriteLine("No class name specified"); 128 | return; 129 | } 130 | 131 | if (!args.GetStringArg("-m", out methodName)) { 132 | System.Console.WriteLine("No method name specified"); 133 | return; 134 | } 135 | 136 | using (injector) { 137 | try { 138 | injector.Eject(assembly, @namespace, className, methodName); 139 | System.Console.WriteLine("Ejection successful"); 140 | } catch (InjectorException ie) { 141 | System.Console.WriteLine("Ejection failed: " + ie); 142 | } catch (Exception exc) { 143 | System.Console.WriteLine("Ejection failed (unknown error): " + exc); 144 | } 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/SharpMonoInjector.Console/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("SharpMonoInjector.Console")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SharpMonoInjector.Console")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 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("e677de19-10c7-4981-bc7a-f49ae07e3391")] 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 | -------------------------------------------------------------------------------- /src/SharpMonoInjector.Console/SharpMonoInjector.Console.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {E677DE19-10C7-4981-BC7A-F49AE07E3391} 8 | Exe 9 | SharpMonoInjector.Console 10 | smi 11 | v4.7.1 12 | 512 13 | true 14 | true 15 | 16 | 17 | 18 | AnyCPU 19 | true 20 | full 21 | false 22 | ..\..\build\debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | false 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | ..\..\build\release\ 33 | TRACE 34 | prompt 35 | 4 36 | false 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | {9a2f2e9f-d314-4b7a-ba8d-8522bb91a114} 53 | SharpMonoInjector 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/SharpMonoInjector.Gui/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/SharpMonoInjector.Gui/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/SharpMonoInjector.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 SharpMonoInjector.Gui 10 | { 11 | /// 12 | /// Interaction logic for App.xaml 13 | /// 14 | public partial class App : Application 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/SharpMonoInjector.Gui/Converters/InjectedAssemblyToStringConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | using SharpMonoInjector.Gui.Models; 5 | 6 | namespace SharpMonoInjector.Gui.Converters 7 | { 8 | public class InjectedAssemblyToStringConverter : IValueConverter 9 | { 10 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 11 | { 12 | if (value == null) 13 | return null; 14 | 15 | InjectedAssembly ia = (InjectedAssembly)value; 16 | return $"[{(ia.Is64Bit ? $"0x{ia.Address.ToInt64():X16}" : $"0x{ia.Address.ToInt32():X8}")}] {ia.Name}"; 17 | } 18 | 19 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 20 | { 21 | throw new NotImplementedException(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/SharpMonoInjector.Gui/Converters/MonoProcessToStringConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | using SharpMonoInjector.Gui.Models; 5 | 6 | namespace SharpMonoInjector.Gui.Converters 7 | { 8 | public class MonoProcessToStringConverter : IValueConverter 9 | { 10 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 11 | { 12 | if (value == null) 13 | return null; 14 | 15 | MonoProcess mp = (MonoProcess)value; 16 | return $"[{mp.Id}] {mp.Name}"; 17 | } 18 | 19 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 20 | { 21 | throw new NotImplementedException(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/SharpMonoInjector.Gui/Models/InjectedAssembly.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SharpMonoInjector.Gui.Models 4 | { 5 | public class InjectedAssembly 6 | { 7 | public int ProcessId { get; set; } 8 | 9 | public IntPtr Address { get; set; } 10 | 11 | public bool Is64Bit { get; set; } 12 | 13 | public string Name { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/SharpMonoInjector.Gui/Models/MonoProcess.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SharpMonoInjector.Gui.Models 4 | { 5 | public class MonoProcess 6 | { 7 | public IntPtr MonoModule { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public int Id { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/SharpMonoInjector.Gui/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("SharpMonoInjector.Gui")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("")] 14 | [assembly: AssemblyProduct("SharpMonoInjector.Gui")] 15 | [assembly: AssemblyCopyright("Copyright © 2019")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | //In order to begin building localizable applications, set 25 | //CultureYouAreCodingWith in your .csproj file 26 | //inside a . For example, if you are using US english 27 | //in your source files, set the to en-US. Then uncomment 28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 29 | //the line below to match the UICulture setting in the project file. 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 36 | //(used if a resource is not found in the page, 37 | // or application resource dictionaries) 38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 39 | //(used if a resource is not found in the page, 40 | // app, or any theme specific resource dictionaries) 41 | )] 42 | 43 | 44 | // Version information for an assembly consists of the following four values: 45 | // 46 | // Major Version 47 | // Minor Version 48 | // Build Number 49 | // Revision 50 | // 51 | // You can specify all the values or you can default the Build and Revision Numbers 52 | // by using the '*' as shown below: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.0.0.0")] 55 | [assembly: AssemblyFileVersion("1.0.0.0")] 56 | -------------------------------------------------------------------------------- /src/SharpMonoInjector.Gui/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 SharpMonoInjector.Gui.Properties 12 | { 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", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Returns the cached ResourceManager instance used by this class. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get { 44 | if ((resourceMan == null)) { 45 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SharpMonoInjector.Gui.Properties.Resources", typeof(Resources).Assembly); 46 | resourceMan = temp; 47 | } 48 | return resourceMan; 49 | } 50 | } 51 | 52 | /// 53 | /// Overrides the current thread's CurrentUICulture property for all 54 | /// resource lookups using this strongly typed resource class. 55 | /// 56 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 57 | internal static global::System.Globalization.CultureInfo Culture 58 | { 59 | get { 60 | return resourceCulture; 61 | } 62 | set { 63 | resourceCulture = value; 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/SharpMonoInjector.Gui/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 | -------------------------------------------------------------------------------- /src/SharpMonoInjector.Gui/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.Gui.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get { 25 | return defaultInstance; 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/SharpMonoInjector.Gui/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/SharpMonoInjector.Gui/SharpMonoInjector.Gui.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {AA64466B-D036-47B1-AA77-DB1DAA026313} 8 | WinExe 9 | SharpMonoInjector.Gui 10 | smi_gui 11 | v4.7.1 12 | 512 13 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 4 15 | true 16 | true 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | ..\..\build\debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | false 28 | 29 | 30 | AnyCPU 31 | pdbonly 32 | true 33 | ..\..\build\release\ 34 | TRACE 35 | prompt 36 | 4 37 | false 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 4.0 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | MSBuild:Compile 58 | Designer 59 | 60 | 61 | MSBuild:Compile 62 | Designer 63 | 64 | 65 | App.xaml 66 | Code 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | MainWindow.xaml 77 | Code 78 | 79 | 80 | 81 | 82 | Code 83 | 84 | 85 | True 86 | True 87 | Resources.resx 88 | 89 | 90 | True 91 | Settings.settings 92 | True 93 | 94 | 95 | ResXFileCodeGenerator 96 | Resources.Designer.cs 97 | Designer 98 | 99 | 100 | SettingsSingleFileGenerator 101 | Settings.Designer.cs 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | {9a2f2e9f-d314-4b7a-ba8d-8522bb91a114} 110 | SharpMonoInjector 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /src/SharpMonoInjector.Gui/ViewModels/MainWindowViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using Microsoft.Win32; 8 | using SharpMonoInjector.Gui.Models; 9 | 10 | namespace SharpMonoInjector.Gui.ViewModels 11 | { 12 | public class MainWindowViewModel : ViewModel 13 | { 14 | public RelayCommand RefreshCommand { get; } 15 | 16 | public RelayCommand BrowseCommand { get; } 17 | 18 | public RelayCommand InjectCommand { get; } 19 | 20 | public RelayCommand EjectCommand { get; } 21 | 22 | public RelayCommand CopyStatusCommand { get; } 23 | 24 | public MainWindowViewModel() 25 | { 26 | RefreshCommand = new RelayCommand(ExecuteRefreshCommand, CanExecuteRefreshCommand); 27 | BrowseCommand = new RelayCommand(ExecuteBrowseCommand); 28 | InjectCommand = new RelayCommand(ExecuteInjectCommand, CanExecuteInjectCommand); 29 | EjectCommand = new RelayCommand(ExecuteEjectCommand, CanExecuteEjectCommand); 30 | CopyStatusCommand = new RelayCommand(ExecuteCopyStatusCommand); 31 | } 32 | 33 | private void ExecuteCopyStatusCommand(object parameter) 34 | { 35 | Clipboard.SetText(Status); 36 | } 37 | 38 | private bool CanExecuteRefreshCommand(object parameter) 39 | { 40 | return !IsRefreshing; 41 | } 42 | 43 | private async void ExecuteRefreshCommand(object parameter) 44 | { 45 | IsRefreshing = true; 46 | Status = "Refreshing processes"; 47 | ObservableCollection processes = new ObservableCollection(); 48 | 49 | await Task.Run(() => 50 | { 51 | int cp = Process.GetCurrentProcess().Id; 52 | 53 | foreach (Process p in Process.GetProcesses()) { 54 | if (p.Id == cp) 55 | continue; 56 | 57 | const ProcessAccessRights flags = ProcessAccessRights.PROCESS_QUERY_INFORMATION | ProcessAccessRights.PROCESS_VM_READ; 58 | IntPtr handle; 59 | 60 | if ((handle = Native.OpenProcess(flags, false, p.Id)) != IntPtr.Zero) { 61 | if (ProcessUtils.GetMonoModule(handle, out IntPtr mono)) { 62 | processes.Add(new MonoProcess 63 | { 64 | MonoModule = mono, 65 | Id = p.Id, 66 | Name = p.ProcessName 67 | }); 68 | } 69 | 70 | Native.CloseHandle(handle); 71 | } 72 | } 73 | }); 74 | 75 | Processes = processes; 76 | 77 | if (Processes.Count > 0) 78 | SelectedProcess = Processes[0]; 79 | 80 | IsRefreshing = false; 81 | Status = "Processes refreshed"; 82 | } 83 | 84 | private void ExecuteBrowseCommand(object parameter) 85 | { 86 | OpenFileDialog ofd = new OpenFileDialog(); 87 | ofd.Filter = "Dynamic Link Library|*.dll"; 88 | ofd.Title = "Select assembly to inject"; 89 | 90 | if (ofd.ShowDialog() == true) 91 | AssemblyPath = ofd.FileName; 92 | } 93 | 94 | private bool CanExecuteInjectCommand(object parameter) 95 | { 96 | return SelectedProcess != null && 97 | File.Exists(AssemblyPath) && 98 | !string.IsNullOrEmpty(InjectClassName) && 99 | !string.IsNullOrEmpty(InjectMethodName) && 100 | !IsExecuting; 101 | } 102 | 103 | private void ExecuteInjectCommand(object parameter) 104 | { 105 | IntPtr handle = Native.OpenProcess(ProcessAccessRights.PROCESS_ALL_ACCESS, false, SelectedProcess.Id); 106 | 107 | if (handle == IntPtr.Zero) { 108 | Status = "Failed to open process"; 109 | return; 110 | } 111 | 112 | byte[] file; 113 | 114 | try { 115 | file = File.ReadAllBytes(AssemblyPath); 116 | } catch (IOException) { 117 | Status = "Failed to read the file " + AssemblyPath; 118 | return; 119 | } 120 | 121 | IsExecuting = true; 122 | Status = "Injecting " + Path.GetFileName(AssemblyPath); 123 | 124 | using (Injector injector = new Injector(handle, SelectedProcess.MonoModule)) { 125 | try { 126 | IntPtr asm = injector.Inject(file, InjectNamespace, InjectClassName, InjectMethodName); 127 | InjectedAssemblies.Add(new InjectedAssembly 128 | { 129 | ProcessId = SelectedProcess.Id, 130 | Address = asm, 131 | Name = Path.GetFileName(AssemblyPath), 132 | Is64Bit = injector.Is64Bit 133 | }); 134 | Status = "Injection successful"; 135 | } catch (InjectorException ie) { 136 | Status = "Injection failed: " + ie.Message; 137 | } catch (Exception e) { 138 | Status = "Injection failed (unknown error): " + e.Message; 139 | } 140 | } 141 | 142 | IsExecuting = false; 143 | } 144 | 145 | private bool CanExecuteEjectCommand(object parameter) 146 | { 147 | return SelectedAssembly != null && 148 | !string.IsNullOrEmpty(EjectClassName) && 149 | !string.IsNullOrEmpty(EjectMethodName) && 150 | !IsExecuting; 151 | } 152 | 153 | private void ExecuteEjectCommand(object parameter) 154 | { 155 | IntPtr handle = Native.OpenProcess(ProcessAccessRights.PROCESS_ALL_ACCESS, false, SelectedAssembly.ProcessId); 156 | 157 | if (handle == IntPtr.Zero) { 158 | Status = "Failed to open process"; 159 | return; 160 | } 161 | 162 | IsExecuting = true; 163 | Status = "Ejecting " + SelectedAssembly.Name; 164 | 165 | ProcessUtils.GetMonoModule(handle, out IntPtr mono); 166 | 167 | using (Injector injector = new Injector(handle, mono)) { 168 | try { 169 | injector.Eject(SelectedAssembly.Address, EjectNamespace, EjectClassName, EjectMethodName); 170 | InjectedAssemblies.Remove(SelectedAssembly); 171 | Status = "Ejection successful"; 172 | } catch (InjectorException ie) { 173 | Status = "Ejection failed: " + ie.Message; 174 | } catch (Exception e) { 175 | Status = "Ejection failed (unknown error): " + e.Message; 176 | } 177 | } 178 | 179 | IsExecuting = false; 180 | } 181 | 182 | private bool _isRefreshing; 183 | public bool IsRefreshing 184 | { 185 | get => _isRefreshing; 186 | set { 187 | Set(ref _isRefreshing, value); 188 | RefreshCommand.RaiseCanExecuteChanged(); 189 | } 190 | } 191 | 192 | private bool _isExecuting; 193 | public bool IsExecuting 194 | { 195 | get => _isExecuting; 196 | set { 197 | Set(ref _isExecuting, value); 198 | InjectCommand.RaiseCanExecuteChanged(); 199 | EjectCommand.RaiseCanExecuteChanged(); 200 | } 201 | } 202 | 203 | private ObservableCollection _processes; 204 | public ObservableCollection Processes 205 | { 206 | get => _processes; 207 | set => Set(ref _processes, value); 208 | } 209 | 210 | private MonoProcess _selectedProcess; 211 | public MonoProcess SelectedProcess 212 | { 213 | get => _selectedProcess; 214 | set { 215 | _selectedProcess = value; 216 | InjectCommand.RaiseCanExecuteChanged(); 217 | } 218 | } 219 | 220 | private string _status; 221 | public string Status 222 | { 223 | get => _status; 224 | set => Set(ref _status, value); 225 | } 226 | 227 | private string _assemblyPath; 228 | public string AssemblyPath 229 | { 230 | get => _assemblyPath; 231 | set { 232 | Set(ref _assemblyPath, value); 233 | if (File.Exists(_assemblyPath)) 234 | InjectNamespace = Path.GetFileNameWithoutExtension(_assemblyPath); 235 | InjectCommand.RaiseCanExecuteChanged(); 236 | } 237 | } 238 | 239 | private string _injectNamespace; 240 | public string InjectNamespace 241 | { 242 | get => _injectNamespace; 243 | set { 244 | Set(ref _injectNamespace, value); 245 | EjectNamespace = value; 246 | } 247 | } 248 | 249 | private string _injectClassName; 250 | public string InjectClassName 251 | { 252 | get => _injectClassName; 253 | set { 254 | Set(ref _injectClassName, value); 255 | EjectClassName = value; 256 | InjectCommand.RaiseCanExecuteChanged(); 257 | } 258 | } 259 | 260 | private string _injectMethodName; 261 | public string InjectMethodName 262 | { 263 | get => _injectMethodName; 264 | set { 265 | Set(ref _injectMethodName, value); 266 | if (_injectMethodName == "Load") 267 | EjectMethodName = "Unload"; 268 | InjectCommand.RaiseCanExecuteChanged(); 269 | } 270 | } 271 | 272 | private ObservableCollection _injectedAssemblies = new ObservableCollection(); 273 | public ObservableCollection InjectedAssemblies 274 | { 275 | get => _injectedAssemblies; 276 | set => Set(ref _injectedAssemblies, value); 277 | } 278 | 279 | private InjectedAssembly _selectedAssembly; 280 | public InjectedAssembly SelectedAssembly 281 | { 282 | get => _selectedAssembly; 283 | set { 284 | Set(ref _selectedAssembly, value); 285 | EjectCommand.RaiseCanExecuteChanged(); 286 | } 287 | } 288 | 289 | private string _ejectNamespace; 290 | public string EjectNamespace 291 | { 292 | get => _ejectNamespace; 293 | set => Set(ref _ejectNamespace, value); 294 | } 295 | 296 | private string _ejectClassName; 297 | public string EjectClassName 298 | { 299 | get => _ejectClassName; 300 | set { 301 | Set(ref _ejectClassName, value); 302 | EjectCommand.RaiseCanExecuteChanged(); 303 | } 304 | } 305 | 306 | private string _ejectMethodName; 307 | public string EjectMethodName 308 | { 309 | get => _ejectMethodName; 310 | set { 311 | Set(ref _ejectMethodName, value); 312 | EjectCommand.RaiseCanExecuteChanged(); 313 | } 314 | } 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /src/SharpMonoInjector.Gui/ViewModels/RelayCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Input; 3 | 4 | namespace SharpMonoInjector.Gui.ViewModels 5 | { 6 | public class RelayCommand : ICommand 7 | { 8 | public event EventHandler CanExecuteChanged; 9 | 10 | private readonly Action _execute; 11 | 12 | private readonly Func _canExecute; 13 | 14 | public RelayCommand(Action execute, Func canExecute = null) 15 | { 16 | _execute = execute; 17 | _canExecute = canExecute; 18 | } 19 | 20 | public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter); 21 | 22 | public void Execute(object parameter) => _execute(parameter); 23 | 24 | public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/SharpMonoInjector.Gui/ViewModels/ViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace SharpMonoInjector.Gui.ViewModels 6 | { 7 | public abstract class ViewModel : INotifyPropertyChanged 8 | { 9 | public event PropertyChangedEventHandler PropertyChanged; 10 | 11 | protected void Set(ref T property, T value, [CallerMemberName]string name = null) 12 | { 13 | if (!EqualityComparer.Default.Equals(property, value)) { 14 | property = value; 15 | RaisePropertyChanged(name); 16 | } 17 | } 18 | 19 | protected void RaisePropertyChanged(string name) 20 | { 21 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/SharpMonoInjector.Gui/Views/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  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 |