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(frame.life+0xC0)==8&&Read(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=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(navigation+0x2C0);var capacity=Read(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(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(record+0x20)==target&&Read(record+0x188)==2&&Read(record+0x184)==0){matches++;key=Read(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(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(route+0x148)!=destination)return false;Call(Pointer(Pointer(route)+0x38),(ulong)route,0);return true; } static bool InvokeGameRouteCancellation(nuint route,ulong expectedDestination) { if(Read(route+0x148)!=expectedDestination||Pointer(Pointer(route)+0xE0)!=image+0x25E9E30)return false; Call(Pointer(Pointer(route)+0xE0),(ulong)route,0,0,1);return Read(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(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(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(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(frame.fsd+0x758)==3||Read(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(already+0x20)==wanted.target&&Read(already+0x188)==2&&Read(already+0x184)==0){targetConfirmed=wanted.generation;targetConsumed=wanted.generation;return;} if(Read(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(record+0x20)!=wanted.target||Read(record+0x188)!=2||Read(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(selected+0x20)!=wanted.target)Call(image+0x2ED6840,(ulong)navigation,unchecked((ulong)key),1,0); var confirmed=SelectedTarget(frame.fsd);if(confirmed!=0&&Read(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(fsd+0x758);FsdLease desired;lock(leaseLock)desired=fsdLease; var heat=HeatHudPercent(Read(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(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(input+0xBD)!=0||Read(input+0xBE)!=0||Read(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(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(pulse.life+0xC0)!=8||Read(pulse.life+0xB0)!=pulse.entity||Pointer(pulse.fsd+0x398)!=pulse.inputRoot+0x60)return; if(Read(pulse.field)==1)Write(pulse.field,(byte)0);fsdOwned=Read(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(fsd+0x758);observed.mode=Read(fsd+0x759);observed.stage=Read(fsd+0x794);observed.clock=Read(fsd+0x7A0);observed.rate=Read(fsd+0x7A4);observed.elapsed=Read(fsd+0x7A8);observed.duration=Read(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(frame.physics+0x6B0);observed.throttle=Read(frame.inputRoot+0x70);observed.throttleOverride=Read(Pointer(frame.inputRoot+0x2F8)+0x680); var target=SelectedTarget(fsd);var navigation=Pointer(fsd+0x338); if(target!=0&&Read(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(fallback,160).Clear();new Span(direction,4).Clear();new Span(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(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(drive+4);observed.driveHeatBase=Read(drive+0xC);observed.driveHeatRamp=Read(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(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(config+4);observed.coolingMaximum=Read(config+8);observed.coolingHeatLower=Read(config+0x18);observed.coolingHeatUpper=Read(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(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(frame.transform+0xE8);bool unified;lock(geometryLock)unified=geometry.unifiedReference;if(unified||world==0||Read(addressValue)!=world)return result; var body=Pointer(output);if(body==0||Read(body+0x18)!=world)return result;var kind=Read(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(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(body+0x18),bodyKind=kind,starType=kind==1?Read(body+0x448):0,exclusionRadius=radius,escapeRadius=radius*multiplier,position=frame.position};lock(geometryLock)geometry=next; }catch{}return result; } }