// // NosInjector.cs // // Copyright (c) František Boháček. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Diagnostics; using System.Security; using System.Text; using Microsoft.Extensions.Options; using NosSmooth.Injector.Errors; using Reloaded.Memory.Sources; using Remora.Results; namespace NosSmooth.Injector; /// /// Nos smooth injector for .NET 5+ projects. /// /// /// If you want to inject your project into NosTale that /// uses NosSmooth.LocalClient, use this injector. /// You must expose static UnmanagedCallersOnly method. /// public class NosInjector { private readonly NosInjectorOptions _options; /// /// Initializes a new instance of the class. /// /// The injector options. public NosInjector(IOptions options) { _options = options.Value; } /// /// Inject the given .NET 5+ dll into NosTale process. /// /// /// The dll must also have .runtimeconfig.json present next to the dll. /// /// The id of the process to inject to. /// The absolute path to the dll to inject. /// The full path to the class. Such as "MyLibrary.DllMain, MyLibrary". /// The name of the method to execute. The method should return void and have no parameters. /// A result that may or may not have succeeded. public Result Inject ( int processId, string dllPath, string classPath, string methodName = "Main" ) { using var process = Process.GetProcessById(processId); return Inject(process, dllPath, classPath, methodName); } /// /// Inject the given .NET 5+ dll into NosTale process. /// /// /// The dll must also have .runtimeconfig.json present next to the dll. /// /// The process to inject to. /// The absolute path to the dll to inject. /// The full path to the class. Such as "MyLibrary.DllMain, MyLibrary". /// The name of the method to execute. The method should return void and have no parameters. /// A result that may or may not have succeeded. public Result Inject ( Process process, string dllPath, string classPath, string methodName = "Main" ) { try { using var injector = new Reloaded.Injector.Injector(process); var memory = new ExternalMemory(process); var directoryName = Path.GetDirectoryName(dllPath); if (directoryName is null) { return new GenericError("There was an error obtaining directory name of the dll path."); } var runtimePath = Path.Combine (directoryName, Path.GetFileNameWithoutExtension(dllPath)) + ".runtimeconfig.json"; using var dllPathMemory = AllocateString(memory, dllPath); using var classPathMemory = AllocateString(memory, classPath); using var methodNameMemory = AllocateString(memory, methodName); using var runtimePathMemory = AllocateString(memory, runtimePath); if (!dllPathMemory.Allocated || !classPathMemory.Allocated || !methodNameMemory.Allocated || !runtimePathMemory.Allocated) { return new GenericError("Could not allocate memory in the external process."); } var loadParams = new LoadParams() { LibraryPath = (int)dllPathMemory.Pointer, MethodName = (int)methodNameMemory.Pointer, RuntimeConfigPath = (int)runtimePathMemory.Pointer, TypePath = (int)classPathMemory.Pointer }; var nosSmoothInjectPath = Path.GetFullPath(_options.NosSmoothInjectPath); var injected = injector.Inject(nosSmoothInjectPath); if (injected == 0) { return new InjectionFailedError(nosSmoothInjectPath); } var functionResult = injector.CallFunction(nosSmoothInjectPath, "LoadAndCallMethod", loadParams); if (functionResult != 0) { return new InjectionFailedError(dllPath); } return Result.FromSuccess(); } catch (UnauthorizedAccessException) { return new InsufficientPermissionsError(process.Id, process.ProcessName); } catch (SecurityException) { return new InsufficientPermissionsError(process.Id, process.ProcessName); } catch (Exception e) { return e; } } private ManagedMemoryAllocation AllocateString(IMemory memory, string str) { var bytes = Encoding.Unicode.GetBytes(str); var allocated = memory.Allocate(bytes.Length + 1); if (allocated == IntPtr.Zero) { return new ManagedMemoryAllocation(memory, allocated); } memory.SafeWriteRaw(allocated + bytes.Length, new byte[] { 0 }); memory.SafeWriteRaw(allocated, bytes); return new ManagedMemoryAllocation(memory, allocated); } }