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 System.Text;
using System.Text.Json;
using XFEEDToolBox.Core.Navigation;
namespace XFEEDToolBox.Core.Native;

/// <summary>Fixed-build, read-only menu definition/cache evidence. No dispatch or game calls.</summary>
public static class NativePauseMenuDiagnostics
{
    public static async Task<object> ReadAsync(int processId,CancellationToken token=default)
    {
        using var process=Process.GetProcessById(processId);var started=process.StartTime.ToUniversalTime().Ticks;
        var pipe=new NavigationPipeClient(processId,"SessionRecovery0514");var nav=new NavigationPipeClient(processId);
        var before=await pipe.CallAsync(new{op="read"},token);var health=await nav.CallAsync(new{op="health.read"},token);
        static ulong Address(JsonElement value,string name)=>ulong.Parse(value.GetProperty(name).GetString()!,NumberStyles.AllowHexSpecifier,CultureInfo.InvariantCulture);
        static void CheckHealth(JsonElement value)
        {
            var age=DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()-value.GetProperty("observedAtUnixMilliseconds").GetInt64();
            if(age is <0 or >500||!value.GetProperty("localPlayerShipVerified").GetBoolean()||!value.GetProperty("shipHealth").GetProperty("verified").GetBoolean())throw new InvalidDataException("Fresh verified health required.");
        }
        CheckHealth(health);
        if(before.GetProperty("processId").GetInt32()!=processId||before.GetProperty("state").GetString()!="Pause"||before.GetProperty("nextState").GetString()!="Pause")throw new InvalidDataException("Stable original Pause menu required.");
        var activity=Address(before,"menuAddress");var flow=Address(before,"flow");var csm=Address(before,"csm");
        using var memory=new ReadOnlyPlayerMemory(processId,started,NativeSystemLocationReader.GameSha256);
        if(activity<0x10000||memory.U64(activity+0xC8)!=flow||memory.U64(memory.U64(activity+0x80)+8)!=memory.ImageBase+0x4838B50||memory.U64(csm)!=memory.ImageBase+0x513BE00)throw new InvalidDataException("Original menu ownership differs.");
        string Text(ulong address)
        {
            if(address<0x10000)throw new InvalidDataException("Missing original menu text.");
            var bytes=new List<byte>();for(var i=0;i<128;i++){var value=memory.Read(address+(ulong)i,1)[0];if(value==0)return Encoding.ASCII.GetString(bytes.ToArray());if(value is <32 or >126)throw new InvalidDataException("Non-ASCII original menu identifier.");bytes.Add(value);}throw new InvalidDataException("Menu identifier exceeds bound.");
        }
        string SlotName(ulong at)=>Text(memory.U64(memory.U64(at)));
        var definition=memory.U64(activity+0x10);var currentIndex=memory.U32(activity+0x108);var categories=memory.U32(definition+0x10);var categoryData=memory.U64(definition+8);
        if(categoryData<0x10000||categories is 0 or >128||currentIndex>=categories)throw new InvalidDataException("Original categories exceed bound.");
        // Original erase 47FA930: container(activity+138)[2] is size,
        // [3] is data and [0] is capacity. +140 is not a live row count.
        var cacheCount=memory.U64(activity+0x148);var cacheCapacity=memory.U64(activity+0x138);var cache=memory.U64(activity+0x150);
        if(cacheCount>64||cacheCapacity>4096||cacheCount>cacheCapacity)throw new InvalidDataException("Original cache exceeds bound.");
        var categoryRows=new List<object>();var currentButtons=new List<(string Name,ulong Address)>();
        for(uint i=0;i<categories;i++)
        {
            token.ThrowIfCancellationRequested();var category=categoryData+i*0x58;var kind=memory.Read(category,1)[0];var count=memory.U32(category+0x48);var buttons=memory.U64(category+0x40);
            if(count>64||count>0&&buttons<0x10000)throw new InvalidDataException("Original button list exceeds bound.");
            var rows=new List<object>();
            for(uint n=0;n<count;n++)
            {
                var button=buttons+n*0x78;var name=SlotName(button);rows.Add(new{index=n,name,address=button.ToString("x")});if(i==currentIndex)currentButtons.Add((name,button));
            }
            categoryRows.Add(new{index=i,kind,name=SlotName(category+8),buttonCount=count,buttons=rows,current=i==currentIndex});
        }
        var cacheRows=new List<object>();
        var inspectedRows=cacheCount>0?cacheCount:Math.Min(cacheCapacity,64);
        for(ulong i=0;cache>=0x10000&&i<inspectedRows;i++)
        {
            var row=cache+i*0xC8;var bytes=memory.Read(row,0xC8);var copies=new List<int>();var mismatches=new List<object>();var recognisedNames=new List<object>();
            if(i<(ulong)currentButtons.Count)
            {
                var original=memory.Read(currentButtons[(int)i].Address,0x78);
                for(var offset=0;offset<=0x50;offset+=8)if(original.AsSpan().SequenceEqual(bytes.AsSpan(offset,0x78)))copies.Add(offset);
                for(var offset=0;offset<0x78;offset+=8)
                {
                    var expected=BitConverter.ToUInt64(original,offset);var actual=BitConverter.ToUInt64(bytes,0x30+offset);
                    if(expected!=actual)mismatches.Add(new{offset=offset.ToString("x"),definition=expected.ToString("x"),cached=actual.ToString("x")});
                }
            }
            for(var offset=0;offset<0xA0;offset+=8)
                try{var name=SlotName(row+(ulong)offset);if(currentButtons.Any(button=>button.Name==name))recognisedNames.Add(new{offset=offset.ToString("x"),name});}catch{}
            cacheRows.Add(new{index=i,address=row.ToString("x"),committed=i<cacheCount,uncommittedStorageCannotAuthorizeAction=i>=cacheCount,flags=BitConverter.ToUInt16(bytes).ToString("x4"),copyOffsets=copies,recognisedNames,expectedOffset30Mismatches=mismatches,bytes=Convert.ToHexStringLower(bytes)});
        }
        var after=await pipe.CallAsync(new{op="read"},token);var healthAfter=await nav.CallAsync(new{op="health.read"},token);CheckHealth(healthAfter);
        if(after.GetProperty("state").GetString()!="Pause"||Address(after,"menuAddress")!=activity||Address(after,"flow")!=flow||Address(after,"csm")!=csm||health.GetProperty("entityId").GetString()!=healthAfter.GetProperty("entityId").GetString()||memory.U32(activity+0x108)!=currentIndex||memory.U64(activity+0x150)!=cache||memory.U64(activity+0x148)!=cacheCount||memory.U64(activity+0x138)!=cacheCapacity)throw new InvalidDataException("Menu or main ship changed during read-only snapshot.");
        memory.Validate();
        var rawHeader=new Dictionary<string,string>();foreach(var offset in new ulong[]{0x138,0x140,0x148,0x150,0x158,0x160})rawHeader[offset.ToString("x")]=memory.U64(activity+offset).ToString("x");
        return new{at=DateTimeOffset.UtcNow,processId,started,entity=health.GetProperty("entityId").GetString(),activity=activity.ToString("x"),flow=flow.ToString("x"),csm=csm.ToString("x"),definition=definition.ToString("x"),currentIndex,categories,cacheCount,cacheCapacity,rawHeader,cache=cache.ToString("x"),countdown=BitConverter.ToSingle(memory.Read(activity+0x164,4)),categoryRows,cacheRows,processAccess="QUERY_INFORMATION | VM_READ",gameCallsSent=0,gameWritesSent=0,osInputSent=false};
    }
}