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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Globalization;

namespace XFEEDToolBox.Plugin;

internal static unsafe class InteractionRuntime
{
    private const nuint DriveGetter = 0x2C9A6E0, InputTable = 0x53143F8, RootTable = 0x53142C8;
    private static nuint image, imageEnd, original;
    private static uint owningThread;
    private static volatile bool started;
    private static readonly object initializeLock = new(), requestLock = new(), frameLock = new(), workLock = new();
    private struct Frame
    {
        internal nuint input, fsd, life;
        internal ulong entity, tick, sequence, applied, utc;
        internal byte hardpoints, cargoScoop, landingGear, fsdState;
    }
    private struct Work
    {
        internal ulong generation, expires, entity, frameSequence;
        internal uint field;
        internal bool value;
    }
    private static Frame frame;
    private static readonly Frame[] history = new Frame[32];
    private static Work work;
    private static ulong generation, consumed;
    private static Frame Current() { lock (frameLock) return frame; }
    private static bool InImage(nuint at) => at >= image && at < imageEnd;
    private static bool Sample(nuint fsd, out Frame result)
    {
        result = default;
        try
        {
            var input = Pointer(fsd + 0x398); var root = input - 0x60; var physics = Pointer(fsd + 0x2a8) - 0x58;
            if (input == 0 || Pointer(input) != image + InputTable || Pointer(root) != image + RootTable || Pointer(physics + 0x3b0) != input || Pointer(physics + 0x10) != Pointer(fsd + 0x10)) return false;
            var owner = Pointer(physics + 0x20); var method = Pointer(Pointer(owner) + 0x20);
            if (!InImage(method)) return false;
            nuint reference = 0;
            var a = Read<byte>(method); var b = Read<byte>(method + 1); var c = Read<byte>(method + 2);
            if (a == 0x48 && b == 0x8d && c == 0x81 && Read<byte>(method + 7) == 0xc3) reference = (nuint)((nint)owner + Read<int>(method + 3));
            else if (a == 0x48 && b == 0x8d && c == 0x41 && Read<byte>(method + 4) == 0xc3) reference = (nuint)((nint)owner + Read<sbyte>(method + 3));
            else if (a == 0x48 && b == 0x8b && c == 0x81 && Read<byte>(method + 7) == 0xc3) reference = Pointer((nuint)((nint)owner + Read<int>(method + 3)));
            else if (a == 0x48 && b == 0x8b && c == 0x41 && Read<byte>(method + 4) == 0xc3) reference = Pointer((nuint)((nint)owner + Read<sbyte>(method + 3)));
            if (reference == 0) return false;
            var life = Pointer(reference);
            if (life == 0 || Read<uint>(life + 0xc0) != 8 || Pointer(life + 0xc8) == 0 || LocalPlayerIdentity.ReadLife(root,image,imageEnd) != life) return false;
            var next = new Frame { input = input, fsd = fsd, life = life, entity = Read<ulong>(life + 0xb0), hardpoints = Read<byte>(input + 0xb1), cargoScoop = Read<byte>(input + 0xbb), landingGear = Read<byte>(input + 0xa9), fsdState = Read<byte>(fsd + 0x758) };
            if (next.entity == 0 || next.hardpoints > 1 || next.cargoScoop > 1 || next.landingGear > 1 || next.fsdState > 4) return false;
            next.tick = GetTickCount64(); next.utc = Utc(); result = next; return true;
        }
        catch (NativeAccessException) { return false; }
    }
    private static bool Apply(Frame fresh, Work wanted)
    {
        try
        {
            if (wanted.entity != fresh.entity || fresh.fsdState != 2 || Read<uint>(fresh.life + 0xc0) != 8 || Read<ulong>(fresh.life + 0xb0) != fresh.entity || LocalPlayerIdentity.ReadLife(fresh.input-0x60,image,imageEnd) != fresh.life) return false;
            if (wanted.field == 1)
            {
                if (Pointer(Pointer(fresh.input) + 0x18) != image + 0x2CB5BB0) return false;
                Call(image + 0x2CB5BB0, (ulong)fresh.input, wanted.value ? 1UL : 0UL);
            }
            else if (wanted.field is 2 or 3) Write(fresh.input + (wanted.field == 2 ? 0xbbu : 0xa9u), (byte)(wanted.value ? 1 : 0));
            else return false;
            return true;
        }
        catch (NativeAccessException) { return false; }
    }
    private static void Observe(nuint fsd)
    {
        if (GetCurrentThreadId() != owningThread) return;
        var before = Current(); Work wanted; lock (workLock) wanted = work;
        if (GetTickCount64() - before.tick < 20 && wanted.generation <= consumed) return;
        if (!Sample(fsd, out var next)) return;
        if (wanted.generation > consumed)
        {
            consumed = wanted.generation;
            if (wanted.expires >= next.tick && wanted.entity == next.entity && before.entity == next.entity && wanted.frameSequence <= before.sequence && next.tick - before.tick <= 300 && Apply(next, wanted)) next.applied = wanted.generation;
        }
        if (next.applied == 0 && before.entity == next.entity) next.applied = before.applied;
        next.sequence = before.sequence + 1;
        lock (frameLock) { frame = next; history[next.sequence % 32] = next; }
    }
    [UnmanagedCallersOnly]
    private static nuint Hook(nuint value) => InvokeObservedGetter(value, original, Observe);

