using System.Security.Cryptography; using System.Text; using XFEEDToolBox.Core.Navigation; namespace XFEEDToolBox.Core.Native; /// Copies the original navigation catalogue and affine body poses; no native invocation or selection. public static class NativeSystemLocationReader { public const string GameSha256="ed5a8f8cd10f9a80d3bda5badd7ba1d4fcfc15996c8c7e018852cb170e264b63"; private const ulong SystemMask=0x007FFFFFFFFFFFFF; private readonly record struct Pose(Vector3d Right,Vector3d Up,Vector3d Forward,Vector3d Position) { public Vector3d Rotate(Vector3d v)=>Right*v.X+Up*v.Y+Forward*v.Z; public Pose Compose(Pose other)=>new(other.Rotate(Right),other.Rotate(Up),other.Rotate(Forward),other.Rotate(Position)+other.Position); public bool Valid=>Right.Finite&&Up.Finite&&Forward.Finite&&Position.Finite&&Math.Abs(Right.Length-1)<.005&&Math.Abs(Up.Length-1)<.005&&Math.Abs(Forward.Length-1)<.005 &&Math.Abs(Vector3d.Dot(Right,Up))<.005&&Math.Abs(Vector3d.Dot(Up,Forward))<.005&&Math.Abs(Vector3d.Dot(Forward,Right))<.005; } public static CurrentSystemSnapshot Read(IReadOnlyGameMemory memory,ulong entity,long sequence,DateTimeOffset observedAt,string systemName) { var (fsd,life,root)=memory.Player(entity);var nav=memory.U64(fsd+0x338); var table=memory.U64(nav); if(!memory.InImage(table)||memory.U64(table+0x20)!=memory.ImageBase+0x2EC8100||memory.U64(table+0xB8)!=memory.ImageBase+0x2EC8D60) throw new InvalidDataException("当前导航目录接口不匹配。"); // The registered pose ID is recovered from the pinned getter, not a guessed dense slot. var getter=memory.Read(memory.ImageBase+0x8AE690,12); if(getter[0]!=0x8B||getter[1]!=0x05||!getter.AsSpan(6,6).SequenceEqual(new byte[]{0x89,0x02,0x48,0x8B,0xC2,0xC3}))throw new InvalidDataException("飞船坐标接口不匹配。"); var typeAt=checked((ulong)((long)memory.ImageBase+0x8AE696+BitConverter.ToInt32(getter,2))); var pose=memory.Component(root,memory.U32(typeAt)); if(memory.U64(memory.U64(pose)+0x28)!=memory.ImageBase+0x3CBBFE0||memory.U64(memory.U64(pose)+0x60)!=memory.ImageBase+0x2D9C200) throw new InvalidDataException("飞船坐标类型不匹配。"); var world=memory.U64(pose+0xE8);var system=(long)(world&SystemMask); var service=memory.U64(fsd+0x6A0); if(memory.U64(memory.U64(service)+0x60)!=memory.ImageBase+0x3CEB360)throw new InvalidDataException("星系天体服务不匹配。"); var resource=Map(memory,memory.U64(service-0x58+0x278),memory.U64(service-0x58+0x280),(ulong)system,24,true); if(resource==0||memory.U32(resource+4)!=5||memory.U32(resource+0x90)==0)throw new InvalidDataException("等待当前星系天体资源。"); var bodies=memory.U64(resource+0x140);var bodyCapacity=memory.U64(resource+0x148); memory=FreezeTransforms(memory,resource); var reference=Map(memory,bodies,bodyCapacity,world,16,false); var ship=BodyPose(memory,reference,pose+0xE8,resource); var count=memory.U64(nav+0x2C0);var capacity=memory.U64(nav+0x2C8);var storage=memory.U64(nav+0x2D0);var selected=memory.I32(nav+0x38C); if(count>1024||capacity is 0 or >16384||(capacity&(capacity-1))!=0||count>capacity||storage==0)throw new InvalidDataException("当前星系地点表超出校验范围。"); var occupied=memory.Read(storage,checked((int)((capacity+7)/8)));var entries=storage+(((capacity>>3)+7)&~7UL); var rows=new List();var visited=0; for(ulong bucket=0;bucket>3]&(1<<(int)(bucket&7)))==0)continue; visited++;var entry=entries+bucket*16;var key=memory.I32(entry);var record=memory.U64(entry+8); var id=memory.U64(record+0x20);var kind=memory.I32(record+0x184);var type=memory.I32(record+0x188); if(type!=1||id==0||(id&SystemMask)!=(ulong)system)continue; var name=Name(memory,record+0x140);if(name.Length==0)continue; var secondary=Name(memory,record+0x148);var market=memory.U32(record+0x130);long? marketId=market is 0 or uint.MaxValue?null:market; Vector3d? point=null;double? distance=null;uint? starType=null; try { var body=Map(memory,bodies,bodyCapacity,id,16,false); if(body!=0) { point=BodyPose(memory,body,record+0x20,resource).Position;distance=(point.Value-ship.Position).Length; if(kind==1&&memory.U64(body+0x18)==id&&memory.U32(body+0x20)==1&&memory.U64(body)==memory.ImageBase+0x553F7E0) {var typeId=memory.U32(body+0x448);if(typeId<=43)starType=typeId;} } } catch(InvalidDataException){/* Some transient signal targets have no loaded physical body yet. */} var identity=Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes($"{system}:{key}:{id}:{kind}:{type}:{marketId}:{name}"))); rows.Add(new(key,id,name,secondary,kind,Category(kind),marketId,point,distance,key==selected,identity){NativeStarType=starType}); if(memory.U64(entry+8)!=record||memory.U64(record+0x20)!=id||memory.I32(record+0x184)!=kind||memory.U32(record+0x130)!=market)throw new InvalidDataException("当前星系地点正在变化。"); } if(visited!=(int)count||memory.U64(nav+0x2C0)!=count||memory.U64(nav+0x2C8)!=capacity||memory.U64(nav+0x2D0)!=storage||memory.U64(pose+0xE8)!=world ||memory.U64(life+0xB0)!=entity)throw new InvalidDataException("当前星系或飞船身份正在变化。"); var orbits=NativeSystemOrbitReader.Read(memory,resource,system,observedAt,rows,id=>Map(memory,bodies,bodyCapacity,id,16,false)); if(memory.U64(pose+0xE8)!=world||memory.U64(life+0xB0)!=entity)throw new InvalidDataException("当前星系或飞船身份正在变化。"); return new(system,systemName,entity,sequence,observedAt,rows){PlayerPosition=ship.Position,PlayerReferenceWorld=world,PlayerForward=ship.Forward,Orbits=orbits}; } public static SystemLocationCategory Category(int kind)=>kind switch { 1=>SystemLocationCategory.Star,2=>SystemLocationCategory.LandablePlanet,3 or 4=>SystemLocationCategory.Planet,5 or 19=>SystemLocationCategory.Asteroid, _ when NativeDockingTargetKinds.IsStation((uint)kind)=>SystemLocationCategory.Station,14=>SystemLocationCategory.Carrier,28 or 35=>SystemLocationCategory.Settlement, 22 or 23 or 25=>SystemLocationCategory.Signal,40=>SystemLocationCategory.PointOfInterest,_=>SystemLocationCategory.Other }; private static string Name(IReadOnlyGameMemory m,ulong at) { var text=m.U64(at);if(text==0)return "";var length=m.U64(text);if(length is 0 or >512)throw new InvalidDataException("星系地点名称无效。"); var name=new UTF8Encoding(false,true).GetString(m.Read(text+0x14,(int)length)); return new string(name.Where(c=>!char.IsControl(c)).ToArray()).Trim(); } private static Vector3d Vector(IReadOnlyGameMemory m,ulong at){var bytes=m.Read(at,24);return new(BitConverter.ToDouble(bytes),BitConverter.ToDouble(bytes,8),BitConverter.ToDouble(bytes,16));} private static Pose Affine(IReadOnlyGameMemory m,ulong at)=>new(Vector(m,at),Vector(m,at+0x20),Vector(m,at+0x40),Vector(m,at+0x60)); private static Pose BodyPose(IReadOnlyGameMemory m,ulong body,ulong universal,ulong resource) { if(body==0||m.U64(body+0x378)!=resource)throw new InvalidDataException("天体坐标资源未就绪。"); var index=m.U32(body+0x384);if(index>=4096||index>=m.U64(resource+0x380))throw new InvalidDataException("天体变换超出校验范围。"); var transforms=m.U64(resource+0x388);var result=Affine(m,universal+0x10); if(m.Read(universal+0x90,1)[0]!=0)result=result.Compose(Affine(m,transforms+index*0x180+0x80)); result=result.Compose(Affine(m,transforms+index*0x180+0x100)); if(m.U64(resource+0x388)!=transforms||!result.Valid)throw new InvalidDataException("天体变换正在更新。");return result; } // The original resource recomputes every global pose in one rotating // reference frame (3D01260). Mixing live per-body reads across its updates // gives geometrically incompatible points, even for a stationary ship. private static IReadOnlyGameMemory FreezeTransforms(IReadOnlyGameMemory memory,ulong resource) { for(var attempt=0;attempt<3;attempt++) { var count=memory.U64(resource+0x380);var pointer=memory.U64(resource+0x388);var clock=memory.U64(resource+0x288); if(count is 0 or >4096||pointer==0||!double.IsFinite(BitConverter.UInt64BitsToDouble(clock)))throw new InvalidDataException("天体变换超出校验范围。"); var bytes=checked((int)(count*0x180)); byte[] Copy() { var data=new byte[bytes]; for(var offset=0;offsetsource.ImageBase; public ulong ImageSize=>source.ImageSize; public byte[] Read(ulong at,int length) { if(at>=address&&at-address<(ulong)bytes.Length) { if(length<1||(ulong)length>(ulong)bytes.Length-(at-address))throw new InvalidDataException("天体变换超出校验范围。"); return bytes.AsSpan(checked((int)(at-address)),length).ToArray(); } return source.Read(at,length); } } private static uint Hash(ulong key) { unchecked{var value=key*0x40000+~key;value=(value>>31^value)*21;value=(value>>11^value)*65;return (uint)(value>>22)^(uint)value;} } private static ulong Map(IReadOnlyGameMemory m,ulong table,ulong capacity,ulong key,int payload,bool system) { if(table==0||capacity is 0 or >65536)throw new InvalidDataException("星系天体索引超出校验范围。"); var sentinel=table+8*(Hash(key)%capacity);var node=m.U64(sentinel);var count=0; while(node!=0&&node!=sentinel&&++count<=4096) { if(m.U64(node+8)==key&&(!system||m.Read(node+16,1)[0]==0))return m.U64(node+(ulong)payload); node=m.U64(node); } if(node!=sentinel&&node!=0)throw new InvalidDataException("星系天体索引链无效。");return 0; } }