using System.Diagnostics;
using System.Text.Json;
using XFEEDToolBox.Core.Navigation;
namespace XFEEDToolBox.Core.Native;
/// <summary>Build-pinned VM_READ observations only. Does not invoke an SCO or fuel method.</summary>
public static class NativeCruiseOverchargeDiagnostics
{
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);var health=await pipe.CallAsync(new{op="health.read"},token);
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 main ship health required for SCO read-only diagnostics.");
}
CheckHealth(health);var entity=ulong.Parse(health.GetProperty("entityId").GetString()!,System.Globalization.CultureInfo.InvariantCulture);
using var memory=new ReadOnlyPlayerMemory(processId,started,NativeSystemLocationReader.GameSha256);var (_,life,root)=memory.Player(entity);
var metadata=memory.U64(root+0x10);var count=memory.U64(metadata+0x18);var slots=memory.U64(metadata+0x30);var components=memory.U64(root+0x18);
if(count is 0 or >1024||slots==0||components==0)throw new InvalidDataException("Current main ship component registry differs.");
object Component(uint globalRva,ulong expectedTable,bool scoState)
{
var type=memory.U32(memory.ImageBase+globalRva);
if(type is 0 or >99999)return new{globalRva=globalRva.ToString("x"),type,available=false,issue="Not a bounded component type",functionsCalled=false};
var slot=memory.U64(slots+type*16UL);
if(slot>=count)return new{globalRva=globalRva.ToString("x"),type,slot,componentCount=count,available=false,functionsCalled=false};
var component=memory.U64(components+slot*8);
if(component<0x10000)return new{globalRva=globalRva.ToString("x"),type,slot,componentCount=count,address=component.ToString("x"),available=false,issue="Null current interface slot",functionsCalled=false};
var table=memory.U64(component);
if(!memory.InImage(table))throw new InvalidDataException("Current component interface is outside the pinned game image.");
var match=expectedTable!=0&&table==memory.ImageBase+expectedTable;int? state=scoState&&match?memory.Read(component+0x228,1)[0]:null;
var method50=memory.U64(table+0x50);
return new{globalRva=globalRva.ToString("x"),type,slot,componentCount=count,available=true,address=component.ToString("x"),vtableRva=(table-memory.ImageBase).ToString("x"),expectedVtableRva=expectedTable==0?null:expectedTable.ToString("x"),expectedType=expectedTable==0?(bool?)null:match,
method50Rva=memory.InImage(method50)?(method50-memory.ImageBase).ToString("x"):"unverified",stateByte228=state,knownState=state is >=0 and <=3,functionsCalled=false};
}
var scoInterface=Component(0x5F44D00,0x533E6E0,true);var scoClass=Component(0x5FA7550,0x533E5B0,false);var fuelInterface=Component(0x5F45128,0x550E8A8,false);
var scoConsumerTarget=Component(0x5F6BCE4,0,false);
var scoGraph=new List<object>();
var scoType=memory.U32(memory.ImageBase+0x5F44D00);
if(scoType is 0 or >99999)throw new InvalidDataException("Original SCO component type is not bounded.");
var scoSlot=memory.U64(slots+scoType*16UL);
var actualSco=scoSlot<count?memory.U64(components+scoSlot*8):0;
if(actualSco>=0x10000&&memory.U64(actualSco)==memory.ImageBase+0x533E6E0&&memory.U64(actualSco-0x58)==memory.ImageBase+0x533E5B0)
{
foreach(var offset in new ulong[]{0x60,0x150,0x168,0x210,0x258})
{
var raw=memory.U64(actualSco-0x58+offset);var inline=memory.InImage(raw);
var address=inline?actualSco-0x58+offset:raw;var table=inline?raw:address>=0x10000?memory.U64(address):0;
string? Method(ulong slot){if(!memory.InImage(table))return null;var function=memory.U64(table+slot);return memory.InImage(function)?(function-memory.ImageBase).ToString("x"):null;}
scoGraph.Add(new{fieldOffset=offset.ToString("x"),raw=raw.ToString("x"),inlineVtableCandidate=inline,address=address.ToString("x"),vtableAddress=table.ToString("x"),vtableRva=memory.InImage(table)?(table-memory.ImageBase).ToString("x"):null,
method00Rva=Method(0),method08Rva=Method(8),method10Rva=Method(0x10),method18Rva=Method(0x18),method20Rva=Method(0x20),method50Rva=Method(0x50),functionsCalled=false});
}
}
var after=await pipe.CallAsync(new{op="health.read"},token);CheckHealth(after);memory.Validate();
if(after.GetProperty("entityId").GetString()!=health.GetProperty("entityId").GetString()||memory.U64(life+0xB0)!=entity||memory.U64(root+0x10)!=metadata
||memory.U64(root+0x18)!=components||memory.U64(metadata+0x18)!=count||memory.U64(metadata+0x30)!=slots)
throw new InvalidDataException("Main ship or component registry changed during SCO diagnostics.");
return new{at=DateTimeOffset.UtcNow,processId,started,imageBase=memory.ImageBase.ToString("x"),entity=entity.ToString(),scoInterface,scoClass,fuelInterface,scoConsumerTarget,scoGraph,processAccess="QUERY_INFORMATION | VM_READ",gameCallsSent=0,gameWritesSent=0,actionsSent=false};
}
}
using System.Diagnostics;
using System.Text.Json;
using XFEEDToolBox.Core.Navigation;
namespace XFEEDToolBox.Core.Native;
/// <summary>Build-pinned VM_READ observations only. Does not invoke an SCO or fuel method.</summary>
public static class NativeCruiseOverchargeDiagnostics
{
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);var health=await pipe.CallAsync(new{op="health.read"},token);
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 main ship health required for SCO read-only diagnostics.");
}
CheckHealth(health);var entity=ulong.Parse(health.GetProperty("entityId").GetString()!,System.Globalization.CultureInfo.InvariantCulture);
using var memory=new ReadOnlyPlayerMemory(processId,started,NativeSystemLocationReader.GameSha256);var (_,life,root)=memory.Player(entity);
var metadata=memory.U64(root+0x10);var count=memory.U64(metadata+0x18);var slots=memory.U64(metadata+0x30);var components=memory.U64(root+0x18);
if(count is 0 or >1024||slots==0||components==0)throw new InvalidDataException("Current main ship component registry differs.");
object Component(uint globalRva,ulong expectedTable,bool scoState)
{
var type=memory.U32(memory.ImageBase+globalRva);
if(type is 0 or >99999)return new{globalRva=globalRva.ToString("x"),type,available=false,issue="Not a bounded component type",functionsCalled=false};
var slot=memory.U64(slots+type*16UL);
if(slot>=count)return new{globalRva=globalRva.ToString("x"),type,slot,componentCount=count,available=false,functionsCalled=false};
var component=memory.U64(components+slot*8);
if(component<0x10000)return new{globalRva=globalRva.ToString("x"),type,slot,componentCount=count,address=component.ToString("x"),available=false,issue="Null current interface slot",functionsCalled=false};
var table=memory.U64(component);
if(!memory.InImage(table))throw new InvalidDataException("Current component interface is outside the pinned game image.");
var match=expectedTable!=0&&table==memory.ImageBase+expectedTable;int? state=scoState&&match?memory.Read(component+0x228,1)[0]:null;
var method50=memory.U64(table+0x50);
return new{globalRva=globalRva.ToString("x"),type,slot,componentCount=count,available=true,address=component.ToString("x"),vtableRva=(table-memory.ImageBase).ToString("x"),expectedVtableRva=expectedTable==0?null:expectedTable.ToString("x"),expectedType=expectedTable==0?(bool?)null:match,
method50Rva=memory.InImage(method50)?(method50-memory.ImageBase).ToString("x"):"unverified",stateByte228=state,knownState=state is >=0 and <=3,functionsCalled=false};
}
var scoInterface=Component(0x5F44D00,0x533E6E0,true);var scoClass=Component(0x5FA7550,0x533E5B0,false);var fuelInterface=Component(0x5F45128,0x550E8A8,false);
var scoConsumerTarget=Component(0x5F6BCE4,0,false);
var scoGraph=new List<object>();
var scoType=memory.U32(memory.ImageBase+0x5F44D00);
if(scoType is 0 or >99999)throw new InvalidDataException("Original SCO component type is not bounded.");
var scoSlot=memory.U64(slots+scoType*16UL);
var actualSco=scoSlot<count?memory.U64(components+scoSlot*8):0;
if(actualSco>=0x10000&&memory.U64(actualSco)==memory.ImageBase+0x533E6E0&&memory.U64(actualSco-0x58)==memory.ImageBase+0x533E5B0)
{
foreach(var offset in new ulong[]{0x60,0x150,0x168,0x210,0x258})
{
var raw=memory.U64(actualSco-0x58+offset);var inline=memory.InImage(raw);
var address=inline?actualSco-0x58+offset:raw;var table=inline?raw:address>=0x10000?memory.U64(address):0;
string? Method(ulong slot){if(!memory.InImage(table))return null;var function=memory.U64(table+slot);return memory.InImage(function)?(function-memory.ImageBase).ToString("x"):null;}
scoGraph.Add(new{fieldOffset=offset.ToString("x"),raw=raw.ToString("x"),inlineVtableCandidate=inline,address=address.ToString("x"),vtableAddress=table.ToString("x"),vtableRva=memory.InImage(table)?(table-memory.ImageBase).ToString("x"):null,
method00Rva=Method(0),method08Rva=Method(8),method10Rva=Method(0x10),method18Rva=Method(0x18),method20Rva=Method(0x20),method50Rva=Method(0x50),functionsCalled=false});
}
}
var after=await pipe.CallAsync(new{op="health.read"},token);CheckHealth(after);memory.Validate();
if(after.GetProperty("entityId").GetString()!=health.GetProperty("entityId").GetString()||memory.U64(life+0xB0)!=entity||memory.U64(root+0x10)!=metadata
||memory.U64(root+0x18)!=components||memory.U64(metadata+0x18)!=count||memory.U64(metadata+0x30)!=slots)
throw new InvalidDataException("Main ship or component registry changed during SCO diagnostics.");
return new{at=DateTimeOffset.UtcNow,processId,started,imageBase=memory.ImageBase.ToString("x"),entity=entity.ToString(),scoInterface,scoClass,fuelInterface,scoConsumerTarget,scoGraph,processAccess="QUERY_INFORMATION | VM_READ",gameCallsSent=0,gameWritesSent=0,actionsSent=false};
}
}