    private static nuint InvokeObservedGetter(nuint value, nuint getter, Action<nuint> observe)
    {
        // No managed exception may unwind across the reverse P/Invoke boundary.
        nuint result;
        try { result = (nuint)Call(getter, (ulong)value); }
        catch (Exception) { return 0; }
        // Sampling is an observer: even a failed sample must preserve the original getter result.
        try { observe(value - 0x58); }
        catch (Exception) { }
        return result;
    }
    private static int testGetterCalls;
    [UnmanagedCallersOnly]
    private static nuint TestGetter(nuint value)
    {
        testGetterCalls++; return value ^ unchecked((nuint)0xABCD1234FEDC5678UL);
    }
    internal static uint SelfTest()
    {
        // Run only in the standalone own-process validation host. No game is opened or modified.
        uint passed = 0; nuint fault = 0;
        var savedOriginal = original; var savedThread = owningThread;
        Frame savedFrame; Work savedWork; lock (frameLock) savedFrame = frame; lock (workLock) savedWork = work;
        var savedConsumed = consumed;
        try
        {
            var getter = (nuint)(delegate* unmanaged<nuint, nuint>)&TestGetter;
            nuint input = unchecked((nuint)0x1234567887654358UL); var expected = input ^ unchecked((nuint)0xABCD1234FEDC5678UL);
            nuint observed = 0; testGetterCalls = 0;
            if (InvokeObservedGetter(input, getter, value => observed = value) == expected && observed == input - 0x58 && testGetterCalls == 1) passed |= 1;
            testGetterCalls = 0;
            if (InvokeObservedGetter(input, getter, _ => throw new NativeAccessException("fixture")) == expected && testGetterCalls == 1) passed |= 2;
            testGetterCalls = 0;
            if (InvokeObservedGetter(input, getter, _ => throw new InvalidOperationException("fixture")) == expected && testGetterCalls == 1) passed |= 4;
            testGetterCalls = 0;
            if (InvokeObservedGetter(input, getter, null!) == expected && testGetterCalls == 1) passed |= 8;
            fault = Allocate(4096);
            if (fault != 0)
            {
                WriteBytes(fault, [0x48, 0x31, 0xC0, 0x48, 0x8B, 0x00, 0xC3]);
                if (Protect(fault, 4096, 0x20, out _) && FlushCode(fault, 7))
                {
                    var calls = 0;
                    if (InvokeObservedGetter(input, fault, _ => calls++) == 0 && calls == 0) passed |= 16;
                    testGetterCalls = 0;
                    if (InvokeObservedGetter(input, getter, _ => calls++) == expected && calls == 1 && testGetterCalls == 1) passed |= 32;
                }
            }
            original = getter; var thread = GetCurrentThreadId(); owningThread = thread == uint.MaxValue ? 1 : thread + 1;
            var callback = (delegate* unmanaged<nuint, nuint>)&Hook; testGetterCalls = 0;
            if (callback(input) == expected && testGetterCalls == 1) passed |= 64;
            owningThread = thread; lock (frameLock) frame = default; lock (workLock) work = default; consumed = 0;
            testGetterCalls = 0; nuint invalidObject = 0x58;
            if (callback(invalidObject) == (invalidObject ^ unchecked((nuint)0xABCD1234FEDC5678UL)) && testGetterCalls == 1) passed |= 128;
        }
        catch (Exception) { }
        finally
        {
            original = savedOriginal; owningThread = savedThread; lock (frameLock) frame = savedFrame; lock (workLock) work = savedWork; consumed = savedConsumed; Free(fault);
        }
        return passed;
    }
    private static JsonNode Handle(JsonElement request)
    {
        lock (requestLock)
        {
            var op = request.Str("op");
            if (op == "describe") return Obj(("schema", 1), ("name", "XFEEDToolBox.Interaction"), ("processId", ProcessId), ("gameSha256", GameHash), ("owningThread", owningThread), ("inputs", Arr("hardpoints", "cargoScoop", "landingGear")), ("ready", started));
            var current = Current(); var now = GetTickCount64();
            if (current.entity == 0 || now < current.tick || now - current.tick > 300) throw new InvalidOperationException("Game input telemetry unavailable or stale");
            if (op == "read") return Obj(("sequence", current.sequence), ("entityId", current.entity.ToString(CultureInfo.InvariantCulture)), ("observedAtUnixMilliseconds", current.utc), ("hardpoints", current.hardpoints != 0), ("cargoScoop", current.cargoScoop != 0), ("landingGear", current.landingGear != 0), ("fsdState", current.fsdState), ("appliedGeneration", current.applied));
            if (op == "input.set")
            {
                var entity = ulong.Parse(request.Required("entityId").GetString()!, CultureInfo.InvariantCulture);
                var expected = request.Required("sequence").GetUInt64(); var deadline = request.Required("expiresAtUnixMilliseconds").GetUInt64(); var utc = Utc();
                var field = request.Required("input").GetString() switch { "hardpoints" => 1u, "cargoScoop" => 2u, "landingGear" => 3u, _ => 0u };
                Frame reference; lock (frameLock) reference = history[expected % 32];
                if (field == 0 || expected == 0 || reference.sequence != expected || reference.entity != current.entity || reference.input != current.input || now - reference.tick > 200 || entity != current.entity || deadline < utc || deadline - utc > 300 || current.fsdState != 2 || request.Required("value").ValueKind is not (JsonValueKind.True or JsonValueKind.False)) throw new InvalidOperationException("Invalid or expired game input request");
                var gen = ++generation;
                var next = new Work { generation = gen, expires = now + deadline - utc, entity = entity, frameSequence = expected, field = field, value = request.Required("value").GetBoolean() };
                lock (workLock) work = next;
                return Obj(("generation", gen), ("queued", true));
            }
            throw new InvalidOperationException("Unsupported game interaction operation");
        }
    }
    internal static uint Start(nuint argument)
    {
        lock (initializeLock)
        {
            if (started) return owningThread == argument ? 1u : 0u;
            if (argument == 0 || argument > uint.MaxValue || ImageHash(ModulePath()) != GameHash) return 0;
            image = ImageBase; imageEnd = ImageEnd;
            if (Read<byte>(image + DriveGetter) == 0xe9) return 0;
            var status = HookInitialize(); if (status is not (0 or 1)) return 0;
            owningThread = (uint)argument;
            if (HookCreate(image + DriveGetter, (nuint)(delegate* unmanaged<nuint, nuint>)&Hook, out original) != 0) return 0;
            if (!Pin((nuint)(delegate* unmanaged<nuint, nuint>)&Hook)) return 0;
            if (HookEnable(image + DriveGetter) != 0) return 0;
            try { PipeServer.StartJson("Interaction", 8192, Handle); }
            catch (Exception) { HookDisable(image + DriveGetter); return 0; }
            started = true; return 1;
        }
    }
}

#if PLUGIN_INTERACTION
internal static class InteractionExports
{
    [UnmanagedCallersOnly(EntryPoint = "XFEInteractionStart")]
    internal static uint XFEInteractionStart(nuint argument)
    { try { return InteractionRuntime.Start(argument); } catch (Exception) { return 0; } }
}
#endif