using System.Text; using XFEEDToolBox.Core.Native; using XFEEDToolBox.Core.Navigation; using XFEExtension.NetCore.XUnit.Attributes; namespace XFEEDToolBox.Test; [TestFixture] public class NativeSystemPlayerPoseTests { private sealed class Memory:IReadOnlyGameMemory { public ulong ImageBase=>0x100000000; public ulong ImageSize=>0x7000000; private readonly Dictionary bytes=[]; internal Action? BeforeRead; public byte[] Read(ulong address,int length){BeforeRead?.Invoke(address);return Enumerable.Range(0,length).Select(index=>bytes.GetValueOrDefault(address+(ulong)index)).ToArray();} internal void Put(ulong at,byte[] data){for(var index=0;indexPut(at,BitConverter.GetBytes(value)); internal void U(ulong at,uint value)=>Put(at,BitConverter.GetBytes(value)); internal void V(ulong at,Vector3d value){Put(at,BitConverter.GetBytes(value.X));Put(at+8,BitConverter.GetBytes(value.Y));Put(at+16,BitConverter.GetBytes(value.Z));} internal void Affine(ulong at,Vector3d right,Vector3d up,Vector3d forward,Vector3d position) {V(at,right);V(at+0x20,up);V(at+0x40,forward);V(at+0x60,position);} } private const ulong World=(3UL<<56)|100; private const ulong Pose=0x52000,Record=0x90000; private static Memory Scene() { var m=new Memory();var image=m.ImageBase;const ulong block=0x20000,life=0x24000,root=0x28000,owner=0x2A000,nav=0x30000,service=0x40058; var fsd=block+0x80; m.Q(image+0x5F2F040+0x68,0xEC0);m.Q(image+0x5F2F040+0x30,block);m.Q(block+0x38,1); m.Q(fsd,image+0x5316CC8);m.Q(fsd+0x58,image+0x5316DF8);m.Q(fsd+0x20,owner);m.Q(owner,image+0x1000); m.Q(image+0x1020,image+0x1100);m.Put(image+0x1100,[0x48,0x8D,0x41,0x10,0xC3]);m.Q(owner+0x10,life); m.U(life+0xC0,8);m.Q(life+0xB0,42);m.Q(life+0xC8,root+0x30); m.Q(fsd+0x338,nav);m.Q(nav,image+0x1800);m.Q(image+0x1820,image+0x2EC8100);m.Q(image+0x18B8,image+0x2EC8D60); var getter=new byte[]{0x8B,0x05,0,0,0,0,0x89,0x02,0x48,0x8B,0xC2,0xC3}; BitConverter.GetBytes(unchecked((int)(0x2000-0x8AE696))).CopyTo(getter,2);m.Put(image+0x8AE690,getter);m.U(image+0x2000,123); m.Q(root+0x10,0x48000);m.Q(0x48018,1);m.Q(0x48030,0x50000);m.Q(0x50000+123*16,0);m.Q(root+0x18,0x51000);m.Q(0x51000,Pose); m.Q(Pose,image+0x2100);m.Q(image+0x2128,image+0x3CBBFE0);m.Q(image+0x2160,image+0x2D9C200);m.Q(Pose+0xE8,World); m.Affine(Pose+0xF8,new(1,0,0),new(0,1,0),new(0,0,1),new(10,20,30)); m.Q(fsd+0x6A0,service);m.Q(service,image+0x3000);m.Q(image+0x3060,image+0x3CEB360); m.Q(service-0x58+0x278,0x60000);m.Q(service-0x58+0x280,1);m.Q(0x60000,0x62000);m.Q(0x62000,0x60000);m.Q(0x62008,100);m.Q(0x62018,0x64000); m.U(0x64004,5);m.U(0x64090,1);m.Q(0x64140,0x66000);m.Q(0x64148,1);m.Q(0x66000,0x67000);m.Q(0x67000,0x66000);m.Q(0x67008,World);m.Q(0x67010,0x68000); m.Q(0x68378,0x64000);m.U(0x68384,0);m.Q(0x64380,1);m.Q(0x64388,0x70000); // A90-degree body rotation distinguishes shared system coordinates // from either an arbitrary ship-centred origin or local body offsets. m.Affine(0x70100,new(0,1,0),new(-1,0,0),new(0,0,1),new(1000,2000,3000)); m.Q(nav+0x2C0,1);m.Q(nav+0x2C8,8);m.Q(nav+0x2D0,0x80000);m.U(nav+0x38C,7);m.Put(0x80000,[1]);m.U(0x80008,7);m.Q(0x80010,Record); m.Q(Record+0x20,World);m.Affine(Record+0x30,new(1,0,0),new(0,1,0),new(0,0,1),new(40,50,60)); m.U(Record+0x184,3);m.U(Record+0x188,1);m.U(Record+0x130,uint.MaxValue);m.Q(Record+0x140,0x91000);m.Q(0x91000,4);m.Put(0x91014,Encoding.UTF8.GetBytes("Body")); return m; } [Test]public void ShipAndDestinationShareTheVerifiedRotatedSystemCoordinates() { var snapshot=NativeSystemLocationReader.Read(Scene(),42,1,DateTimeOffset.UtcNow,"Fixture"); Check.Equal(new Vector3d(980,2010,3030),snapshot.PlayerPosition);Check.Equal(new Vector3d(0,0,1),snapshot.PlayerForward); var place=snapshot.Locations.Single();Check.Equal(new Vector3d(950,2040,3060),place.Position); Check.That(Math.Abs(place.DistanceMetres!.Value-Math.Sqrt(2700))<1e-10); } [Test]public void APlaceWithoutLoadedGeometryDoesNotReceiveAnInventedPosition() { var memory=Scene();memory.Q(Record+0x20,(4UL<<56)|100); var snapshot=NativeSystemLocationReader.Read(memory,42,1,DateTimeOffset.UtcNow,"Fixture"); Check.That(snapshot.PlayerPosition is {Finite:true}&&snapshot.Locations.Single().Position is null&&snapshot.Locations.Single().DistanceMetres is null); } [Test]public void InvalidActualShipPoseCannotBecomeAZeroOrigin() { var memory=Scene();memory.V(Pose+0xF8+0x60,new(double.NaN,20,30)); Check.Throws(()=>NativeSystemLocationReader.Read(memory,42,1,DateTimeOffset.UtcNow,"Fixture")); } [Test]public void AllPlacesAndShipUseOneCapturedFrameEvenIfTheLiveReferenceRotatesDuringNameReads() { var memory=Scene(); memory.BeforeRead=at=>{if(at==0x91000)memory.Affine(0x70100,new(1,0,0),new(0,0,1),new(0,-1,0),new(9000,8000,7000));}; var snapshot=NativeSystemLocationReader.Read(memory,42,1,DateTimeOffset.UtcNow,"Fixture"); Check.Equal(new Vector3d(980,2010,3030),snapshot.PlayerPosition); Check.Equal(new Vector3d(950,2040,3060),snapshot.Locations.Single().Position); } [Test]public void ATransformBufferThatChangesBetweenBothCopiesNeverBecomesASnapshot() { var memory=Scene();var change=0; memory.BeforeRead=at=>{if(at==0x70000)memory.V(0x70160,new(1000+ ++change,2000,3000));}; Check.Throws(()=>NativeSystemLocationReader.Read(memory,42,1,DateTimeOffset.UtcNow,"Fixture")); Check.Equal(6,change); } }