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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Diagnostics;
using System.Globalization;
using XFEEDToolBox.Core.Navigation;

namespace XFEEDToolBox.Core.Native;

/// <summary>Read-only provenance check for the original FSD and docking action registries.</summary>
public static class NativeDockingInputDiagnostics
{
    public static Task<object> ReadAsync(int processId,CancellationToken token=default)=>ReadCoreAsync(processId,null,token);
    public static Task<object> ReadForStationAsync(int processId,long marketId,CancellationToken token=default)
        =>marketId is <=0 or >uint.MaxValue?throw new ArgumentOutOfRangeException(nameof(marketId)):ReadCoreAsync(processId,marketId,token);
    private static async Task<object> ReadCoreAsync(int processId,long? stationMarketId,CancellationToken token)
    {
        using var process=Process.GetProcessById(processId);
        var started=process.StartTime.ToUniversalTime().Ticks;
        var pipe=new NavigationPipeClient(processId);
        var frame=await pipe.CallAsync(new{op="health.read"},token);
        static void FreshHealth(System.Text.Json.JsonElement value)
        {
            var age=DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()-value.GetProperty("observedAtUnixMilliseconds").GetInt64();
            if(!value.GetProperty("localPlayerShipVerified").GetBoolean()||!value.GetProperty("shipHealth").GetProperty("verified").GetBoolean()||age is <0 or >500)
                throw new InvalidDataException("Fresh verified main ship health is required for read-only input diagnostics.");
        }
        FreshHealth(frame);
        var entity=ulong.Parse(frame.GetProperty("entityId").GetString()!,CultureInfo.InvariantCulture);
        using var memory=new ReadOnlyPlayerMemory(processId,started,NativeSystemLocationReader.GameSha256);
        var (fsd,life,root)=memory.Player(entity);
        var computer=memory.Component(root,memory.U32(memory.ImageBase+0x5F5317C));
        if(memory.U64(computer)!=memory.ImageBase+0x5165248)throw new InvalidDataException("Original docking computer differs.");
        var fsdController=memory.U64(fsd+0x700);var dockingController=memory.U64(computer+0x240);
        if(fsdController==0||fsdController!=dockingController||memory.U64(memory.U64(fsdController)+0x158)!=memory.ImageBase+0x25E5D10)
            throw new InvalidDataException("Original throttle controller ownership differs.");
        var fsdActions=memory.U64(fsd+0x6F8);var dockingActions=memory.U64(computer+0x230);
        object Registry(ulong value,out Dictionary<string,ulong> bindings)
        {
            if(memory.U64(value)!=memory.ImageBase+0x512FD90||memory.U64(memory.U64(value)+0x40)!=memory.ImageBase+0x8FEDC0)
                throw new InvalidDataException("Original action registry differs.");
            var buckets=memory.U64(value+0xC0);var count=memory.U64(value+0xC8);
            if(buckets==0||count is 0 or >4096)throw new InvalidDataException("Original action registry exceeds bound.");
            bindings=new(StringComparer.Ordinal);
            var seen=new HashSet<ulong>();object? zero=null,absoluteSet=null,thrust=null;
            for(ulong i=0;i<count;i++)
            {
                var node=memory.U64(buckets+i*8);
                while(node<buckets||node>=buckets+count*8)
                {
                    if(node==0||!seen.Add(node)||seen.Count>4096)throw new InvalidDataException("Original action registry chain differs.");
                    var name=memory.U64(node+8);var length=memory.U64(name);
                    if(length is 0 or >128)throw new InvalidDataException("Original action name exceeds bound.");
                    var originalName=System.Text.Encoding.ASCII.GetString(memory.Read(name+0x14,(int)length));
                    if(originalName is "SetSpeedZero" or "ForwardReverseThrust_Set" or "ForwardReverseThrust")
                    {
                        var binding=memory.U64(node+0x10);var bytes=memory.Read(binding,0x38);bindings.Add(originalName,binding);
                        var bindingName=BitConverter.ToUInt64(bytes,8);var bindingLength=memory.U64(bindingName);
                        var bindingNameMatches=bindingLength==(ulong)originalName.Length&&memory.Read(bindingName+0x14,originalName.Length).AsSpan().SequenceEqual(System.Text.Encoding.ASCII.GetBytes(originalName));
                        var input=new{originalName,binding=binding.ToString("x"),mapName=name.ToString("x"),bindingName=bindingName.ToString("x"),bindingLength,bindingNameEqualsMapKey=bindingName==name,bindingNameMatches,
                            registryOwnerMatches=BitConverter.ToUInt64(bytes,0x10)==value+0xB8,
                            value=BitConverter.ToSingle(bytes,0x18),pending=BitConverter.ToInt32(bytes,0x1C),forced=bytes[0x24],consumed=BitConverter.ToInt32(bytes,0x2C),consumedForced=bytes[0x34]};
                        if(originalName=="SetSpeedZero")zero=input;else if(originalName=="ForwardReverseThrust_Set")absoluteSet=input;else thrust=input;
                    }
                    node=memory.U64(node);
                }
            }
            if(memory.U64(value+0xC0)!=buckets||memory.U64(value+0xC8)!=count)throw new InvalidDataException("Original action registry changed.");
            return new{address=value.ToString("x"),zero,absoluteSet,thrust};
        }
        var originalFsd=Registry(fsdActions,out _);var originalDocking=Registry(dockingActions,out var dockingBindings);
        var physics=memory.U64(fsd+0x2A8)-0x58;var inputRoot=memory.U64(physics+0x3B0)-0x60;
        if(memory.U64(inputRoot+0x2F8)!=fsdController)throw new InvalidDataException("Original ship input controller differs.");
        var dockingThrottle=memory.U64(computer+0x260);
        float Float(ulong address)=>BitConverter.ToSingle(memory.Read(address,4));
        object domain;ulong domainTable=0,domainGetter=0,domainRoot=0;uint domainEventType=0;var eventSources=new HashSet<ulong>();
        try
        {
            var context=memory.U64(fsdActions-0x50);var getter=memory.U64(context);var objectRoot=memory.LeafReference(fsdActions-0x50,getter);
            domainTable=context;domainGetter=getter;domainRoot=objectRoot;var eventType=memory.U32(memory.ImageBase+0x5F2DFF4);domainEventType=eventType;
            // Same original service-domain chain used by 869FC0. The pinned
            // leaf getters and registry key are also checked by GalaxyCatalog.
            if(memory.U64(memory.U64(objectRoot)+0x20)!=memory.ImageBase+0x8C51E0)throw new InvalidDataException("Original input service-domain getter differs.");
            var scope=memory.LeafReference(objectRoot,memory.ImageBase+0x8C51E0);
            if(scope==0||memory.U64(memory.U64(scope))!=memory.ImageBase+0x5299D0||memory.U32(memory.ImageBase+0x5F05AB8)!=1)throw new InvalidDataException("Original input scope differs.");
            var scopeSlots=memory.U64(scope+0x20);var scopeCount=memory.U64(scope+0x28);if(scopeSlots==0||scopeCount is 0 or >1024)throw new InvalidDataException("Original input scope map exceeds bound.");
            ulong serviceContext=0;var scopeSeen=new HashSet<ulong>();var scopeContexts=new List<(uint Kind,ulong Context)>();
            for(ulong i=0;i<scopeCount;i++)
            {
                var head=scopeSlots+i*8;var item=memory.U64(head);
                while(item!=head)
                {
                    if(item<0x10000||!scopeSeen.Add(item)||scopeSeen.Count>1024)throw new InvalidDataException("Original input scope chain differs.");
                    var kind=memory.U32(item+8);var childContext=memory.U64(item+0x10);scopeContexts.Add((kind,childContext));
                    if(kind==1){if(serviceContext!=0)throw new InvalidDataException("Original service context is ambiguous.");serviceContext=childContext;}
                    item=memory.U64(item);
                }
            }
            if(serviceContext==0||memory.U64(memory.U64(serviceContext)+0x18)!=memory.ImageBase+0x1F3150)throw new InvalidDataException("Original service context differs.");
            var serviceLife=memory.U64(serviceContext+0x568);if(serviceLife==0||memory.U32(serviceLife+0xC0)!=8)throw new InvalidDataException("Original service life differs.");
            var services=memory.U64(serviceLife+0xC8)-0x30;var events=memory.Component(services,eventType);
            var table=events!=0?memory.U64(events):0;
            if(table!=memory.ImageBase+0x512EC98||memory.U64(table+0x18)!=memory.ImageBase+0x90C170)throw new InvalidDataException("Original input event registry differs.");
            eventSources.Add(events);var contextEvents=new List<object>();
            foreach(var (kind,child) in scopeContexts)
            {
                try
                {
                    var lifeGetter=memory.U64(memory.U64(child)+0x18);
                    if(lifeGetter!=memory.ImageBase+0x1F3150){contextEvents.Add(new{kind,lifeGetterRva=memory.InImage(lifeGetter)?(lifeGetter-memory.ImageBase).ToString("x"):"outside-image",checkedContext=false});continue;}
                    var childLife=memory.U64(child+0x568);if(childLife==0||memory.U32(childLife+0xC0)!=8)continue;
                    var childRegistry=memory.U64(childLife+0xC8)-0x30;var inputEvents=memory.Component(childRegistry,eventType);
                    if(memory.U64(inputEvents)!=memory.ImageBase+0x512EC98||memory.U64(memory.U64(inputEvents)+0x18)!=memory.ImageBase+0x90C170)throw new InvalidDataException("Different original event registry.");
                    eventSources.Add(inputEvents);contextEvents.Add(new{kind,events=inputEvents.ToString("x"),checkedContext=true});
                }
                catch(Exception error){contextEvents.Add(new{kind,issue=error.GetType().Name});}
            }
            domain=new{contextTable=context.ToString("x"),getterRva=memory.InImage(getter)?(getter-memory.ImageBase).ToString("x"):"outside-image",objectRoot=objectRoot.ToString("x"),serviceContext=serviceContext.ToString("x"),services=services.ToString("x"),eventType,events=events.ToString("x"),eventTableRva=memory.InImage(table)?(table-memory.ImageBase).ToString("x"):"outside-image",
                subscribeRva=table!=0?(memory.U64(table+0x18)-memory.ImageBase).ToString("x"):"0",contextEvents};
        }
        catch(Exception error){domain=new{issue=error.GetType().Name,detail=error.Message,contextTable=domainTable.ToString("x"),getterRva=memory.InImage(domainGetter)?(domainGetter-memory.ImageBase).ToString("x"):"outside-image",objectRoot=domainRoot.ToString("x"),eventType=domainEventType};}
        object dockingConsumer;
        try
        {
            var updateObject=computer-0x58;var configuration=memory.U64(updateObject+0x50);var controls=memory.U64(updateObject+0x180);var policy=memory.U64(updateObject+0x198);
            var stateInterface=memory.U64(updateObject+0x298);var stateTable=memory.U64(stateInterface);var flags=memory.Read(updateObject+0x3CA,6);
            var profileMode=memory.I32(stateInterface+0x5F8);var profileIndex=memory.I32(stateInterface+0x560);var profileCount=memory.U32(stateInterface+0x530);var profile=stateInterface+0x2E0;
            if(profileMode==2&&profileIndex>=0&&(uint)profileIndex<profileCount&&profileCount<=128)profile=memory.U64(stateInterface+0x538)+(ulong)profileIndex*0x248+0x18;
            var fallback=memory.U64(stateInterface+0x80);var fallbackTable=memory.U64(fallback);var fallback78=memory.U64(fallbackTable+0x78);
            string Getter(ulong offset){var getter=memory.U64(stateTable+offset);return memory.InImage(getter)?(getter-memory.ImageBase).ToString("x"):"unverified";}
            object Interface(ulong pointer,params ulong[] offsets)
            {
                var table=memory.U64(pointer);if(!memory.InImage(table))throw new InvalidDataException("Original consumer interface is outside the pinned image.");
                return new{address=pointer.ToString("x"),vtableRva=(table-memory.ImageBase).ToString("x"),slots=offsets.ToDictionary(offset=>offset.ToString("x"),offset=>{var method=memory.U64(table+offset);return memory.InImage(method)?(method-memory.ImageBase).ToString("x"):"unverified";})};
            }
            object Activity(ulong address)
            {
                if(address==0)return new{available=false};var table=memory.U64(address);var getter=memory.U64(table+0x20);
                if(!memory.InImage(table)||!memory.InImage(getter))throw new InvalidDataException("Original activity interface is outside the pinned image.");
                var code=memory.Read(getter,16);int? state=null;
                if(code[0]==0xB8&&code[5]==0xC3)state=BitConverter.ToInt32(code,1);
                else if(code[0]==0x31&&code[1]==0xC0&&code[2]==0xC3)state=0;
                else if(code[0]==0x8B&&code[1]==0x81&&code[6]==0xC3)state=memory.I32(checked((ulong)((long)address+BitConverter.ToInt32(code,2))));
                else if(code[0]==0x8B&&code[1]==0x41&&code[3]==0xC3)state=memory.I32(checked((ulong)((long)address+(sbyte)code[2])));
                return new{available=true,address=address.ToString("x"),vtableRva=(table-memory.ImageBase).ToString("x"),stateGetterRva=(getter-memory.ImageBase).ToString("x"),leafState=state,functionsCalled=false};
            }
            var duty=memory.U64(updateObject+0x228);var currentActivity=memory.U64(duty+0x1E8);var activities=new List<object>();var activityBuckets=memory.U64(duty+0x1A8);var activityCapacity=memory.U64(duty+0x1B0);
            if(activityCapacity>512)throw new InvalidDataException("Original activity set exceeds the read-only bound.");var activityNodes=new HashSet<ulong>();
            for(ulong bucket=0;bucket<activityCapacity;bucket++)
            {
                var head=activityBuckets+bucket*8;var entry=memory.U64(head);
                while(entry!=head)
                {
                    if(entry<0x10000||!activityNodes.Add(entry)||activityNodes.Count>512)throw new InvalidDataException("Original activity set chain differs.");
                    activities.Add(Activity(memory.U64(entry+8)));entry=memory.U64(entry);
                }
            }
            dockingConsumer=new{computer=computer.ToString("x"),updateObject=updateObject.ToString("x"),moduleSlot=memory.I32(updateObject+0x3B8),mode=memory.U32(updateObject+0x3BC),
                timer3C0=Float(updateObject+0x3C0),timer3C4=Float(updateObject+0x3C4),flags3CAto3CF=flags.Select(x=>(int)x).ToArray(),
                source=memory.U64(updateObject+0x2B8).ToString("x"),sourceMatches=memory.U64(updateObject+0x2B8)==dockingThrottle,
                configuration=configuration.ToString("x"),threshold18=Float(configuration+0x18),delay1C=Float(configuration+0x1C),
                controls=controls.ToString("x"),controlB8=memory.Read(controls+0xB8,1)[0],controlB9=memory.Read(controls+0xB9,1)[0],controlBB=memory.Read(controls+0xBB,1)[0],
                policy=policy.ToString("x"),policy08=memory.Read(policy+8,1)[0],stateInterface=stateInterface.ToString("x"),stateTableRva=memory.InImage(stateTable)?(stateTable-memory.ImageBase).ToString("x"):"unverified",
                stateGetter90Rva=Getter(0x90),stateGetterA0Rva=Getter(0xA0),profileMode,profileIndex,profileCount,profile=profile.ToString("x"),
                profile1E5=memory.Read(profile+0x1E5,1)[0],profile1E6=memory.Read(profile+0x1E6,1)[0],profile1E8=memory.Read(profile+0x1E8,1)[0],
                fallback78Rva=memory.InImage(fallback78)?(fallback78-memory.ImageBase).ToString("x"):"unverified",
                commandF0=Interface(memory.U64(updateObject+0xF0),0,0x20,0x28,0x30,0x40,0x48,0x58,0x60),notify1B0=Interface(memory.U64(updateObject+0x1B0),0xF0,0xF8),
                blockSourceA8=Interface(memory.U64(updateObject+0xA8),0x30),blockSet1C8=memory.U64(updateObject+0x1C8).ToString("x"),
                duty228=Interface(memory.U64(updateObject+0x228),0xA0,0xA8),padC0=Interface(memory.U64(updateObject+0xC0),0xC8),
                currentActivity=Activity(currentActivity),activities,activityCapacity,stationHandle2B0=memory.U64(updateObject+0x2B0).ToString("x"),functionsCalled=false,fieldsAreRawOffsets=true};
        }
        catch(Exception error){dockingConsumer=new{issue=error.GetType().Name,detail=error.Message,functionsCalled=false};}
        var graph=ReadInputGraph(memory,dockingActions,dockingThrottle,dockingBindings,eventSources,token);
        object stationService;
        if(stationMarketId is null)stationService=new{requested=false,functionsCalled=false};
        else try
        {
            var expectedMarket=(ulong)stationMarketId.Value;
            var context=memory.U64(life+0x38);var count=memory.U64(context+0x148);var capacity=memory.U64(context+0x150);var storage=memory.U64(context+0x158);
            if(capacity is <8 or >65536||(capacity&(capacity-1))!=0||count>capacity)throw new InvalidDataException("Original entity directory bound differs.");
            var bits=memory.Read(storage,checked((int)((capacity+7)/8)));var entries=storage+(((capacity>>3)+7)&~7UL);var own=false;ulong stationLife=0;
            for(ulong i=0;i<capacity;i++)
            {
                if((bits[i>>3]&(1<<(int)(i&7)))==0)continue;var entry=memory.Read(entries+i*16,16);var key=BitConverter.ToUInt64(entry,0);var value=BitConverter.ToUInt64(entry,8);
                if(key==entity)own=value==life;if(key==expectedMarket){if(stationLife!=0)throw new InvalidDataException("Original target station is ambiguous.");stationLife=value;}
            }
            if(!own||stationLife==0||memory.U32(stationLife+0xC0)!=8||memory.U64(stationLife+0xB0)!=expectedMarket)throw new InvalidDataException("Original target station identity is unavailable.");
            var stationRoot=memory.U64(stationLife+0xC8)-0x30;var type=memory.U32(memory.ImageBase+0x5F771D0);var metadata=memory.U64(stationRoot+0x10);var componentCount=memory.U64(metadata+0x18);
            if(type is 0 or >99999||componentCount is 0 or >1024)throw new InvalidDataException("Original station component layout differs.");
            var slots=memory.U64(metadata+0x30);var slot=memory.U64(slots+type*16UL);
            if(slot>=componentCount)stationService=new{marketId=expectedMarket,entityVerified=true,componentAvailable=false,type,slot,componentCount,functionsCalled=false};
            else
            {
                var service=memory.U64(memory.U64(stationRoot+0x18)+slot*8);var table=memory.U64(service);var active=memory.U64(table+0x28);var allowed=memory.U64(table+0x58);
                if(!memory.InImage(table)||!memory.InImage(active)||!memory.InImage(allowed))throw new InvalidDataException("Original target station service is outside the pinned image.");
                stationService=new{marketId=expectedMarket,entityVerified=true,componentAvailable=true,type,slot,componentCount,address=service.ToString("x"),vtableRva=(table-memory.ImageBase).ToString("x"),activeGetterRva=(active-memory.ImageBase).ToString("x"),allowedGetterRva=(allowed-memory.ImageBase).ToString("x"),functionsCalled=false};
            }
        }
        catch(Exception error){stationService=new{issue=error.GetType().Name,detail=error.Message,functionsCalled=false};}
        var after=await pipe.CallAsync(new{op="health.read"},token);FreshHealth(after);
        if(after.GetProperty("entityId").GetString()!=frame.GetProperty("entityId").GetString())throw new InvalidDataException("Main ship changed during read-only input diagnostics.");
        memory.Validate();var afterPlayer=memory.Player(entity);
        if(afterPlayer!=(fsd,life,root)||memory.U64(fsd+0x6F8)!=fsdActions||memory.U64(computer+0x230)!=dockingActions)throw new InvalidDataException("Main ship, FSD or original registry changed during diagnostics.");
        return new{entity=entity.ToString(),fsd=fsd.ToString("x"),computer=computer.ToString("x"),sameRegistry=fsdActions==dockingActions,
            controllerThrottle=Float(fsdController+0x680),controllerDemand=Float(fsdController+0x684),
            inputRoot=inputRoot.ToString("x"),actualThrottle=Float(inputRoot+0x70),dockingThrottleSource=dockingThrottle.ToString("x"),
            dockingThrottleAliasesActualInput=dockingThrottle+0x18==inputRoot+0x70,
            dockingThrottle=dockingThrottle>0x10000?Float(dockingThrottle+0x18):float.NaN,
            originalFsd,originalDocking,domain,graph,stationService,dockingConsumer,processAccess="QUERY_INFORMATION | VM_READ",mutationsSent=0};
    }

