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)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)&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