XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
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