using System.Diagnostics; using System.Globalization; using System.Text; using System.Text.Json; using XFEEDToolBox.Core.Navigation; namespace XFEEDToolBox.Core.Native; /// Fixed-build, read-only menu definition/cache evidence. No dispatch or game calls. public static class NativePauseMenuDiagnostics { public static async Task 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();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();var currentButtons=new List<(string Name,ulong Address)>(); for(uint i=0;i64||count>0&&buttons<0x10000)throw new InvalidDataException("Original button list exceeds bound."); var rows=new List(); for(uint n=0;n(); var inspectedRows=cacheCount>0?cacheCount:Math.Min(cacheCapacity,64); for(ulong i=0;cache>=0x10000&&i();var mismatches=new List();var recognisedNames=new List(); 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,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();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}; } }