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));
}
}
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));
}
}