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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Security.Cryptography;
using System.Text;
using XFEEDToolBox.Core.Navigation;

namespace XFEEDToolBox.Core.Native;

/// <summary>Copies the original navigation catalogue and affine body poses; no native invocation or selection.</summary>
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<SystemLocation>();var visited=0;
        for(ulong bucket=0;bucket<capacity;bucket++)
        {
            if((occupied[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;offset<data.Length;offset+=8192)
                {var size=Math.Min(8192,data.Length-offset);memory.Read(pointer+(ulong)offset,size).CopyTo(data,offset);}
                return data;
            }
            var first=Copy();var second=Copy();
            if(memory.U64(resource+0x380)==count&&memory.U64(resource+0x388)==pointer&&memory.U64(resource+0x288)==clock&&first.AsSpan().SequenceEqual(second))
                return new FrozenTransforms(memory,pointer,first);
        }
        throw new InvalidDataException("天体变换正在更新。");
    }
    private sealed class FrozenTransforms(IReadOnlyGameMemory source,ulong address,byte[] bytes):IReadOnlyGameMemory
    {
        public ulong ImageBase=>source.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;
    }
}