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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

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

namespace XFEEDToolBox.Core.Native;

/// <summary>Pure reads of the pinned original Star/Planet orbital elements and their existing parent frame.</summary>
public static class NativeSystemOrbitReader
{
    private const ulong SystemMask=0x007FFFFFFFFFFFFF;
    private static readonly (ulong Rva,string Bytes)[] Layout=
    [
        (0x29EB59A,"410f108f300100000f540dd7f43f02660f2f0dafac730276754d8d4770450fb6ce488d542468498bcde828190000"),
        (0x3D00FA0,"48895c2408574883ec30488bd90f297424204883c170498bf90f28f1e8ff936bfe"),
        (0x23BA7A7,"4c8d87800100000f28050b6fa202488d542440"),
        (0x3D0180D,"49039e88030000488bd3e824ffbbfc")
    ];
    private readonly record struct Frame(Vector3d X,Vector3d Y,Vector3d Z,Vector3d Position)
    {
        public Vector3d Rotate(Vector3d v)=>X*v.X+Y*v.Y+Z*v.Z;
        public bool Valid=>Unit(X)&&Unit(Y)&&Unit(Z)&&Position.Finite
            &&Math.Abs(Vector3d.Dot(X,Y))<1e-5&&Math.Abs(Vector3d.Dot(X,Z))<1e-5&&Math.Abs(Vector3d.Dot(Y,Z))<1e-5;
    }
    private static bool Unit(Vector3d v)=>v.Finite&&Math.Abs(v.Length-1)<1e-5;
    private static Vector3d Vector(byte[] bytes,int offset)=>new(BitConverter.ToDouble(bytes,offset),BitConverter.ToDouble(bytes,offset+8),BitConverter.ToDouble(bytes,offset+16));
    private static Frame Affine(byte[] bytes)=>new(Vector(bytes,0),Vector(bytes,0x20),Vector(bytes,0x40),Vector(bytes,0x60));

