namespace XFEEDToolBox.Plugin;
internal static unsafe partial class Navigation
{
// Original input-graph node. Its SetSpeedZero branch clears its own
// throttle accumulator before producing ForwardReverseThrust. A pulse
// delivered after the FSD update misses this node's legal evaluation.
private const nuint DockingInputMapper=0x8F3500;
private static nuint originalDockingInputMapper;
private static bool dockingInputMapperReady;
private static ulong dockingInputMapperCalls,dockingInputMapperMatched,dockingInputMapperApplied;
private struct DockingMapperReceipt
{
public ulong sequence,entity,utc;
public float before,after,source;
public bool verified;
}
private static DockingMapperReceipt dockingInputMapperReceipt;
private struct DockingMapperPulse
{
public nuint binding,source,node;
public uint beforeCount,afterCount;
public byte beforeFlags,afterFlags;
public float beforeAccumulator;
public ulong entity,sequence;
public bool applied;
}
private static bool DockingMapperNodeMatches(nuint node,nuint binding,nuint source)
{
if(node<0x10000||binding<0x10000||source<0x10000)return false;
// Both input and output must alias the exact current ship's original
// registry bindings. Matching a node type or offset alone is unsafe.
return Pointer(node+0xB8)==binding&&Pointer(node+0x78)==source;
}
private static bool BeginDockingMapperPulse(nuint node,nuint binding,nuint source,out DockingMapperPulse pulse)
{
pulse=default;
if(!DockingMapperNodeMatches(node,binding,source)||Read<int>(binding)<=0)return false;
var count=Read<uint>(binding+0x1C);var flags=Read<byte>(binding+0x24);
var accumulator=Read<float>(node+0xD0);
if(count==uint.MaxValue||!float.IsFinite(accumulator)||Math.Abs(accumulator)>1.001f)return false;
pulse=new(){node=node,binding=binding,source=source,beforeCount=count,afterCount=count+1,beforeFlags=flags,afterFlags=(byte)(flags|1),beforeAccumulator=accumulator,applied=true};
Write(binding+0x1C,pulse.afterCount);Write(binding+0x24,pulse.afterFlags);
return true;
}
private static void EndDockingMapperPulse(in DockingMapperPulse pulse)
{
if(!pulse.applied||!DockingMapperNodeMatches(pulse.node,pulse.binding,pulse.source))return;
// Do not erase a later physical input or another original consumer's
// update. Only restore the temporary logical pulse we actually wrote.
if(Read<uint>(pulse.binding+0x1C)!=pulse.afterCount||Read<byte>(pulse.binding+0x24)!=pulse.afterFlags)return;
Write(pulse.binding+0x1C,pulse.beforeCount);Write(pulse.binding+0x24,pulse.beforeFlags);
}
private static bool PrepareDockingMapperPulse(nuint node,float dt,out DockingMapperPulse pulse)
{
pulse=default;dockingInputMapperCalls++;
if(!dockingInputMapperReady||!float.IsFinite(dt)||dt<=0||dt>.25f||GetCurrentThreadId()!=owningThread)return false;
var now=GetTickCount64();if(Volatile.Read(ref dockingZeroUpdate.command.expires)<=now)return false;
var frame=Current();DockingZeroUpdate wanted;LocalSelection selection;LocalFlightObservation observed;
lock(localLock){wanted=dockingZeroUpdate;selection=localSelection;observed=localObservation;}
if(wanted.command.action!=10)return false;
if(!DockingZeroUpdateMatches(wanted,frame,selection,wanted.computer,now,controllerEpoch,
clientOwner.Length!=0,Enabled(),inGame,overridden)||localConfirmed!=selection.generation||fsdOwned||!BoundFsd(frame,frame.fsd))return false;
var computer=LocalDockingComputer(frame);var receipt=dockingZeroInputReceipt;
if(computer!=wanted.computer||computer==0||!LocalDockingAvailable(computer)||Pointer(computer+0x240)!=wanted.controller
||receipt.entity!=frame.entity||receipt.life!=frame.life||receipt.market!=wanted.market||receipt.inputService!=Pointer(computer+0x230)
||now<receipt.tick||now-receipt.tick>=30000||!observed.valid||observed.entity!=frame.entity||observed.inSupercruise
||observed.distance>7500||now<observed.tick||now-observed.tick>200||!SameLocal(observed.target,selection.target))return false;
if((byte)Call(image+0xED6620,computer)!=0)
{lock(localLock)dockingZeroUpdate.command.expires=0;return false;}
var source=Pointer(computer+0x260);
if(!DockingMapperNodeMatches(node,receipt.binding,source))return false;
// The registry clears this frame's aggregate before evaluating the
// node. Its zero at entry does not describe the retained accumulator;
// let the original SetSpeedZero branch handle that state itself.
if(!ReadOriginalDockingThrottle(computer,out _))return false;
var active=Pointer(node+0x90);
if(active<0x10000||Read<byte>(active+0x24)==0)return false;
var dock=LocalDocking(frame);
if(dock==0||Read<uint>(dock+0x614)!=2||Read<uint>(dock+0x4C0)is not(3 or 4)||Read<byte>(dock+0x4E9)==255)return false;
var station=Pointer(dock+0x4E0);
if(station==0||Read<uint>(station+0xC0)!=8||Read<ulong>(station+0xB0)!=wanted.market||!LocalRecordMatches(Pointer(frame.fsd+0x338),selection.target))return false;
bool released;lock(leaseLock)released=lease.expires<=now&&fsdLease.expires<=now;
if(!released)return false;
dockingInputMapperMatched++;
if(!BeginDockingMapperPulse(node,receipt.binding,source,out pulse))return false;
pulse.entity=frame.entity;pulse.sequence=frame.sequence;return true;
}
[UnmanagedCallersOnly]
private static ulong DockingInputMapperHook(nuint node,float dt)
{
DockingMapperPulse pulse=default;
DockingFixturePulse fixture=default;
try{if(!PrepareDockingFixturePulse(node,dt,out fixture))PrepareDockingMapperPulse(node,dt,out pulse);}catch{try{FixtureExceptionEvidence();}catch{}}
try{return Call(originalDockingInputMapper,(ulong)node,F32(dt));}
catch{return 0;}
finally
{
try
{
if(pulse.applied&&DockingMapperNodeMatches(pulse.node,pulse.binding,pulse.source))
{
var accumulator=Read<float>(pulse.node+0xD0);var source=Read<float>(pulse.source+0x18);
var valid=float.IsFinite(accumulator)&&Math.Abs(accumulator)<=1.001f&&float.IsFinite(source)&&Math.Abs(source)<=1.001f;
lock(localLock)dockingInputMapperReceipt=new(){sequence=pulse.sequence,entity=pulse.entity,utc=UtcMilliseconds(),before=pulse.beforeAccumulator,
after=valid?accumulator:0,source=valid?source:0,verified=valid};
if(valid&&Math.Abs(accumulator)<=.001f&&Math.Abs(source)<=.001f)dockingInputMapperApplied++;
}
}
catch{}
finally
{
try{EndDockingMapperPulse(pulse);}catch{}
try{EndDockingFixturePulse(fixture);}catch{}
try{ObserveDockingFixtureMapper(node,fixture,pulse);}catch{}
}
}
}
}
namespace XFEEDToolBox.Plugin;
internal static unsafe partial class Navigation
{
// Original input-graph node. Its SetSpeedZero branch clears its own
// throttle accumulator before producing ForwardReverseThrust. A pulse
// delivered after the FSD update misses this node's legal evaluation.
private const nuint DockingInputMapper=0x8F3500;
private static nuint originalDockingInputMapper;
private static bool dockingInputMapperReady;
private static ulong dockingInputMapperCalls,dockingInputMapperMatched,dockingInputMapperApplied;
private struct DockingMapperReceipt
{
public ulong sequence,entity,utc;
public float before,after,source;
public bool verified;
}
private static DockingMapperReceipt dockingInputMapperReceipt;
private struct DockingMapperPulse
{
public nuint binding,source,node;
public uint beforeCount,afterCount;
public byte beforeFlags,afterFlags;
public float beforeAccumulator;
public ulong entity,sequence;
public bool applied;
}
private static bool DockingMapperNodeMatches(nuint node,nuint binding,nuint source)
{
if(node<0x10000||binding<0x10000||source<0x10000)return false;
// Both input and output must alias the exact current ship's original
// registry bindings. Matching a node type or offset alone is unsafe.
return Pointer(node+0xB8)==binding&&Pointer(node+0x78)==source;
}
private static bool BeginDockingMapperPulse(nuint node,nuint binding,nuint source,out DockingMapperPulse pulse)
{
pulse=default;
if(!DockingMapperNodeMatches(node,binding,source)||Read<int>(binding)<=0)return false;
var count=Read<uint>(binding+0x1C);var flags=Read<byte>(binding+0x24);
var accumulator=Read<float>(node+0xD0);
if(count==uint.MaxValue||!float.IsFinite(accumulator)||Math.Abs(accumulator)>1.001f)return false;
pulse=new(){node=node,binding=binding,source=source,beforeCount=count,afterCount=count+1,beforeFlags=flags,afterFlags=(byte)(flags|1),beforeAccumulator=accumulator,applied=true};
Write(binding+0x1C,pulse.afterCount);Write(binding+0x24,pulse.afterFlags);
return true;
}
private static void EndDockingMapperPulse(in DockingMapperPulse pulse)
{
if(!pulse.applied||!DockingMapperNodeMatches(pulse.node,pulse.binding,pulse.source))return;
// Do not erase a later physical input or another original consumer's
// update. Only restore the temporary logical pulse we actually wrote.
if(Read<uint>(pulse.binding+0x1C)!=pulse.afterCount||Read<byte>(pulse.binding+0x24)!=pulse.afterFlags)return;
Write(pulse.binding+0x1C,pulse.beforeCount);Write(pulse.binding+0x24,pulse.beforeFlags);
}
private static bool PrepareDockingMapperPulse(nuint node,float dt,out DockingMapperPulse pulse)
{
pulse=default;dockingInputMapperCalls++;
if(!dockingInputMapperReady||!float.IsFinite(dt)||dt<=0||dt>.25f||GetCurrentThreadId()!=owningThread)return false;
var now=GetTickCount64();if(Volatile.Read(ref dockingZeroUpdate.command.expires)<=now)return false;
var frame=Current();DockingZeroUpdate wanted;LocalSelection selection;LocalFlightObservation observed;
lock(localLock){wanted=dockingZeroUpdate;selection=localSelection;observed=localObservation;}
if(wanted.command.action!=10)return false;
if(!DockingZeroUpdateMatches(wanted,frame,selection,wanted.computer,now,controllerEpoch,
clientOwner.Length!=0,Enabled(),inGame,overridden)||localConfirmed!=selection.generation||fsdOwned||!BoundFsd(frame,frame.fsd))return false;
var computer=LocalDockingComputer(frame);var receipt=dockingZeroInputReceipt;
if(computer!=wanted.computer||computer==0||!LocalDockingAvailable(computer)||Pointer(computer+0x240)!=wanted.controller
||receipt.entity!=frame.entity||receipt.life!=frame.life||receipt.market!=wanted.market||receipt.inputService!=Pointer(computer+0x230)
||now<receipt.tick||now-receipt.tick>=30000||!observed.valid||observed.entity!=frame.entity||observed.inSupercruise
||observed.distance>7500||now<observed.tick||now-observed.tick>200||!SameLocal(observed.target,selection.target))return false;
if((byte)Call(image+0xED6620,computer)!=0)
{lock(localLock)dockingZeroUpdate.command.expires=0;return false;}
var source=Pointer(computer+0x260);
if(!DockingMapperNodeMatches(node,receipt.binding,source))return false;
// The registry clears this frame's aggregate before evaluating the
// node. Its zero at entry does not describe the retained accumulator;
// let the original SetSpeedZero branch handle that state itself.
if(!ReadOriginalDockingThrottle(computer,out _))return false;
var active=Pointer(node+0x90);
if(active<0x10000||Read<byte>(active+0x24)==0)return false;
var dock=LocalDocking(frame);
if(dock==0||Read<uint>(dock+0x614)!=2||Read<uint>(dock+0x4C0)is not(3 or 4)||Read<byte>(dock+0x4E9)==255)return false;
var station=Pointer(dock+0x4E0);
if(station==0||Read<uint>(station+0xC0)!=8||Read<ulong>(station+0xB0)!=wanted.market||!LocalRecordMatches(Pointer(frame.fsd+0x338),selection.target))return false;
bool released;lock(leaseLock)released=lease.expires<=now&&fsdLease.expires<=now;
if(!released)return false;
dockingInputMapperMatched++;
if(!BeginDockingMapperPulse(node,receipt.binding,source,out pulse))return false;
pulse.entity=frame.entity;pulse.sequence=frame.sequence;return true;
}
[UnmanagedCallersOnly]
private static ulong DockingInputMapperHook(nuint node,float dt)
{
DockingMapperPulse pulse=default;
DockingFixturePulse fixture=default;
try{if(!PrepareDockingFixturePulse(node,dt,out fixture))PrepareDockingMapperPulse(node,dt,out pulse);}catch{try{FixtureExceptionEvidence();}catch{}}
try{return Call(originalDockingInputMapper,(ulong)node,F32(dt));}
catch{return 0;}
finally
{
try
{
if(pulse.applied&&DockingMapperNodeMatches(pulse.node,pulse.binding,pulse.source))
{
var accumulator=Read<float>(pulse.node+0xD0);var source=Read<float>(pulse.source+0x18);
var valid=float.IsFinite(accumulator)&&Math.Abs(accumulator)<=1.001f&&float.IsFinite(source)&&Math.Abs(source)<=1.001f;
lock(localLock)dockingInputMapperReceipt=new(){sequence=pulse.sequence,entity=pulse.entity,utc=UtcMilliseconds(),before=pulse.beforeAccumulator,
after=valid?accumulator:0,source=valid?source:0,verified=valid};
if(valid&&Math.Abs(accumulator)<=.001f&&Math.Abs(source)<=.001f)dockingInputMapperApplied++;
}
}
catch{}
finally
{
try{EndDockingMapperPulse(pulse);}catch{}
try{EndDockingFixturePulse(fixture);}catch{}
try{ObserveDockingFixtureMapper(node,fixture,pulse);}catch{}
}
}
}
}