using System.Text;
namespace XFEEDToolBox.Plugin;
internal static unsafe class PluginLoader
{
private static readonly object initializeLock = new();
private static nint realInput;
private static nuint createInput;
private static bool loaded;
private static int serviceStarted;
internal static volatile bool Active = true;
private static bool observationStarted;
private static string disabledFile = "";
internal static int Create(nint instance, uint version, Guid* iid, void** output, void* outer)
{
try
{
lock (initializeLock)
{
if (!loaded)
{
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "dinput8.dll");
realInput = LoadLibraryExW(path, 0, 0x800);
createInput = realInput == 0 ? 0 : (nuint)GetProcAddress(realInput, "DirectInput8Create");
disabledFile = Path.Combine(Path.GetDirectoryName(ModulePath())!, ".xfeedtoolbox-plugin", "disabled");
loaded = true;
}
}
if (createInput == 0) return unchecked((int)0x8007007e);
var result = ((delegate* unmanaged<nint, uint, Guid*, void**, void*, int>)createInput)(instance, version, iid, output, outer);
var lastError = GetLastError();
if (result >= 0)
{
try { StartService(); }
catch (Exception) { }
}
SetLastError(lastError);
return result;
}
catch (Exception) { return unchecked((int)0x80004005); }
}
private static void StartService()
{
if (!Path.GetFileName(ModulePath()).Equals("EliteDangerous64.exe", StringComparison.OrdinalIgnoreCase)) return;
if (Interlocked.CompareExchange(ref serviceStarted, 1, 0) != 0) return;
try
{
if (!Pin((nuint)(delegate* unmanaged<nint, uint, Guid*, void**, void*, int>)&PinAnchor))
{ Volatile.Write(ref serviceStarted, 0); return; }
if (!ThreadPool.QueueUserWorkItem(static _ => InitializeService())) Volatile.Write(ref serviceStarted, 0);
}
catch (Exception) { Volatile.Write(ref serviceStarted, 0); }
}
private static void InitializeService()
{
try
{
if (File.Exists(disabledFile)) Active = false;
Trace("service.started.after-directinput");
PipeServer.Start("Plugin", 4096, Handle);
}
catch (Exception) { Volatile.Write(ref serviceStarted, 0); }
}
// Pin by address without using a managed function address or loading any game hooks.
[UnmanagedCallersOnly]
private static int PinAnchor(nint instance, uint version, Guid* iid, void** output, void* outer) => 0;
private static JsonNode Handle(string command)
{
if (disabledFile.Length != 0 && File.Exists(disabledFile)) Active = false;
if (command == "deactivate\n") { Active = false; Navigation.Deactivate(); }
if (command == "navigation.observe.start\n" && Active)
{
Trace("navigation.observation.requested");
try { observationStarted = Navigation.Initialize(() => Active); }
catch (Exception) { observationStarted = false; }
Trace(observationStarted ? "navigation.observation.started" : "navigation.observation.rejected");
}
return Obj(("schema", 1), ("name", "XFEEDToolBox.Plugin"), ("version", PluginVersion),
("processId", ProcessId), ("loaded", true), ("active", Active), ("observationStarted", observationStarted),
("supportsTrade", false), ("supportsRelog", Active && Navigation.SessionReady()), ("supportsNavigation", Active && Navigation.Ready()));
}
internal static void Trace(string stage)
{
var error = GetLastError();
try
{
var folder = Path.Combine(Path.GetDirectoryName(ModulePath())!, ".xfeedtoolbox-plugin");
if (!Directory.Exists(folder)) return;
var diagnostics = Path.Combine(folder, "diagnostics"); Directory.CreateDirectory(diagnostics);
var path = Path.Combine(diagnostics, $"startup-{ProcessId}.jsonl");
using var file = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete);
if (file.Length >= 65536) return;
file.Seek(0, SeekOrigin.End);
var value = Obj(("version", PluginVersion), ("processId", ProcessId), ("threadId", GetCurrentThreadId()), ("tick", GetTickCount64()), ("stage", stage));
file.Write(Encoding.UTF8.GetBytes(value.ToJsonString() + "\n"));
}
catch (Exception) { }
finally { SetLastError(error); }
}
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern nint LoadLibraryExW(string path, nint file, uint flags);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi)] private static extern nint GetProcAddress(nint module, string name);
}
#if PLUGIN_LOADER
internal static unsafe class LoaderExports
{
[UnmanagedCallersOnly(EntryPoint = "DirectInput8Create")]
internal static int DirectInput8Create(nint instance, uint version, Guid* iid, void** output, void* outer) => PluginLoader.Create(instance, version, iid, output, outer);
[UnmanagedCallersOnly(EntryPoint = "XFEPluginGetAbiVersion")]
internal static uint XFEPluginGetAbiVersion() => 1;
}
#endif
using System.Text;
namespace XFEEDToolBox.Plugin;
internal static unsafe class PluginLoader
{
private static readonly object initializeLock = new();
private static nint realInput;
private static nuint createInput;
private static bool loaded;
private static int serviceStarted;
internal static volatile bool Active = true;
private static bool observationStarted;
private static string disabledFile = "";
internal static int Create(nint instance, uint version, Guid* iid, void** output, void* outer)
{
try
{
lock (initializeLock)
{
if (!loaded)
{
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "dinput8.dll");
realInput = LoadLibraryExW(path, 0, 0x800);
createInput = realInput == 0 ? 0 : (nuint)GetProcAddress(realInput, "DirectInput8Create");
disabledFile = Path.Combine(Path.GetDirectoryName(ModulePath())!, ".xfeedtoolbox-plugin", "disabled");
loaded = true;
}
}
if (createInput == 0) return unchecked((int)0x8007007e);
var result = ((delegate* unmanaged<nint, uint, Guid*, void**, void*, int>)createInput)(instance, version, iid, output, outer);
var lastError = GetLastError();
if (result >= 0)
{
try { StartService(); }
catch (Exception) { }
}
SetLastError(lastError);
return result;
}
catch (Exception) { return unchecked((int)0x80004005); }
}
private static void StartService()
{
if (!Path.GetFileName(ModulePath()).Equals("EliteDangerous64.exe", StringComparison.OrdinalIgnoreCase)) return;
if (Interlocked.CompareExchange(ref serviceStarted, 1, 0) != 0) return;
try
{
if (!Pin((nuint)(delegate* unmanaged<nint, uint, Guid*, void**, void*, int>)&PinAnchor))
{ Volatile.Write(ref serviceStarted, 0); return; }
if (!ThreadPool.QueueUserWorkItem(static _ => InitializeService())) Volatile.Write(ref serviceStarted, 0);
}
catch (Exception) { Volatile.Write(ref serviceStarted, 0); }
}
private static void InitializeService()
{
try
{
if (File.Exists(disabledFile)) Active = false;
Trace("service.started.after-directinput");
PipeServer.Start("Plugin", 4096, Handle);
}
catch (Exception) { Volatile.Write(ref serviceStarted, 0); }
}
// Pin by address without using a managed function address or loading any game hooks.
[UnmanagedCallersOnly]
private static int PinAnchor(nint instance, uint version, Guid* iid, void** output, void* outer) => 0;
private static JsonNode Handle(string command)
{
if (disabledFile.Length != 0 && File.Exists(disabledFile)) Active = false;
if (command == "deactivate\n") { Active = false; Navigation.Deactivate(); }
if (command == "navigation.observe.start\n" && Active)
{
Trace("navigation.observation.requested");
try { observationStarted = Navigation.Initialize(() => Active); }
catch (Exception) { observationStarted = false; }
Trace(observationStarted ? "navigation.observation.started" : "navigation.observation.rejected");
}
return Obj(("schema", 1), ("name", "XFEEDToolBox.Plugin"), ("version", PluginVersion),
("processId", ProcessId), ("loaded", true), ("active", Active), ("observationStarted", observationStarted),
("supportsTrade", false), ("supportsRelog", Active && Navigation.SessionReady()), ("supportsNavigation", Active && Navigation.Ready()));
}
internal static void Trace(string stage)
{
var error = GetLastError();
try
{
var folder = Path.Combine(Path.GetDirectoryName(ModulePath())!, ".xfeedtoolbox-plugin");
if (!Directory.Exists(folder)) return;
var diagnostics = Path.Combine(folder, "diagnostics"); Directory.CreateDirectory(diagnostics);
var path = Path.Combine(diagnostics, $"startup-{ProcessId}.jsonl");
using var file = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete);
if (file.Length >= 65536) return;
file.Seek(0, SeekOrigin.End);
var value = Obj(("version", PluginVersion), ("processId", ProcessId), ("threadId", GetCurrentThreadId()), ("tick", GetTickCount64()), ("stage", stage));
file.Write(Encoding.UTF8.GetBytes(value.ToJsonString() + "\n"));
}
catch (Exception) { }
finally { SetLastError(error); }
}
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern nint LoadLibraryExW(string path, nint file, uint flags);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi)] private static extern nint GetProcAddress(nint module, string name);
}
#if PLUGIN_LOADER
internal static unsafe class LoaderExports
{
[UnmanagedCallersOnly(EntryPoint = "DirectInput8Create")]
internal static int DirectInput8Create(nint instance, uint version, Guid* iid, void** output, void* outer) => PluginLoader.Create(instance, version, iid, output, outer);
[UnmanagedCallersOnly(EntryPoint = "XFEPluginGetAbiVersion")]
internal static uint XFEPluginGetAbiVersion() => 1;
}
#endif