    public static IReadOnlyList<SystemOrbit> Read(IReadOnlyGameMemory memory,ulong resource,long system,DateTimeOffset observedAt,
        IReadOnlyList<SystemLocation> locations,Func<ulong,ulong> bodyFor)
    {
        var bodies=locations.Where(location=>location.Category is SystemLocationCategory.Star or SystemLocationCategory.Planet or SystemLocationCategory.LandablePlanet).ToArray();
        // Existing location reads remain usable if a future client changes just
        // the orbital layout. No unverified axis is substituted in that case.
        if(Layout.Any(part=>!memory.Read(memory.ImageBase+part.Rva,part.Bytes.Length/2).AsSpan().SequenceEqual(Convert.FromHexString(part.Bytes))))return [];
        var result=new List<SystemOrbit>();
        foreach(var child in bodies)
        {
            var unavailable=new SystemOrbit{SystemAddress=system,ChildWorldAddress=child.WorldAddress,ChildIdentity=child.Identity,
                Source="Native.StellarForge.Orbit/"+NativeSystemLocationReader.GameSha256,Epoch=observedAt,
                UnavailableReason="原生天体轨道或父坐标尚未就绪。"};
            try
            {
                var body=bodyFor(child.WorldAddress);
                if(body==0||memory.U64(body+0x18)!=child.WorldAddress||memory.U64(body+0x378)!=resource)throw new InvalidDataException("轨道天体身份不匹配。");
                var kind=memory.U32(body+0x20);var table=memory.U64(body);
                if(kind is not (1 or 2)||table!=memory.ImageBase+(kind==1?0x553F7E0UL:0x553F860UL)
                    ||memory.U64(table+0x20)!=memory.ImageBase+(kind==1?0x3D00E20UL:0x3D00FA0UL))throw new InvalidDataException("该天体没有已验证的恒星或行星轨道接口。");
                var elements=memory.Read(body+0x70,0x120);var basisBytes=memory.Read(body+0x1F0,0x80);
                var a=BitConverter.ToDouble(elements,0x100);var e=BitConverter.ToDouble(elements,0xE0);var period=BitConverter.ToDouble(elements,0xC0);
                var orbit=unavailable with{SemiMajorAxisMetres=a,Eccentricity=e,PeriodSeconds=double.IsFinite(period)?period:null,
                    OrbitalInclinationDegrees=Degrees(elements,0),ArgumentOfPeriapsisDegrees=Degrees(elements,0x20),AscendingNodeDegrees=Degrees(elements,0x40),
                    Quality=SystemOrbitQuality.ElementsOnly};
                if(!orbit.HasEllipticElements||period<=0||!double.IsFinite(period))throw new InvalidDataException("原生天体没有有效的椭圆轨道。");
                var parent=memory.U64(body+0x350);
                if(parent==0)throw new InvalidDataException("该天体没有已定位的原生父焦点。");
                var parentAddress=memory.U64(parent+0x18);var index=memory.U32(parent+0x384);var transforms=memory.U64(resource+0x388);
                if(parentAddress==0||(parentAddress&SystemMask)!=(ulong)system||bodyFor(parentAddress)!=parent
                    ||memory.U64(parent+0x378)!=resource||index>=4096||index>=memory.U64(resource+0x380)||transforms==0)throw new InvalidDataException("原生轨道父体或变换身份不匹配。");
                var parentBytes=memory.Read(transforms+index*0x180+0x100,0x80);var parentFrame=Affine(parentBytes);var basis=Affine(basisBytes);
                if(!parentFrame.Valid||!basis.Valid)throw new InvalidDataException("原生轨道基向量无效。");
                // 23BA3C0 composes [r*cos(nu),0,r*sin(nu)] with Orbit+180.
                // 3D01260 then composes that local orbit with parent slot+100.
                // Parent slot+80 is the independent spin and is never used here.
                var periapsis=parentFrame.Rotate(basis.X);var transverse=parentFrame.Rotate(basis.Z);
                if(!Unit(periapsis)||!Unit(transverse)||Math.Abs(Vector3d.Dot(periapsis,transverse))>=1e-5)
                    throw new InvalidDataException("原生轨道平面未通过正交校验。");
                // WorldAddress is a reference body, also shared by nearby
                // carriers, settlements and POIs. Only its celestial-body row
                // can supply the parent's catalogue identity/centre.
                var parents=bodies.Where(location=>location.WorldAddress==parentAddress).ToArray();
                if(parents.Length>1)throw new InvalidDataException("原生轨道父体在目录中不唯一。");
                var parentLocation=parents.SingleOrDefault();
                if(parentLocation?.Position is {} parentPoint&&(parentPoint-parentFrame.Position).Length>Math.Max(1,a*1e-7))
                    throw new InvalidDataException("轨道父焦点与目录坐标不一致。");
                if(child.Position is not {Finite:true} childPoint||!ContainsPoint(orbit,periapsis,transverse,childPoint-parentFrame.Position))
                    throw new InvalidDataException("原生轨道平面与当前天体位置不一致。");
                // Static elements/basis and both owners must remain unchanged.
                // Motion is not frozen: the shape check tolerates only1e-4 of a.
                if(!elements.AsSpan().SequenceEqual(memory.Read(body+0x70,elements.Length))||!basisBytes.AsSpan().SequenceEqual(memory.Read(body+0x1F0,basisBytes.Length))
                    ||memory.U64(body+0x350)!=parent||memory.U64(body+0x18)!=child.WorldAddress||memory.U64(body+0x378)!=resource
                    ||memory.U64(parent+0x18)!=parentAddress||memory.U64(parent+0x378)!=resource||memory.U32(parent+0x384)!=index
                    ||memory.U64(resource+0x388)!=transforms||bodyFor(parentAddress)!=parent)
                    throw new InvalidDataException("原生轨道身份或参数在读取期间变化。");
                result.Add(orbit with{ParentWorldAddress=parentAddress,ParentIdentity=parentLocation?.Identity??$"native-body:{system}:{parentAddress}",
                    ParentName=parentLocation?.Name??Name(memory,parent+0x28),ParentPosition=parentFrame.Position,
                    PeriapsisDirection=periapsis,TransverseDirection=transverse,Quality=SystemOrbitQuality.NativeVerified,UnavailableReason=""});
            }
            catch(InvalidDataException error){result.Add(unavailable with{UnavailableReason=error.Message});}
        }
        return result;
    }
    private static double? Degrees(byte[] bytes,int offset)
    {
        var value=BitConverter.ToDouble(bytes,offset);return double.IsFinite(value)?value*180/Math.PI:null;
    }
    private static bool ContainsPoint(SystemOrbit orbit,Vector3d x,Vector3d z,Vector3d relative)
    {
        var a=orbit.SemiMajorAxisMetres!.Value;var e=orbit.Eccentricity!.Value;
        var length=relative.Length;var minor=a*Math.Sqrt(1-e*e);
        if(!relative.Finite||!double.IsFinite(length)||minor<=0||!double.IsFinite(minor))return false;
        if(Math.Abs(Vector3d.Dot(relative,Vector3d.Cross(x,z)))>Math.Max(1,a*1e-4))return false;
        var u=Vector3d.Dot(relative,x)/a+e;var v=Vector3d.Dot(relative,z)/minor;
        return double.IsFinite(u)&&double.IsFinite(v)&&Math.Abs(u*u+v*v-1)<1e-4;
    }
    private static string Name(IReadOnlyGameMemory memory,ulong at)
    {
        var pointer=memory.U64(at);if(pointer==0)return "原生父焦点";
        var length=memory.U64(pointer);if(length>512)throw new InvalidDataException("原生父体名称超出范围。");
        return length==0?"原生父焦点":new UTF8Encoding(false,true).GetString(memory.Read(pointer+0x14,(int)length));
    }
}