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