//
// 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);
}
}