    private static object ReadInputGraph(ReadOnlyPlayerMemory memory,ulong actions,ulong source,IReadOnlyDictionary<string,ulong> bindings,IReadOnlyCollection<ulong> eventSources,CancellationToken token)
    {
        // 8F0BA0 owns this bounded registry graph table. Follow only its heap
        // pointers; never enumerate arbitrary process regions or call a node.
        var buckets=memory.U64(actions+0x78);var count=memory.U64(actions+0x80);
        if(buckets==0||count is 0 or >4096)throw new InvalidDataException("Original graph table exceeds bound.");
        var records=new HashSet<ulong>();var roots=new List<ulong>();var flightEntries=new List<ulong>();var relatedOriginalFlightNames=new List<string>();
        for(ulong i=0;i<count;i++)
        {
            var item=memory.U64(buckets+i*8);
            while(item<buckets||item>=buckets+count*8)
            {
                if(item==0||!records.Add(item)||records.Count>4096)throw new InvalidDataException("Original graph chain differs.");
                var contextName=memory.U64(item+8);
                if(contextName>=0x10000&&memory.U64(contextName) is >0 and <=128)
                {
                    var bytes=memory.Read(contextName+0x14,checked((int)memory.U64(contextName)));
                    if(bytes.All(b=>b is >=32 and <=126))
                    {
                        var originalName=System.Text.Encoding.ASCII.GetString(bytes);
                        // 8FA080 and 8F9DB0 use the original ASCII case-insensitive
                        // comparison. No cruising/landing prefix match is admitted.
                        if(originalName.Equals("Bindings_Flight",StringComparison.OrdinalIgnoreCase))flightEntries.Add(item);
                        if(originalName.Contains("Flight",StringComparison.OrdinalIgnoreCase))relatedOriginalFlightNames.Add(originalName);
                    }
                }
                var vm=memory.U64(item+0x38);if(vm!=0)roots.Add(vm);
                item=memory.U64(item);
            }
        }
        var queue=new Queue<(ulong Address,int Depth)>();var visited=new HashSet<ulong>();
        foreach(var root in roots)queue.Enqueue((root,0));
        var matches=new List<object>();var unreadable=0;var readBytes=0;var exhausted=false;
        var matchedNodes=new HashSet<ulong>();var vmLists=new List<object>();var matchingSourceNodes=0;var subscriberNodes=new HashSet<ulong>();var activeBindings=new HashSet<ulong>();var flightActiveBindings=new HashSet<ulong>();
        bool HeapPointer(ulong value)=>value>=0x10000&&value<0x7fffffff0000&&(value&7)==0&&!memory.InImage(value);
        string KnownBinding(ulong value)
        {
            foreach(var pair in bindings)if(pair.Value==value)return pair.Key;
            return "different-original-binding";
        }
        string InputBindingName(ulong value)
        {
            var text=memory.U64(value+8);var length=memory.U64(text);if(length is 0 or >64)return "unverified";
            var name=memory.Read(text+0x14,(int)length);return name.All(x=>x is >=32 and <=126)?System.Text.Encoding.ASCII.GetString(name):"unverified";
        }
        void ObserveNode(ulong node,string origin)
        {
            if(memory.U64(node)==memory.ImageBase+0x512E3A0)node-=0x20;
            if(matches.Count>=16||memory.U64(node)!=memory.ImageBase+0x512E330||!matchedNodes.Add(node))return;
            var output=memory.U64(node+0x78);var registry=memory.U64(node+0x40);var absolute=memory.U64(node+0xB0);var zero=memory.U64(node+0xB8);var active=memory.U64(node+0x90);var config=memory.U64(node+0x10);
            var activeByte=memory.Read(active+0x24,1)[0];var accumulator=BitConverter.ToSingle(memory.Read(node+0xD0,4));
            activeBindings.Add(active);
            if(output==source)
            {
                matchingSourceNodes++;
                if(registry==actions&&InputBindingName(active)=="Bindings_Flight_Active"&&memory.U64(active+0x10)==registry+0xB8)flightActiveBindings.Add(active);
            }
            matches.Add(new{node=node.ToString("x"),origin,registry=registry.ToString("x"),registryMatches=registry==actions,output=output.ToString("x"),outputMatches=output==source,
                absolute=absolute.ToString("x"),absoluteName=KnownBinding(absolute),zero=zero.ToString("x"),zeroName=KnownBinding(zero),active=active.ToString("x"),activeName=InputBindingName(active),activeRegistryOwnerMatches=memory.U64(active+0x10)==registry+0xB8,activeByte,activePreviousByte=memory.Read(active+0x34,1)[0],activeValue=BitConverter.ToSingle(memory.Read(active+0x18,4)),accumulator,
                config=config.ToString("x"),configAbsoluteName=memory.U64(config+0x48).ToString("x"),configZeroName=memory.U64(config+0x50).ToString("x")});
        }
        foreach(var eventSource in eventSources)
        {
            // 914D00 subscribes node+20 through 90C170. The latter inserts
            // the exact interface pointer into events+9E0 using 662DC0.
            var set=eventSource+0x9E0;var slots=memory.U64(set);var capacity=memory.U64(set+8);var entries=new HashSet<ulong>();var used=memory.U64(set+0x18);
            if(slots==0||capacity is 0 or >8192||used>4096)throw new InvalidDataException("Original input subscribers exceed bound.");
            for(ulong i=0;i<capacity;i++)
            {
                token.ThrowIfCancellationRequested();var head=slots+i*8;var item=memory.U64(head);
                while(item!=head)
                {
                    if(item<0x10000||!entries.Add(item)||entries.Count>4096)throw new InvalidDataException("Original input subscriber chain differs.");
                    var secondary=memory.U64(item+8);if(secondary>=0x10020)subscriberNodes.Add(secondary-0x20);
                    ObserveNode(secondary,"original-input-event-subscription");item=memory.U64(item);
                }
            }
            if(memory.U64(set)!=slots||memory.U64(set+8)!=capacity||memory.U64(set+0x18)!=used)throw new InvalidDataException("Original input subscribers changed.");
            vmLists.Add(new{eventSource=eventSource.ToString("x"),capacity,used,count=entries.Count,matchingSourceNodes});
        }
        // 4C92420 stores {model,node} at list-entry +10/+18. 4C91960
        // walks core+58 and core+70; this path avoids unrelated heap objects.
        foreach(var vm in roots)
        foreach(var mapOffset in new ulong[]{0x20,0x90})
        {
            var map=vm+0x10+mapOffset;var slots=memory.U64(map);var capacity=memory.U64(map+8);var entries=new HashSet<ulong>();
            if(slots==0||capacity is 0 or >4096)throw new InvalidDataException("Original VM node map exceeds bound.");
            for(ulong i=0;i<capacity;i++)
            {
                var item=memory.U64(slots+i*8);
                while(item<slots||item>=slots+capacity*8)
                {
                    if(item<0x10000||!entries.Add(item)||entries.Count>4096)throw new InvalidDataException("Original VM node map chain differs.");
                    foreach(var field in new ulong[]{8,0x10,0x18,0x20,0x28})
                    {
                        try{var candidate=memory.U64(item+field);if(HeapPointer(candidate)){ObserveNode(candidate,"original-vm-node-map-"+mapOffset.ToString("x"));queue.Enqueue((candidate,0));}}catch{unreadable++;}
                    }
                    item=memory.U64(item);
                }
            }
            vmLists.Add(new{vm=vm.ToString("x"),mapOffset=mapOffset.ToString("x"),capacity,count=entries.Count});
        }
        foreach(var vm in roots)
        foreach(var listOffset in new ulong[]{0x58,0x70})
        {
            var head=vm+0x10+listOffset;var entries=new HashSet<ulong>();var item=memory.U64(head);var types=new Dictionary<string,int>();
            while(item!=head)
            {
                token.ThrowIfCancellationRequested();
                if(item<0x10000||!entries.Add(item)||entries.Count>4096)throw new InvalidDataException("Original VM node list differs.");
                var node=memory.U64(item+0x18);var table=memory.U64(node);var type=memory.InImage(table)?(table-memory.ImageBase).ToString("x"):"outside-image";
                types[type]=types.GetValueOrDefault(type)+1;ObserveNode(node,"original-vm-node-list");item=memory.U64(item);
            }
            vmLists.Add(new{vm=vm.ToString("x"),listOffset=listOffset.ToString("x"),count=entries.Count,types});
        }
        if(matchingSourceNodes>0)queue.Clear();
        while(queue.Count>0)
        {
            token.ThrowIfCancellationRequested();
            if(visited.Count>=4096){exhausted=true;break;}
            var (address,depth)=queue.Dequeue();if(!HeapPointer(address)||!visited.Add(address))continue;
            byte[] bytes;try{bytes=memory.Read(address,512);}catch{unreadable++;continue;}
            readBytes+=bytes.Length;
            for(var offset=0;offset<bytes.Length;offset+=8)
            {
                var value=BitConverter.ToUInt64(bytes,offset);
                if((value==memory.ImageBase+0x512E330||value==memory.ImageBase+0x512E3A0)&&matches.Count<16)
                {
                    var node=address+(ulong)offset;
                    try
                    {
                        ObserveNode(node,"bounded-graph-pointer");
                    }
                    catch{unreadable++;}
                }
                if(depth<6&&HeapPointer(value)&&!visited.Contains(value)&&queue.Count<16384)queue.Enqueue((value,depth+1));
            }
        }
        if(memory.U64(actions+0x78)!=buckets||memory.U64(actions+0x80)!=count)throw new InvalidDataException("Original graph table changed.");
        var activeReferences=new List<object>();
        foreach(var node in subscriberNodes)
        {
            try
            {
                var table=memory.U64(node);if(!memory.InImage(table))continue;var getter=memory.U64(table+8);if(!memory.InImage(getter))continue;
                var code=memory.Read(getter,8);if(code[0]!=0x48||code[1]!=0x8D||code[2]!=5||code[7]!=0xC3)continue;
                var text=checked((ulong)((long)getter+7+BitConverter.ToInt32(code,3)));if(!memory.InImage(text))continue;
                var typeBytes=new List<byte>();for(var n=0;n<96;n++){var b=memory.Read(text+(ulong)n,1)[0];if(b==0)break;if(b is <32 or >126)throw new InvalidDataException("Original node type is not ASCII.");typeBytes.Add(b);}
                var typeName=System.Text.Encoding.ASCII.GetString(typeBytes.ToArray());
                foreach(var active in activeBindings)
                {
                    var offsets=new List<string>();for(ulong offset=0x78;offset<=0xC8;offset+=8)if(memory.U64(node+offset)==active)offsets.Add(offset.ToString("x"));
                    if(offsets.Count>0)activeReferences.Add(new{node=node.ToString("x"),typeName,vtableRva=(table-memory.ImageBase).ToString("x"),binding=active.ToString("x"),fieldOffsets=offsets,candidateReferencesOnly=true});
                }
            }
            catch{unreadable++;}
        }
        object contextAdmission;
        try
        {
            if(flightEntries.Count!=1||flightActiveBindings.Count!=1||matchingSourceNodes!=1)throw new InvalidDataException("One exact Flight profile and current-source Active binding are required.");
            var entry=flightEntries[0];var active=flightActiveBindings.Single();var header=memory.Read(entry+0x10,0x30);
            var layerCount=BitConverter.ToUInt64(header,0x18);var layerData=BitConverter.ToUInt64(header,0x20);var originalVm=BitConverter.ToUInt64(header,0x28);
            if(layerCount>256||layerCount>0&&!HeapPointer(layerData)||originalVm!=0&&!HeapPointer(originalVm))throw new InvalidDataException("Original Flight admission layers exceed their pinned layout bound.");
            var rawLayers=layerCount==0?[]:memory.Read(layerData,checked((int)layerCount*32));var activeByte=memory.Read(active+0x24,1)[0];
            var layers=new List<object>();int? highest=null;
            for(var index=0;index<(int)layerCount;index++)
            {
                var raw=BitConverter.ToUInt64(rawLayers,index*32+0x18);
                if(raw>4096)throw new InvalidDataException("Original Flight layer occupancy exceeds bound.");
                if(raw!=0)highest=index;
                layers.Add(new{index,priority=index/2,originalEnabledLayer=(index&1)==0,rawOccupancy=raw});
            }
            var secondLayers=layerCount==0?[]:memory.Read(layerData,rawLayers.Length);var secondHeader=memory.Read(entry+0x10,header.Length);
            if(!header.AsSpan().SequenceEqual(secondHeader)||!rawLayers.AsSpan().SequenceEqual(secondLayers)||activeByte!=memory.Read(active+0x24,1)[0]
                ||memory.U64(actions+0x78)!=buckets||memory.U64(actions+0x80)!=count||memory.U64(active+0x10)!=actions+0xB8||InputBindingName(active)!="Bindings_Flight_Active")throw new InvalidDataException("Original Flight profile, layer array or Active binding changed during the two reads.");
            var expectedEnabled=highest is {} layer&&(layer&1)==0;
            contextAdmission=new{verified=true,contextName="Bindings_Flight",registry=actions.ToString("x"),entry=entry.ToString("x"),layerCount,layerData=layerData.ToString("x"),originalVm=originalVm.ToString("x"),layers,
                highestOriginalLayer=highest,expectedEnabledByOriginalHighestLayer=expectedEnabled,activeBinding=active.ToString("x"),activeByte,activeMatchesHighestLayer=(activeByte!=0)==expectedEnabled,
                originalVmMatchesAdmission=(originalVm!=0)==expectedEnabled,metadataReadTwice=true,gameCallsSent=0,gameWritesSent=0};
        }
        catch(Exception error){contextAdmission=new{verified=false,issue=error.GetType().Name,detail=error.Message,flightProfileMatches=flightEntries.Count,sourceActiveMatches=flightActiveBindings.Count,matchingSourceNodes,relatedOriginalFlightNames,gameCallsSent=0,gameWritesSent=0};}
        return new{records=records.Count,roots=roots.Count,vmLists,visited=visited.Count,readBytes,unreadable,exhausted,matches,activeReferences,contextAdmission,gameCallsSent=0,gameWritesSent=0};
    }
}