using System.Runtime.InteropServices;
namespace XFEEDToolBox.Plugin;
internal static unsafe partial class Navigation
{
struct FsdPulse {public nuint field,life,fsd,inputRoot;public ulong entity,generation;public uint command;public bool applied;}
static bool BoundFsd(Snapshot frame,nuint fsd)
{
try{return frame.life!=0&&frame.fsd==fsd&&Read<uint>(frame.life+0xC0)==8&&Read<ulong>(frame.life+0xB0)==frame.entity&&Pointer(fsd+0x398)==frame.inputRoot+0x60&&Pointer(fsd+0x10)==Pointer(frame.physics+0x10)&&LocalShipLife(frame.inputRoot)==frame.life;}catch{return false;}
}
[UnmanagedCallersOnly]
static void ObserveHeatInput(nuint obj,float amount,nuint unused,float unused2)
{
try{
if(!Enabled()||!inGame||GetCurrentThreadId()!=owningThread)return;var frame=Current();if(!frame.valid||GetTickCount64()-frame.tick>300||obj!=frame.heatManager+0x60||!BoundFsd(frame,frame.fsd))return;
try{ObserveScoThermalInput(frame,obj,amount,unused);}catch{ /* Source diagnostics cannot change the original heat request. */ }
lock(heatLock){if(heatObservation.entity!=frame.entity||heatObservation.world!=frame.referenceWorld)heatObservation=new(){entity=frame.entity,world=frame.referenceWorld};
if(float.IsFinite(amount)&&amount>0&&amount<10000)heatObservation.pending+=amount;else if(!float.IsFinite(amount)||amount>=10000)heatObservation.valid=false;}
}catch{}
}
[UnmanagedCallersOnly]
static void ObserveHeatTick(nuint obj,float dt)
{
try{
if(!Enabled()||!inGame||GetCurrentThreadId()!=owningThread)return;var frame=Current();if(!frame.valid||GetTickCount64()-frame.tick>300||obj!=frame.heatManager||!BoundFsd(frame,frame.fsd))return;
try{ObserveScoThermalTick(frame,dt);}catch{ /* Shared read-only accounting never changes the game's heat tick. */ }
FsdLease desired;lock(leaseLock)desired=fsdLease;
lock(heatLock){ref var value=ref heatObservation;if(value.entity!=frame.entity||value.world!=frame.referenceWorld)value=new(){entity=frame.entity,world=frame.referenceWorld};
if(!float.IsFinite(dt)||dt<=0||dt>.25f){value.valid=false;value.buckets=0;value.pending=0;value.bucketTime=0;value.bucketEnergy=0;}
else{value.bucketEnergy+=value.pending;value.pending=0;value.bucketTime+=dt;value.seconds+=dt;value.tick=GetTickCount64();
if(value.bucketTime>=.1){value.rates[(int)(value.buckets%10)]=value.bucketEnergy/value.bucketTime;++value.buckets;value.bucketTime=0;value.bucketEnergy=0;
var count=Math.Min(value.buckets,10u);double maximum=double.MinValue;for(var i=0;i<count;i++)maximum=Math.Max(maximum,value.rates[i]);value.inputUpper=maximum*1.02+.1;
value.valid=value.buckets>=5&&double.IsFinite(value.inputUpper)&&value.inputUpper>=0&&value.inputUpper<10000;
if(frame.rawFsdState!=3&&!fsdOwned&&desired.expires<=GetTickCount64())value.idleUpper=value.inputUpper;}
}
}
}catch{}
}
static nuint SelectedTarget(nuint fsd)
{
try{var navigation=Pointer(fsd+0x338);if(navigation==0||Pointer(Pointer(navigation)+0xB8)!=image+0x2EC8D60)return 0;return (nuint)Call(image+0x2EC8D60,(ulong)navigation);}catch{return 0;}
}
static bool TargetKey(nuint navigation,ulong target,out int key)
{
key=0;var count=Read<ulong>(navigation+0x2C0);var capacity=Read<ulong>(navigation+0x2C8);var storage=Pointer(navigation+0x2D0);
if(target==0||count==0||count>1024||count>capacity||capacity>16384||capacity==0||(capacity&(capacity-1))!=0||storage==0)return false;
var entries=storage+(nuint)((((capacity+7)>>3)+7)&~7UL);uint matches=0,occupied=0;
for(ulong i=0;i<capacity;i++){if((Read<byte>(storage+(nuint)(i>>3))&(1u<<(int)(i&7)))==0)continue;occupied++;var entry=entries+(nuint)i*16;var record=Pointer(entry+8);if(record==0)return false;
if(Read<ulong>(record+0x20)==target&&Read<uint>(record+0x188)==2&&Read<uint>(record+0x184)==0){matches++;key=Read<int>(entry);}}
return occupied==count&&matches==1;
}
static nuint GameRoute(in Snapshot frame,out nuint pilotLife)
{
pilotLife=0;try{
if(LocalShipLife(frame.inputRoot)!=frame.life)return 0;var local=Pointer(frame.inputRoot+0x2E8);var reference=Leaf(local,image+0x978A90);if(reference==0)return 0;
var pilot=Pointer(reference);var component=pilot!=0?Pointer(pilot+0xC8):0;if(component==0)return 0;var route=RegisteredComponent(component-0x30,Read<uint>(image+0x5F05DD0));
if(route==0||Pointer(route)!=image+0x529E2F8||Pointer(Pointer(route)+0x30)!=image+0x2647900||Pointer(Pointer(route)+0x38)!=image+0x264BD30||Pointer(Pointer(route)+0xB0)!=image+0x2604DA0)return 0;pilotLife=pilot;return route;
}catch{return 0;}
}
static bool InvokeGameRoute(nuint route,ulong destination)
{
ulong body=0x200;long market=-1,settlement=-1;
Call(Pointer(Pointer(route)+0x30),(ulong)route,0,(ulong)&destination,(ulong)&body,(ulong)&market,(ulong)&settlement,0,0);
if(Read<ulong>(route+0x148)!=destination)return false;Call(Pointer(Pointer(route)+0x38),(ulong)route,0);return true;
}
static bool InvokeGameRouteCancellation(nuint route,ulong expectedDestination)
{
if(Read<ulong>(route+0x148)!=expectedDestination||Pointer(Pointer(route)+0xE0)!=image+0x25E9E30)return false;
Call(Pointer(Pointer(route)+0xE0),(ulong)route,0,0,1);return Read<ulong>(route+0x148)==0;
}
static void PlanGameRoute(Snapshot frame)
{
RouteRequest wanted;lock(routeLock)wanted=routeRequest;if(wanted.generation==0)return;var observedAt=GetTickCount64();
if(routeConsumed==wanted.generation){if(observedAt>routeReadUntil)return;RouteObservation previous;lock(routeLock)previous=routeObservation;if(previous.generation==wanted.generation&&observedAt-previous.tick<80)return;}
try{
var route=GameRoute(frame,out var pilot);var now=GetTickCount64();var context=Enabled()&&inGame&&frame.valid&&now-frame.tick<=200&&wanted.entity==frame.entity&&wanted.life==frame.life&&wanted.fsd==frame.fsd&&wanted.inputRoot==frame.inputRoot&&route!=0&&route==wanted.route&&pilot==wanted.pilotLife&&BoundFsd(frame,frame.fsd);
if(routeConsumed!=wanted.generation){ulong flightExpiry;lock(leaseLock)flightExpiry=Math.Max(lease.expires,Math.Max(fsdLease.expires,targetLease.expires));
if(!context||wanted.expires<=now||fsdOwned||Read<byte>(frame.fsd+0x758)!=2||flightExpiry>now){routeFailed=wanted.generation;routeConsumed=wanted.generation;return;}
routeConsumed=wanted.generation;
if(!wanted.clear&&wanted.options is{} options&&!ApplyGameRouteOptions(frame,options)){routeFailed=wanted.generation;return;}
if(wanted.clear?InvokeGameRouteCancellation(route,wanted.destination):InvokeGameRoute(route,wanted.destination))routeApplied=wanted.generation;else routeFailed=wanted.generation;
}
var observed=new RouteObservation{tick=now,generation=wanted.generation,destination=context?Read<ulong>(route+0x148):0,route=context?route:0};
if(context){var sentinel=route+0x110;var node=Pointer(sentinel);var previous=sentinel;ulong hash=14695981039346656037;
while(node!=0&&node!=sentinel&&observed.count<25000){if(Pointer(node+8)!=previous)break;var system=Read<ulong>(node+0x10);if(system==0||system>=0x80000000000000)break;hash=unchecked((hash^system)*1099511628211);++observed.count;previous=node;node=Pointer(node);}
observed.topologyValid=node==sentinel&&Pointer(sentinel+8)==previous&&(wanted.clear?observed.count==0:observed.count>=2);if(observed.topologyValid)observed.hash=hash;}
lock(routeLock)routeObservation=observed;
}catch{routeFailed=wanted.generation;routeConsumed=wanted.generation;}
}
static void SelectTarget(Snapshot frame)
{
try{
TargetLease wanted;lock(leaseLock)wanted=targetLease;var now=GetTickCount64();if(wanted.generation==0||targetConsumed==wanted.generation)return;
if(!Enabled()||!inGame||!frame.valid||now-frame.tick>200||wanted.expires<=now||overridden||wanted.entity!=frame.entity||wanted.life!=frame.life||wanted.fsd!=frame.fsd||wanted.inputRoot!=frame.inputRoot||!BoundFsd(frame,frame.fsd)||Read<byte>(frame.fsd+0x758)==3||Read<byte>(frame.fsd+0x758)==4||fsdOwned){targetFailed=wanted.generation;targetConsumed=wanted.generation;return;}
var navigation=Pointer(frame.fsd+0x338);
if(Pointer(Pointer(navigation)+0x20)!=image+0x2EC8100||Pointer(Pointer(navigation)+0x90)!=image+0x2ED6840||Pointer(Pointer(navigation)+0xB8)!=image+0x2EC8D60){targetFailed=wanted.generation;targetConsumed=wanted.generation;return;}
var already=SelectedTarget(frame.fsd);if(already!=0&&Read<ulong>(already+0x20)==wanted.target&&Read<uint>(already+0x188)==2&&Read<uint>(already+0x184)==0){targetConfirmed=wanted.generation;targetConsumed=wanted.generation;return;}
if(Read<byte>(frame.fsd+0x758)!=2||!TargetKey(navigation,wanted.target,out var key)){targetFailed=wanted.generation;targetConsumed=wanted.generation;return;}
var record=(nuint)Call(image+0x2EC8100,(ulong)navigation,unchecked((ulong)key));
if(record==0||Read<ulong>(record+0x20)!=wanted.target||Read<uint>(record+0x188)!=2||Read<uint>(record+0x184)!=0){targetFailed=wanted.generation;targetConsumed=wanted.generation;return;}
lock(leaseLock){if(targetLease.generation!=wanted.generation||targetLease.expires<=GetTickCount64()){targetFailed=wanted.generation;targetConsumed=wanted.generation;return;}
var selected=SelectedTarget(frame.fsd);if(selected==0||Read<ulong>(selected+0x20)!=wanted.target)Call(image+0x2ED6840,(ulong)navigation,unchecked((ulong)key),1,0);
var confirmed=SelectedTarget(frame.fsd);if(confirmed!=0&&Read<ulong>(confirmed+0x20)==wanted.target)targetConfirmed=wanted.generation;else targetFailed=wanted.generation;targetConsumed=wanted.generation;}
}catch{targetFailed=targetGeneration;targetConsumed=targetGeneration;Expire();}
}
static FsdPulse PrepareFsdPulse(Snapshot frame,nuint fsd)
{
FsdPulse result=default;try{
var now=GetTickCount64();var state=Read<byte>(fsd+0x758);FsdLease desired;lock(leaseLock)desired=fsdLease;
var heat=HeatHudPercent(Read<float>(frame.heat+0x188)*100.0,frame.heatDisplayScale);var eligible=Enabled()&&inGame&&frame.valid&&frame.healthValid&&frame.hullFraction>0&&!frame.healthEmergency&&now-frame.tick<=300&&double.IsFinite(heat)&&heat<Math.Min(97f,desired.heatCeiling)&&!overridden;
var target=desired.target!=0?SelectedTarget(fsd):0;var locked=target!=0?Read<ulong>(target+0x20):0;Lease axes;lock(leaseLock)axes=lease;
var fresh=eligible&&desired.expires>now&&desired.fsd==fsd&&desired.life==frame.life&&desired.entity==frame.entity&&desired.inputRoot==frame.inputRoot&&(desired.target==0||desired.target==locked)&&(!desired.local||axes.expires>now&&LocalControlMatches(axes,frame));uint command=0;
var input=Pointer(fsd+0x398);
// Preserve the original button for this frame. Do not consume a
// pending automation generation until its pulse can actually run.
if(Read<byte>(input+0xBD)!=0||Read<byte>(input+0xBE)!=0||Read<byte>(input+0xBF)!=0)return result;
if(fsdOwned&&(!fresh||desired.command==2)){if(state==3&&now-fsdLastPulseAt>=250)command=2;}
else if(fresh&&desired.generation!=fsdConsumedGeneration){fsdConsumedGeneration=desired.generation;
if(!fsdOwned&&state==2&&((desired.command==1&&(frame.speed<1||desired.supercruiseEntry))||(desired.command==3&&desired.target!=0)))command=desired.command;
if(!fsdOwned&&state==2&&desired.command==4&&desired.local&&frame.speed<=100000&&axes.throttle==0&&LocalCruiseArrival(frame)!=0)command=4;}
if(command==0)return result;
if(command==1||command==3)for(var i=0;i<6;i++)if(!float.IsFinite(Read<float>(frame.inputRoot+0x70+(nuint)i*16))){Expire();return result;}
var pulseMode=command==2?fsdOwnedMode:command==4?1:command;result=new(){field=input+(nuint)(pulseMode==3?0xBF:0xBE),life=frame.life,fsd=fsd,inputRoot=frame.inputRoot,entity=frame.entity,generation=desired.generation,command=command,applied=true};
if(command!=2)fsdOwnedMode=command;Write(result.field,(byte)1);fsdLastPulseAt=now;fsdPulseCount++;return result;
}catch{Expire();return result;}
}
static void FinishFsdPulse(ref FsdPulse pulse)
{
if(!pulse.applied)return;try{if(Read<uint>(pulse.life+0xC0)!=8||Read<ulong>(pulse.life+0xB0)!=pulse.entity||Pointer(pulse.fsd+0x398)!=pulse.inputRoot+0x60)return;
if(Read<byte>(pulse.field)==1)Write(pulse.field,(byte)0);fsdOwned=Read<byte>(pulse.fsd+0x758)==3;}catch{fsdOwned=false;Expire();}
}
static void ObserveFsd(Snapshot frame,nuint fsd,FsdPulse pulse)
{
try{
var observed=CurrentFsd();var now=GetTickCount64();observed.driveParametersValid=false;observed.tick=now;observed.entity=frame.entity;observed.life=frame.life;observed.fsd=fsd;
observed.state=Read<byte>(fsd+0x758);observed.mode=Read<byte>(fsd+0x759);observed.stage=Read<byte>(fsd+0x794);observed.clock=Read<float>(fsd+0x7A0);observed.rate=Read<float>(fsd+0x7A4);observed.elapsed=Read<float>(fsd+0x7A8);observed.duration=Read<float>(fsd+0x7B0);
if(fsdOwned&&observed.state!=3)fsdOwned=false;observed.owned=fsdOwned;observed.generation=fsdGeneration;observed.pulses=fsdPulseCount;
if(pulse.applied){observed.appliedGeneration=pulse.generation;observed.beginRejected=(pulse.command==1||pulse.command==3)&&observed.state!=3;}
observed.targetValid=false;observed.targetAddress=0;observed.speedFraction=Read<float>(frame.physics+0x6B0);observed.throttle=Read<float>(frame.inputRoot+0x70);observed.throttleOverride=Read<float>(Pointer(frame.inputRoot+0x2F8)+0x680);
var target=SelectedTarget(fsd);var navigation=Pointer(fsd+0x338);
if(target!=0&&Read<uint>(target+0x188)==2&&Pointer(Pointer(navigation)+0x60)==image+0x2EC89E0){
byte* fallback=stackalloc byte[160];float* direction=stackalloc float[4];float* distance=stackalloc float[4];new Span<byte>(fallback,160).Clear();new Span<float>(direction,4).Clear();new Span<float>(distance,4).Clear();
Call(image+0x2EC89E0,(ulong)navigation,(ulong)distance,(ulong)(target+0xC0),(ulong)(target+0x20),(ulong)direction,(ulong)fallback);
observed.targetDirection=new(direction[0],direction[1],direction[2]);observed.targetDistance=distance[0];observed.targetAddress=Read<ulong>(target+0x20);
observed.targetValid=Finite(observed.targetDirection)&&Math.Abs(Dot(observed.targetDirection,observed.targetDirection)-1)<.005&&float.IsFinite(distance[0])&&distance[0]>0;
}
var driveGetter=Pointer(Pointer(fsd+0x58)+0x130);var heatGetter=Pointer(Pointer(frame.heatManager+0x58)+0x28);
if(driveGetter==image+0x2C9A6E0&&heatGetter==image+0x38139A0){var drive=(nuint)Call(driveGetter,(ulong)(fsd+0x58));if(drive!=0){
observed.driveChargeSeconds=Read<float>(drive+4);observed.driveHeatBase=Read<float>(drive+0xC);observed.driveHeatRamp=Read<float>(drive+0x10);observed.heatCapacity=CallFloat(heatGetter,(ulong)(frame.heatManager+0x58));
observed.driveParametersValid=float.IsFinite(observed.driveChargeSeconds)&&observed.driveChargeSeconds>0&&observed.driveChargeSeconds<120&&float.IsFinite(observed.driveHeatBase)&&observed.driveHeatBase>=0&&float.IsFinite(observed.driveHeatRamp)&&observed.driveHeatRamp>=0&&float.IsFinite(observed.heatCapacity)&&observed.heatCapacity>0;}}
observed.coolingConfigurationValid=false;observed.coolingActive=false;var count=Read<ulong>(frame.heatManager+0x160);var modifiers=Pointer(frame.heatManager+0x168);
if(count<=16&&modifiers!=0)for(nuint i=0;i<(nuint)count;i++){var modifier=Pointer(modifiers+i*8);if(modifier==0||Pointer(Pointer(modifier)+0x28)!=image+0x4398F40)continue;
var config=Pointer(modifier-0x58+0x1B0);observed.coolingPassive=Read<float>(config+4);observed.coolingMaximum=Read<float>(config+8);observed.coolingHeatLower=Read<float>(config+0x18);observed.coolingHeatUpper=Read<float>(config+0x1C);
observed.coolingConfigurationValid=float.IsFinite(observed.coolingPassive)&&observed.coolingPassive>=0&&float.IsFinite(observed.coolingMaximum)&&observed.coolingMaximum>=observed.coolingPassive&&float.IsFinite(observed.coolingHeatLower)&&observed.coolingHeatLower>=0&&float.IsFinite(observed.coolingHeatUpper)&&observed.coolingHeatUpper>observed.coolingHeatLower;
var encoded=Read<ulong>(frame.heatManager+0x210);observed.coolingScale=BitConverter.UInt32BitsToSingle((uint)encoded^(uint)(encoded>>32));
if(observed.coolingConfigurationValid&&float.IsFinite(observed.coolingScale)&&observed.coolingScale>0&&observed.coolingScale<=10){
observed.coolingActual=CallFloat(image+0x4399D90,(ulong)(modifier-0x58),F32(observed.coolingScale),0,0,0,0);var ratio=Math.Clamp(((float)(frame.heatPercent*.01)-observed.coolingHeatLower)/(observed.coolingHeatUpper-observed.coolingHeatLower),0f,1f);
var expected=observed.coolingScale*(observed.coolingPassive+(observed.coolingMaximum-observed.coolingPassive)*ratio*ratio);observed.coolingActive=float.IsFinite(observed.coolingActual)&&observed.coolingActual>=expected*.97f&&expected>.01f;
}break;
}
lock(fsdObservationLock)fsdObservation=observed;
}catch{Expire();}
}
[UnmanagedCallersOnly]
static ulong FsdHook(nuint obj,float dt)
{
Interlocked.Increment(ref fsdEntryCallsValue);fsdEntryAt=GetTickCount64();fsdEntryThread=GetCurrentThreadId();fsdEntryDt=dt;var fsd=obj;var frame=Current();var selected=inGame&&GetCurrentThreadId()==owningThread&&BoundFsd(frame,fsd);
if(GetCurrentThreadId()==owningThread)try{PollNormalBoostPulse(frame);}catch{try{Expire();}catch{}}
InputOverlay overlay=default;FsdPulse pulse=default;bool applied=false;
try{if(selected)Interlocked.Increment(ref fsdSelectedCallsValue);if(selected)SelectTarget(frame);if(selected)SelectLocalTarget(frame);applied=selected&&ApplyInput(frame.inputRoot,ref overlay);if(selected)pulse=PrepareFsdPulse(frame,fsd);}catch{}
try{return Call(originalFsd,(ulong)obj,F32(dt));}catch{return 0;}
finally{
try{if(pulse.applied)FinishFsdPulse(ref pulse);if(selected&&BoundFsd(frame,fsd))ObserveFsd(frame,fsd,pulse);if(selected&&BoundFsd(frame,fsd))PollReferenceGeometry(frame);
if(selected&&BoundFsd(frame,fsd))PollLocalFlight(frame);if(selected&&BoundFsd(frame,fsd))PollCommerceRead(frame);if(selected&&BoundFsd(frame,fsd))PollCommunityGoals(frame);
}catch{try{Expire();}catch{}}
finally{try{if(applied){Restore(ref overlay);inputAppliedAt=GetTickCount64();}}catch{}}
// A persistent original action must not be undone by restoring the
// temporary axes of the FSD invocation that carried it.
try{if(selected&&BoundFsd(frame,fsd))PollLocalAction(frame);}catch{try{Expire();}catch{}}
try{if(selected)PlanGameRoute(frame);if(selected)PollGalaxyCatalogue(frame);}catch{try{Expire();}catch{}}
try{if(selected)PollGalaxyAccount(frame);}catch{}
try{if(selected)PollGalaxyFilterFacts(frame);}catch{}
try{RetireGalaxyColonisationLoads(frame);}catch{}
}
}
[UnmanagedCallersOnly]
static nuint BodyLookupHook(nuint source,nuint output,nuint addressValue)
{
nuint result;try{result=(nuint)Call(originalBodyLookup,(ulong)source,(ulong)output,(ulong)addressValue);}catch{return 0;}
if(!Enabled()||!inGame||GetCurrentThreadId()!=owningThread)return result;
try{
var frame=Current();if(!frame.valid||GetTickCount64()-frame.tick>300||!BoundFsd(frame,frame.fsd)||Pointer(frame.fsd+0x6A0)!=source||Pointer(Pointer(frame.transform)+0x60)!=image+0x2D9C200)return result;
var world=Read<ulong>(frame.transform+0xE8);bool unified;lock(geometryLock)unified=geometry.unifiedReference;if(unified||world==0||Read<ulong>(addressValue)!=world)return result;
var body=Pointer(output);if(body==0||Read<ulong>(body+0x18)!=world)return result;var kind=Read<uint>(body+0x20);if(kind!=1&&kind!=2&&kind!=7)return result;var radiusGetter=Pointer(Pointer(source)+0xD0);if(radiusGetter!=image+0x3CF0410)return result;
double radius=0;var returned=(nuint)Call(radiusGetter,(ulong)source,(ulong)&radius,(ulong)body);var multiplier=Read<float>(Pointer(frame.fsd+0x50)+0x44);
if(returned!=(nuint)(&radius)||!double.IsFinite(radius)||radius<=0||radius>1e14||!float.IsFinite(multiplier)||multiplier<1||multiplier>10)return result;
var next=new GeometryObservation{tick=GetTickCount64(),entity=frame.entity,worldAddress=world,bodyAddress=Read<ulong>(body+0x18),bodyKind=kind,starType=kind==1?Read<uint>(body+0x448):0,exclusionRadius=radius,escapeRadius=radius*multiplier,position=frame.position};lock(geometryLock)geometry=next;
}catch{}return result;
}
}
using System.Runtime.InteropServices;
namespace XFEEDToolBox.Plugin;
internal static unsafe partial class Navigation
{
struct FsdPulse {public nuint field,life,fsd,inputRoot;public ulong entity,generation;public uint command;public bool applied;}
static bool BoundFsd(Snapshot frame,nuint fsd)
{
try{return frame.life!=0&&frame.fsd==fsd&&Read<uint>(frame.life+0xC0)==8&&Read<ulong>(frame.life+0xB0)==frame.entity&&Pointer(fsd+0x398)==frame.inputRoot+0x60&&Pointer(fsd+0x10)==Pointer(frame.physics+0x10)&&LocalShipLife(frame.inputRoot)==frame.life;}catch{return false;}
}
[UnmanagedCallersOnly]
static void ObserveHeatInput(nuint obj,float amount,nuint unused,float unused2)
{
try{
if(!Enabled()||!inGame||GetCurrentThreadId()!=owningThread)return;var frame=Current();if(!frame.valid||GetTickCount64()-frame.tick>300||obj!=frame.heatManager+0x60||!BoundFsd(frame,frame.fsd))return;
try{ObserveScoThermalInput(frame,obj,amount,unused);}catch{ /* Source diagnostics cannot change the original heat request. */ }
lock(heatLock){if(heatObservation.entity!=frame.entity||heatObservation.world!=frame.referenceWorld)heatObservation=new(){entity=frame.entity,world=frame.referenceWorld};
if(float.IsFinite(amount)&&amount>0&&amount<10000)heatObservation.pending+=amount;else if(!float.IsFinite(amount)||amount>=10000)heatObservation.valid=false;}
}catch{}
}
[UnmanagedCallersOnly]
static void ObserveHeatTick(nuint obj,float dt)
{
try{
if(!Enabled()||!inGame||GetCurrentThreadId()!=owningThread)return;var frame=Current();if(!frame.valid||GetTickCount64()-frame.tick>300||obj!=frame.heatManager||!BoundFsd(frame,frame.fsd))return;
try{ObserveScoThermalTick(frame,dt);}catch{ /* Shared read-only accounting never changes the game's heat tick. */ }
FsdLease desired;lock(leaseLock)desired=fsdLease;
lock(heatLock){ref var value=ref heatObservation;if(value.entity!=frame.entity||value.world!=frame.referenceWorld)value=new(){entity=frame.entity,world=frame.referenceWorld};
if(!float.IsFinite(dt)||dt<=0||dt>.25f){value.valid=false;value.buckets=0;value.pending=0;value.bucketTime=0;value.bucketEnergy=0;}
else{value.bucketEnergy+=value.pending;value.pending=0;value.bucketTime+=dt;value.seconds+=dt;value.tick=GetTickCount64();
if(value.bucketTime>=.1){value.rates[(int)(value.buckets%10)]=value.bucketEnergy/value.bucketTime;++value.buckets;value.bucketTime=0;value.bucketEnergy=0;
var count=Math.Min(value.buckets,10u);double maximum=double.MinValue;for(var i=0;i<count;i++)maximum=Math.Max(maximum,value.rates[i]);value.inputUpper=maximum*1.02+.1;
value.valid=value.buckets>=5&&double.IsFinite(value.inputUpper)&&value.inputUpper>=0&&value.inputUpper<10000;
if(frame.rawFsdState!=3&&!fsdOwned&&desired.expires<=GetTickCount64())value.idleUpper=value.inputUpper;}
}
}
}catch{}
}
static nuint SelectedTarget(nuint fsd)
{
try{var navigation=Pointer(fsd+0x338);if(navigation==0||Pointer(Pointer(navigation)+0xB8)!=image+0x2EC8D60)return 0;return (nuint)Call(image+0x2EC8D60,(ulong)navigation);}catch{return 0;}
}
static bool TargetKey(nuint navigation,ulong target,out int key)
{
key=0;var count=Read<ulong>(navigation+0x2C0);var capacity=Read<ulong>(navigation+0x2C8);var storage=Pointer(navigation+0x2D0);
if(target==0||count==0||count>1024||count>capacity||capacity>16384||capacity==0||(capacity&(capacity-1))!=0||storage==0)return false;
var entries=storage+(nuint)((((capacity+7)>>3)+7)&~7UL);uint matches=0,occupied=0;
for(ulong i=0;i<capacity;i++){if((Read<byte>(storage+(nuint)(i>>3))&(1u<<(int)(i&7)))==0)continue;occupied++;var entry=entries+(nuint)i*16;var record=Pointer(entry+8);if(record==0)return false;
if(Read<ulong>(record+0x20)==target&&Read<uint>(record+0x188)==2&&Read<uint>(record+0x184)==0){matches++;key=Read<int>(entry);}}
return occupied==count&&matches==1;
}
static nuint GameRoute(in Snapshot frame,out nuint pilotLife)
{
pilotLife=0;try{
if(LocalShipLife(frame.inputRoot)!=frame.life)return 0;var local=Pointer(frame.inputRoot+0x2E8);var reference=Leaf(local,image+0x978A90);if(reference==0)return 0;
var pilot=Pointer(reference);var component=pilot!=0?Pointer(pilot+0xC8):0;if(component==0)return 0;var route=RegisteredComponent(component-0x30,Read<uint>(image+0x5F05DD0));
if(route==0||Pointer(route)!=image+0x529E2F8||Pointer(Pointer(route)+0x30)!=image+0x2647900||Pointer(Pointer(route)+0x38)!=image+0x264BD30||Pointer(Pointer(route)+0xB0)!=image+0x2604DA0)return 0;pilotLife=pilot;return route;
}catch{return 0;}
}
static bool InvokeGameRoute(nuint route,ulong destination)
{
ulong body=0x200;long market=-1,settlement=-1;
Call(Pointer(Pointer(route)+0x30),(ulong)route,0,(ulong)&destination,(ulong)&body,(ulong)&market,(ulong)&settlement,0,0);
if(Read<ulong>(route+0x148)!=destination)return false;Call(Pointer(Pointer(route)+0x38),(ulong)route,0);return true;
}
static bool InvokeGameRouteCancellation(nuint route,ulong expectedDestination)
{
if(Read<ulong>(route+0x148)!=expectedDestination||Pointer(Pointer(route)+0xE0)!=image+0x25E9E30)return false;
Call(Pointer(Pointer(route)+0xE0),(ulong)route,0,0,1);return Read<ulong>(route+0x148)==0;
}
static void PlanGameRoute(Snapshot frame)
{
RouteRequest wanted;lock(routeLock)wanted=routeRequest;if(wanted.generation==0)return;var observedAt=GetTickCount64();
if(routeConsumed==wanted.generation){if(observedAt>routeReadUntil)return;RouteObservation previous;lock(routeLock)previous=routeObservation;if(previous.generation==wanted.generation&&observedAt-previous.tick<80)return;}
try{
var route=GameRoute(frame,out var pilot);var now=GetTickCount64();var context=Enabled()&&inGame&&frame.valid&&now-frame.tick<=200&&wanted.entity==frame.entity&&wanted.life==frame.life&&wanted.fsd==frame.fsd&&wanted.inputRoot==frame.inputRoot&&route!=0&&route==wanted.route&&pilot==wanted.pilotLife&&BoundFsd(frame,frame.fsd);
if(routeConsumed!=wanted.generation){ulong flightExpiry;lock(leaseLock)flightExpiry=Math.Max(lease.expires,Math.Max(fsdLease.expires,targetLease.expires));
if(!context||wanted.expires<=now||fsdOwned||Read<byte>(frame.fsd+0x758)!=2||flightExpiry>now){routeFailed=wanted.generation;routeConsumed=wanted.generation;return;}
routeConsumed=wanted.generation;
if(!wanted.clear&&wanted.options is{} options&&!ApplyGameRouteOptions(frame,options)){routeFailed=wanted.generation;return;}
if(wanted.clear?InvokeGameRouteCancellation(route,wanted.destination):InvokeGameRoute(route,wanted.destination))routeApplied=wanted.generation;else routeFailed=wanted.generation;
}
var observed=new RouteObservation{tick=now,generation=wanted.generation,destination=context?Read<ulong>(route+0x148):0,route=context?route:0};
if(context){var sentinel=route+0x110;var node=Pointer(sentinel);var previous=sentinel;ulong hash=14695981039346656037;
while(node!=0&&node!=sentinel&&observed.count<25000){if(Pointer(node+8)!=previous)break;var system=Read<ulong>(node+0x10);if(system==0||system>=0x80000000000000)break;hash=unchecked((hash^system)*1099511628211);++observed.count;previous=node;node=Pointer(node);}
observed.topologyValid=node==sentinel&&Pointer(sentinel+8)==previous&&(wanted.clear?observed.count==0:observed.count>=2);if(observed.topologyValid)observed.hash=hash;}
lock(routeLock)routeObservation=observed;
}catch{routeFailed=wanted.generation;routeConsumed=wanted.generation;}
}
static void SelectTarget(Snapshot frame)
{
try{
TargetLease wanted;lock(leaseLock)wanted=targetLease;var now=GetTickCount64();if(wanted.generation==0||targetConsumed==wanted.generation)return;
if(!Enabled()||!inGame||!frame.valid||now-frame.tick>200||wanted.expires<=now||overridden||wanted.entity!=frame.entity||wanted.life!=frame.life||wanted.fsd!=frame.fsd||wanted.inputRoot!=frame.inputRoot||!BoundFsd(frame,frame.fsd)||Read<byte>(frame.fsd+0x758)==3||Read<byte>(frame.fsd+0x758)==4||fsdOwned){targetFailed=wanted.generation;targetConsumed=wanted.generation;return;}
var navigation=Pointer(frame.fsd+0x338);
if(Pointer(Pointer(navigation)+0x20)!=image+0x2EC8100||Pointer(Pointer(navigation)+0x90)!=image+0x2ED6840||Pointer(Pointer(navigation)+0xB8)!=image+0x2EC8D60){targetFailed=wanted.generation;targetConsumed=wanted.generation;return;}
var already=SelectedTarget(frame.fsd);if(already!=0&&Read<ulong>(already+0x20)==wanted.target&&Read<uint>(already+0x188)==2&&Read<uint>(already+0x184)==0){targetConfirmed=wanted.generation;targetConsumed=wanted.generation;return;}
if(Read<byte>(frame.fsd+0x758)!=2||!TargetKey(navigation,wanted.target,out var key)){targetFailed=wanted.generation;targetConsumed=wanted.generation;return;}
var record=(nuint)Call(image+0x2EC8100,(ulong)navigation,unchecked((ulong)key));
if(record==0||Read<ulong>(record+0x20)!=wanted.target||Read<uint>(record+0x188)!=2||Read<uint>(record+0x184)!=0){targetFailed=wanted.generation;targetConsumed=wanted.generation;return;}
lock(leaseLock){if(targetLease.generation!=wanted.generation||targetLease.expires<=GetTickCount64()){targetFailed=wanted.generation;targetConsumed=wanted.generation;return;}
var selected=SelectedTarget(frame.fsd);if(selected==0||Read<ulong>(selected+0x20)!=wanted.target)Call(image+0x2ED6840,(ulong)navigation,unchecked((ulong)key),1,0);
var confirmed=SelectedTarget(frame.fsd);if(confirmed!=0&&Read<ulong>(confirmed+0x20)==wanted.target)targetConfirmed=wanted.generation;else targetFailed=wanted.generation;targetConsumed=wanted.generation;}
}catch{targetFailed=targetGeneration;targetConsumed=targetGeneration;Expire();}
}
static FsdPulse PrepareFsdPulse(Snapshot frame,nuint fsd)
{
FsdPulse result=default;try{
var now=GetTickCount64();var state=Read<byte>(fsd+0x758);FsdLease desired;lock(leaseLock)desired=fsdLease;
var heat=HeatHudPercent(Read<float>(frame.heat+0x188)*100.0,frame.heatDisplayScale);var eligible=Enabled()&&inGame&&frame.valid&&frame.healthValid&&frame.hullFraction>0&&!frame.healthEmergency&&now-frame.tick<=300&&double.IsFinite(heat)&&heat<Math.Min(97f,desired.heatCeiling)&&!overridden;
var target=desired.target!=0?SelectedTarget(fsd):0;var locked=target!=0?Read<ulong>(target+0x20):0;Lease axes;lock(leaseLock)axes=lease;
var fresh=eligible&&desired.expires>now&&desired.fsd==fsd&&desired.life==frame.life&&desired.entity==frame.entity&&desired.inputRoot==frame.inputRoot&&(desired.target==0||desired.target==locked)&&(!desired.local||axes.expires>now&&LocalControlMatches(axes,frame));uint command=0;
var input=Pointer(fsd+0x398);
// Preserve the original button for this frame. Do not consume a
// pending automation generation until its pulse can actually run.
if(Read<byte>(input+0xBD)!=0||Read<byte>(input+0xBE)!=0||Read<byte>(input+0xBF)!=0)return result;
if(fsdOwned&&(!fresh||desired.command==2)){if(state==3&&now-fsdLastPulseAt>=250)command=2;}
else if(fresh&&desired.generation!=fsdConsumedGeneration){fsdConsumedGeneration=desired.generation;
if(!fsdOwned&&state==2&&((desired.command==1&&(frame.speed<1||desired.supercruiseEntry))||(desired.command==3&&desired.target!=0)))command=desired.command;
if(!fsdOwned&&state==2&&desired.command==4&&desired.local&&frame.speed<=100000&&axes.throttle==0&&LocalCruiseArrival(frame)!=0)command=4;}
if(command==0)return result;
if(command==1||command==3)for(var i=0;i<6;i++)if(!float.IsFinite(Read<float>(frame.inputRoot+0x70+(nuint)i*16))){Expire();return result;}
var pulseMode=command==2?fsdOwnedMode:command==4?1:command;result=new(){field=input+(nuint)(pulseMode==3?0xBF:0xBE),life=frame.life,fsd=fsd,inputRoot=frame.inputRoot,entity=frame.entity,generation=desired.generation,command=command,applied=true};
if(command!=2)fsdOwnedMode=command;Write(result.field,(byte)1);fsdLastPulseAt=now;fsdPulseCount++;return result;
}catch{Expire();return result;}
}
static void FinishFsdPulse(ref FsdPulse pulse)
{
if(!pulse.applied)return;try{if(Read<uint>(pulse.life+0xC0)!=8||Read<ulong>(pulse.life+0xB0)!=pulse.entity||Pointer(pulse.fsd+0x398)!=pulse.inputRoot+0x60)return;
if(Read<byte>(pulse.field)==1)Write(pulse.field,(byte)0);fsdOwned=Read<byte>(pulse.fsd+0x758)==3;}catch{fsdOwned=false;Expire();}
}
static void ObserveFsd(Snapshot frame,nuint fsd,FsdPulse pulse)
{
try{
var observed=CurrentFsd();var now=GetTickCount64();observed.driveParametersValid=false;observed.tick=now;observed.entity=frame.entity;observed.life=frame.life;observed.fsd=fsd;
observed.state=Read<byte>(fsd+0x758);observed.mode=Read<byte>(fsd+0x759);observed.stage=Read<byte>(fsd+0x794);observed.clock=Read<float>(fsd+0x7A0);observed.rate=Read<float>(fsd+0x7A4);observed.elapsed=Read<float>(fsd+0x7A8);observed.duration=Read<float>(fsd+0x7B0);
if(fsdOwned&&observed.state!=3)fsdOwned=false;observed.owned=fsdOwned;observed.generation=fsdGeneration;observed.pulses=fsdPulseCount;
if(pulse.applied){observed.appliedGeneration=pulse.generation;observed.beginRejected=(pulse.command==1||pulse.command==3)&&observed.state!=3;}
observed.targetValid=false;observed.targetAddress=0;observed.speedFraction=Read<float>(frame.physics+0x6B0);observed.throttle=Read<float>(frame.inputRoot+0x70);observed.throttleOverride=Read<float>(Pointer(frame.inputRoot+0x2F8)+0x680);
var target=SelectedTarget(fsd);var navigation=Pointer(fsd+0x338);
if(target!=0&&Read<uint>(target+0x188)==2&&Pointer(Pointer(navigation)+0x60)==image+0x2EC89E0){
byte* fallback=stackalloc byte[160];float* direction=stackalloc float[4];float* distance=stackalloc float[4];new Span<byte>(fallback,160).Clear();new Span<float>(direction,4).Clear();new Span<float>(distance,4).Clear();
Call(image+0x2EC89E0,(ulong)navigation,(ulong)distance,(ulong)(target+0xC0),(ulong)(target+0x20),(ulong)direction,(ulong)fallback);
observed.targetDirection=new(direction[0],direction[1],direction[2]);observed.targetDistance=distance[0];observed.targetAddress=Read<ulong>(target+0x20);
observed.targetValid=Finite(observed.targetDirection)&&Math.Abs(Dot(observed.targetDirection,observed.targetDirection)-1)<.005&&float.IsFinite(distance[0])&&distance[0]>0;
}
var driveGetter=Pointer(Pointer(fsd+0x58)+0x130);var heatGetter=Pointer(Pointer(frame.heatManager+0x58)+0x28);
if(driveGetter==image+0x2C9A6E0&&heatGetter==image+0x38139A0){var drive=(nuint)Call(driveGetter,(ulong)(fsd+0x58));if(drive!=0){
observed.driveChargeSeconds=Read<float>(drive+4);observed.driveHeatBase=Read<float>(drive+0xC);observed.driveHeatRamp=Read<float>(drive+0x10);observed.heatCapacity=CallFloat(heatGetter,(ulong)(frame.heatManager+0x58));
observed.driveParametersValid=float.IsFinite(observed.driveChargeSeconds)&&observed.driveChargeSeconds>0&&observed.driveChargeSeconds<120&&float.IsFinite(observed.driveHeatBase)&&observed.driveHeatBase>=0&&float.IsFinite(observed.driveHeatRamp)&&observed.driveHeatRamp>=0&&float.IsFinite(observed.heatCapacity)&&observed.heatCapacity>0;}}
observed.coolingConfigurationValid=false;observed.coolingActive=false;var count=Read<ulong>(frame.heatManager+0x160);var modifiers=Pointer(frame.heatManager+0x168);
if(count<=16&&modifiers!=0)for(nuint i=0;i<(nuint)count;i++){var modifier=Pointer(modifiers+i*8);if(modifier==0||Pointer(Pointer(modifier)+0x28)!=image+0x4398F40)continue;
var config=Pointer(modifier-0x58+0x1B0);observed.coolingPassive=Read<float>(config+4);observed.coolingMaximum=Read<float>(config+8);observed.coolingHeatLower=Read<float>(config+0x18);observed.coolingHeatUpper=Read<float>(config+0x1C);
observed.coolingConfigurationValid=float.IsFinite(observed.coolingPassive)&&observed.coolingPassive>=0&&float.IsFinite(observed.coolingMaximum)&&observed.coolingMaximum>=observed.coolingPassive&&float.IsFinite(observed.coolingHeatLower)&&observed.coolingHeatLower>=0&&float.IsFinite(observed.coolingHeatUpper)&&observed.coolingHeatUpper>observed.coolingHeatLower;
var encoded=Read<ulong>(frame.heatManager+0x210);observed.coolingScale=BitConverter.UInt32BitsToSingle((uint)encoded^(uint)(encoded>>32));
if(observed.coolingConfigurationValid&&float.IsFinite(observed.coolingScale)&&observed.coolingScale>0&&observed.coolingScale<=10){
observed.coolingActual=CallFloat(image+0x4399D90,(ulong)(modifier-0x58),F32(observed.coolingScale),0,0,0,0);var ratio=Math.Clamp(((float)(frame.heatPercent*.01)-observed.coolingHeatLower)/(observed.coolingHeatUpper-observed.coolingHeatLower),0f,1f);
var expected=observed.coolingScale*(observed.coolingPassive+(observed.coolingMaximum-observed.coolingPassive)*ratio*ratio);observed.coolingActive=float.IsFinite(observed.coolingActual)&&observed.coolingActual>=expected*.97f&&expected>.01f;
}break;
}
lock(fsdObservationLock)fsdObservation=observed;
}catch{Expire();}
}
[UnmanagedCallersOnly]
static ulong FsdHook(nuint obj,float dt)
{
Interlocked.Increment(ref fsdEntryCallsValue);fsdEntryAt=GetTickCount64();fsdEntryThread=GetCurrentThreadId();fsdEntryDt=dt;var fsd=obj;var frame=Current();var selected=inGame&&GetCurrentThreadId()==owningThread&&BoundFsd(frame,fsd);
if(GetCurrentThreadId()==owningThread)try{PollNormalBoostPulse(frame);}catch{try{Expire();}catch{}}
InputOverlay overlay=default;FsdPulse pulse=default;bool applied=false;
try{if(selected)Interlocked.Increment(ref fsdSelectedCallsValue);if(selected)SelectTarget(frame);if(selected)SelectLocalTarget(frame);applied=selected&&ApplyInput(frame.inputRoot,ref overlay);if(selected)pulse=PrepareFsdPulse(frame,fsd);}catch{}
try{return Call(originalFsd,(ulong)obj,F32(dt));}catch{return 0;}
finally{
try{if(pulse.applied)FinishFsdPulse(ref pulse);if(selected&&BoundFsd(frame,fsd))ObserveFsd(frame,fsd,pulse);if(selected&&BoundFsd(frame,fsd))PollReferenceGeometry(frame);
if(selected&&BoundFsd(frame,fsd))PollLocalFlight(frame);if(selected&&BoundFsd(frame,fsd))PollCommerceRead(frame);if(selected&&BoundFsd(frame,fsd))PollCommunityGoals(frame);
}catch{try{Expire();}catch{}}
finally{try{if(applied){Restore(ref overlay);inputAppliedAt=GetTickCount64();}}catch{}}
// A persistent original action must not be undone by restoring the
// temporary axes of the FSD invocation that carried it.
try{if(selected&&BoundFsd(frame,fsd))PollLocalAction(frame);}catch{try{Expire();}catch{}}
try{if(selected)PlanGameRoute(frame);if(selected)PollGalaxyCatalogue(frame);}catch{try{Expire();}catch{}}
try{if(selected)PollGalaxyAccount(frame);}catch{}
try{if(selected)PollGalaxyFilterFacts(frame);}catch{}
try{RetireGalaxyColonisationLoads(frame);}catch{}
}
}
[UnmanagedCallersOnly]
static nuint BodyLookupHook(nuint source,nuint output,nuint addressValue)
{
nuint result;try{result=(nuint)Call(originalBodyLookup,(ulong)source,(ulong)output,(ulong)addressValue);}catch{return 0;}
if(!Enabled()||!inGame||GetCurrentThreadId()!=owningThread)return result;
try{
var frame=Current();if(!frame.valid||GetTickCount64()-frame.tick>300||!BoundFsd(frame,frame.fsd)||Pointer(frame.fsd+0x6A0)!=source||Pointer(Pointer(frame.transform)+0x60)!=image+0x2D9C200)return result;
var world=Read<ulong>(frame.transform+0xE8);bool unified;lock(geometryLock)unified=geometry.unifiedReference;if(unified||world==0||Read<ulong>(addressValue)!=world)return result;
var body=Pointer(output);if(body==0||Read<ulong>(body+0x18)!=world)return result;var kind=Read<uint>(body+0x20);if(kind!=1&&kind!=2&&kind!=7)return result;var radiusGetter=Pointer(Pointer(source)+0xD0);if(radiusGetter!=image+0x3CF0410)return result;
double radius=0;var returned=(nuint)Call(radiusGetter,(ulong)source,(ulong)&radius,(ulong)body);var multiplier=Read<float>(Pointer(frame.fsd+0x50)+0x44);
if(returned!=(nuint)(&radius)||!double.IsFinite(radius)||radius<=0||radius>1e14||!float.IsFinite(multiplier)||multiplier<1||multiplier>10)return result;
var next=new GeometryObservation{tick=GetTickCount64(),entity=frame.entity,worldAddress=world,bodyAddress=Read<ulong>(body+0x18),bodyKind=kind,starType=kind==1?Read<uint>(body+0x448):0,exclusionRadius=radius,escapeRadius=radius*multiplier,position=frame.position};lock(geometryLock)geometry=next;
}catch{}return result;
}
}