// Build-pinned diagnostics and original eligible menu actions. No screen, input or readiness overrides.
const module = Process.getModuleByName('EliteDangerous64.exe');
if (module.path.toLowerCase() !== PROFILE.image.toLowerCase()) throw new Error('Unexpected game image');
const base = module.base;
const seen = new Set();
let flowThread = 0, flowState = '', lastPose = 0, lastFlow = 0, entered = false, menuStage = 0;
const poseBindings = new Map();
function emit(value) { send({...value, at: new Date().toISOString()}); }
function once(value) { const key=JSON.stringify(value);if(!seen.has(key)){seen.add(key);emit(value);} }
function rva(pointer) {return pointer.compare(base)>=0&&pointer.compare(base.add(module.size))<0?pointer.sub(base).toUInt32():null;}
function name(pointer) {try {const text=pointer.readUtf8String();return /^[A-Za-z][A-Za-z0-9_ .-]{0,127}$/.test(text)?text:null;}catch(_){return null;}}
function leaf(object, method) {
const code=new Uint8Array(method.readByteArray(12));
if(code[0]!==0x48)return null;
if(code[1]===0x8D&&code[2]===0x81&&code[7]===0xC3)return object.add(method.add(3).readS32());
if(code[1]===0x8D&&code[2]===0x41&&code[4]===0xC3)return object.add(method.add(3).readS8());
if(code[1]===0x8B&&code[2]===0x81&&code[7]===0xC3)return object.add(method.add(3).readS32()).readPointer();
if(code[1]===0x8B&&code[2]===0x41&&code[4]===0xC3)return object.add(method.add(3).readS8()).readPointer();
return null;
}
function currentFlow(context) {
const flow=context.add(0x30).readPointer(),table=flow.readPointer();
if(!table.add(0x20).readPointer().equals(base.add(PROFILE.flowAction)))return null;
const csm=flow.add(0x310).readPointer();
const from=name(csm.add(0x100).readPointer().readPointer()),to=name(csm.add(0x108).readPointer().readPointer());
if(from===null||to===null)return null;
return {flow,csm,from,to};
}
Interceptor.attach(base.add(PROFILE.flowUpdate),{
onEnter(args){this.flowContext=args[1];},
onLeave(){
if(Date.now()-lastFlow<100)return;lastFlow=Date.now();
try {
const state=currentFlow(this.flowContext);if(state===null)return;
const thread=Process.getCurrentThreadId();
if(flowThread!==0&&flowThread!==thread)throw new Error('Game owning thread changed');
flowThread=thread;flowState=state.from===state.to?state.from:'';
once({kind:'flow',from:state.from,to:state.to,thread,flowVtable:rva(state.flow.readPointer()),csmVtable:rva(state.csm.readPointer())});
if(flowState==='InGame'&&!entered){entered=true;emit({kind:'native-world-entered'});}
}catch(error){once({kind:'flow-unavailable',error:String(error)});}
}
});
const selectMenu = new NativeFunction(base.add(PROFILE.menuAction),'void',['pointer','pointer','int'],{scheduling:'exclusive'});
if(ENTER_SOLO)Interceptor.attach(base.add(PROFILE.menuUpdate),{
onEnter(args){this.activity=args[0];},
onLeave(){
if(entered||Process.getCurrentThreadId()!==flowThread)return;
try {
const activity=this.activity;
if(!activity.add(0x80).readPointer().add(8).readPointer().equals(base.add(PROFILE.menuAction)))return;
const flow=activity.add(0x118).readPointer();
if(!flow.readPointer().add(0x20).readPointer().equals(base.add(PROFILE.flowAction)))return;
const csm=flow.add(0x310).readPointer(),from=name(csm.add(0x100).readPointer().readPointer()),to=name(csm.add(0x108).readPointer().readPointer());
if(from!==to)return;
const wanted=menuStage===0&&from==='MainMenu'?'ContinueMenu':menuStage===1&&from==='ContinueMenu'?'SetupPlaySolo':null;
if(wanted===null)return;
const definition=activity.add(0x10).readPointer(),selected=activity.add(0x208).readU32(),categories=definition.add(0x10).readU32();
if(categories>128||selected>=categories)return;
const category=definition.add(8).readPointer().add(selected*0x58);
if(category.readU8()<1||category.readU8()>3)return;
const count=category.add(0x48).readU32(),buttons=category.add(0x40).readPointer(),cache=activity.add(0x248).readPointer();
if(count>128||activity.add(0x240).readU64().toNumber()!==count)return;
let candidate=-1;const rows=[];
for(let index=0;index<count;index++) {
const button=buttons.add(index*0x78),cached=cache.add(index*0xC8);
const configured=new Uint8Array(button.readByteArray(0x78)),actual=new Uint8Array(cached.add(0x30).readByteArray(0x78));
if(configured.some((byte,offset)=>byte!==actual[offset]))throw new Error('Menu cache/definition mismatch');
const action=name(button.readPointer().readPointer()),eligible=cached.readU16();
rows.push({action,eligible});
if(action===wanted&&eligible===0x0101){if(candidate>=0)throw new Error('Ambiguous action');candidate=index;}
}
once({kind:'menu-eligibility',wanted,rows});
if(candidate<0||cache.add(candidate*0xC8).readU16()!==0x0101)return;
menuStage++;emit({kind:'original-native-menu-action',action:wanted,thread:flowThread});
selectMenu(activity.add(0x80),ptr(0),candidate);
}catch(error){once({kind:'menu-unavailable',error:String(error)});}
}
});
function poseFor(physics) {
const key=physics.toString();let binding=poseBindings.get(key);
if(binding!==undefined)return binding;
const owner=physics.add(0x20).readPointer(),ownerTable=owner.readPointer(),method=ownerTable.add(0x20).readPointer();
const reference=leaf(owner,method);
if(reference===null){once({kind:'owner-getter-unmapped',method:rva(method),code:method.readByteArray(24)});return null;}
const life=reference.readPointer();if(life.add(0xC0).readU32()!==8)return null;
const component=life.add(0xC8).readPointer(),root=component.sub(0x30),array=root.add(0x18).readPointer();
let found=null;
for(let index=0;index<512;index++){
try{
const object=array.add(index*8).readPointer(),table=object.readPointer();
if(!table.add(0x28).readPointer().equals(base.add(PROFILE.poseGetter)))continue;
if(found!==null&&!found.object.equals(object))throw new Error('Ambiguous ship transform');
found={object,life,id:life.add(0xB0).readU64().toString(),index};
}catch(_){}
}
if(found!==null){poseBindings.set(key,found);emit({kind:'player-transform-binding',id:found.id,index:found.index,transform:found.object.toString(),physics:key,ownerMethod:rva(method)});}
return found;
}
function pooled(poolRva,owner,expectedSize) {
const pool=base.add(poolRva),stride=pool.add(0x68).readU64().toNumber();
if(stride!==expectedSize||pool.add(0x90).readU64().toNumber()>128)return null;
let block=pool.add(0x30).readPointer();
for(let chain=0;!block.isNull()&&chain<128;chain++){
const count=block.add(0x38).readU64().toNumber();
if(count<1||count>128)return null;
for(let index=0;index<count;index++){
const object=block.add(0x80+index*stride);
try{if(rva(object.readPointer())!==null&&object.add(0x10).readPointer().equals(owner))return object;}catch(_){}
}
block=block.add(0x20).readPointer();
}
return null;
}
Interceptor.attach(base.add(PROFILE.physicsUpdate),{
onEnter(args){
if(flowState!=='InGame'||Date.now()-lastPose<100||Process.getCurrentThreadId()!==flowThread)return;
lastPose=Date.now();
try {
const physics=args[0],binding=poseFor(physics);if(binding===null)return;
if(binding.life.add(0xC0).readU32()!==8){poseBindings.clear();return;}
const transform=binding.object;
if(!transform.readPointer().add(0x28).readPointer().equals(base.add(PROFILE.poseGetter)))throw new Error('Transform type changed');
const matrix=transform.add(transform.add(0x410).readU8()!==0||transform.sub(8).readPointer().add(0x10).readU32()===0?0xF8:0x1E8);
const position=[0x60,0x68,0x70].map(offset=>matrix.add(offset).readDouble());
const axes=[[0,8,0x10],[0x20,0x28,0x30],[0x40,0x48,0x50]].map(offsets=>offsets.map(offset=>matrix.add(offset).readDouble()));
if(position.some(value=>!Number.isFinite(value))||axes.some(axis=>Math.abs(axis.reduce((sum,v)=>sum+v*v,0)-1)>.01))throw new Error('Invalid native transform');
const owner=physics.add(0x10).readPointer();
const heat=pooled(PROFILE.heatPool,owner,0x190),manager=pooled(PROFILE.heatManagerPool,owner,0x228),fsd=pooled(PROFILE.fsdPool,owner,0xEC0);
const input=physics.add(0x3B0).readPointer();
const sample={kind:'native-flight-sample',id:binding.id,position,axes,speed:physics.add(0x6A8).readFloat(),inputRoot:input.sub(0x60).toString(),inputVtable:rva(input.readPointer()),heatPercent:heat===null?null:heat.add(0x188).readFloat()*100,thermalObject:heat===null?null:heat.toString(),fsdObject:fsd===null?null:fsd.toString()};
if(manager!==null){const ratio=manager.add(0x1F0).readPointer();sample.heatDisplayPercent=ratio.isNull()?null:ratio.readFloat()*100;}
if(fsd!==null){sample.fsdState=fsd.add(0x758).readU8();sample.fsdStage=fsd.add(0x794).readU8();sample.fsdRemaining=fsd.add(0x7A0).readFloat();}
emit(sample);
}catch(error){once({kind:'telemetry-unavailable',error:String(error)});}
}
});
emit({kind:'probe-ready',build:PROFILE.sha256,readOnlyFlight:true,originalSoloMenuAllowed:ENTER_SOLO});
// Build-pinned diagnostics and original eligible menu actions. No screen, input or readiness overrides.
const module = Process.getModuleByName('EliteDangerous64.exe');
if (module.path.toLowerCase() !== PROFILE.image.toLowerCase()) throw new Error('Unexpected game image');
const base = module.base;
const seen = new Set();
let flowThread = 0, flowState = '', lastPose = 0, lastFlow = 0, entered = false, menuStage = 0;
const poseBindings = new Map();
function emit(value) { send({...value, at: new Date().toISOString()}); }
function once(value) { const key=JSON.stringify(value);if(!seen.has(key)){seen.add(key);emit(value);} }
function rva(pointer) {return pointer.compare(base)>=0&&pointer.compare(base.add(module.size))<0?pointer.sub(base).toUInt32():null;}
function name(pointer) {try {const text=pointer.readUtf8String();return /^[A-Za-z][A-Za-z0-9_ .-]{0,127}$/.test(text)?text:null;}catch(_){return null;}}
function leaf(object, method) {
const code=new Uint8Array(method.readByteArray(12));
if(code[0]!==0x48)return null;
if(code[1]===0x8D&&code[2]===0x81&&code[7]===0xC3)return object.add(method.add(3).readS32());
if(code[1]===0x8D&&code[2]===0x41&&code[4]===0xC3)return object.add(method.add(3).readS8());
if(code[1]===0x8B&&code[2]===0x81&&code[7]===0xC3)return object.add(method.add(3).readS32()).readPointer();
if(code[1]===0x8B&&code[2]===0x41&&code[4]===0xC3)return object.add(method.add(3).readS8()).readPointer();
return null;
}
function currentFlow(context) {
const flow=context.add(0x30).readPointer(),table=flow.readPointer();
if(!table.add(0x20).readPointer().equals(base.add(PROFILE.flowAction)))return null;
const csm=flow.add(0x310).readPointer();
const from=name(csm.add(0x100).readPointer().readPointer()),to=name(csm.add(0x108).readPointer().readPointer());
if(from===null||to===null)return null;
return {flow,csm,from,to};
}
Interceptor.attach(base.add(PROFILE.flowUpdate),{
onEnter(args){this.flowContext=args[1];},
onLeave(){
if(Date.now()-lastFlow<100)return;lastFlow=Date.now();
try {
const state=currentFlow(this.flowContext);if(state===null)return;
const thread=Process.getCurrentThreadId();
if(flowThread!==0&&flowThread!==thread)throw new Error('Game owning thread changed');
flowThread=thread;flowState=state.from===state.to?state.from:'';
once({kind:'flow',from:state.from,to:state.to,thread,flowVtable:rva(state.flow.readPointer()),csmVtable:rva(state.csm.readPointer())});
if(flowState==='InGame'&&!entered){entered=true;emit({kind:'native-world-entered'});}
}catch(error){once({kind:'flow-unavailable',error:String(error)});}
}
});
const selectMenu = new NativeFunction(base.add(PROFILE.menuAction),'void',['pointer','pointer','int'],{scheduling:'exclusive'});
if(ENTER_SOLO)Interceptor.attach(base.add(PROFILE.menuUpdate),{
onEnter(args){this.activity=args[0];},
onLeave(){
if(entered||Process.getCurrentThreadId()!==flowThread)return;
try {
const activity=this.activity;
if(!activity.add(0x80).readPointer().add(8).readPointer().equals(base.add(PROFILE.menuAction)))return;
const flow=activity.add(0x118).readPointer();
if(!flow.readPointer().add(0x20).readPointer().equals(base.add(PROFILE.flowAction)))return;
const csm=flow.add(0x310).readPointer(),from=name(csm.add(0x100).readPointer().readPointer()),to=name(csm.add(0x108).readPointer().readPointer());
if(from!==to)return;
const wanted=menuStage===0&&from==='MainMenu'?'ContinueMenu':menuStage===1&&from==='ContinueMenu'?'SetupPlaySolo':null;
if(wanted===null)return;
const definition=activity.add(0x10).readPointer(),selected=activity.add(0x208).readU32(),categories=definition.add(0x10).readU32();
if(categories>128||selected>=categories)return;
const category=definition.add(8).readPointer().add(selected*0x58);
if(category.readU8()<1||category.readU8()>3)return;
const count=category.add(0x48).readU32(),buttons=category.add(0x40).readPointer(),cache=activity.add(0x248).readPointer();
if(count>128||activity.add(0x240).readU64().toNumber()!==count)return;
let candidate=-1;const rows=[];
for(let index=0;index<count;index++) {
const button=buttons.add(index*0x78),cached=cache.add(index*0xC8);
const configured=new Uint8Array(button.readByteArray(0x78)),actual=new Uint8Array(cached.add(0x30).readByteArray(0x78));
if(configured.some((byte,offset)=>byte!==actual[offset]))throw new Error('Menu cache/definition mismatch');
const action=name(button.readPointer().readPointer()),eligible=cached.readU16();
rows.push({action,eligible});
if(action===wanted&&eligible===0x0101){if(candidate>=0)throw new Error('Ambiguous action');candidate=index;}
}
once({kind:'menu-eligibility',wanted,rows});
if(candidate<0||cache.add(candidate*0xC8).readU16()!==0x0101)return;
menuStage++;emit({kind:'original-native-menu-action',action:wanted,thread:flowThread});
selectMenu(activity.add(0x80),ptr(0),candidate);
}catch(error){once({kind:'menu-unavailable',error:String(error)});}
}
});
function poseFor(physics) {
const key=physics.toString();let binding=poseBindings.get(key);
if(binding!==undefined)return binding;
const owner=physics.add(0x20).readPointer(),ownerTable=owner.readPointer(),method=ownerTable.add(0x20).readPointer();
const reference=leaf(owner,method);
if(reference===null){once({kind:'owner-getter-unmapped',method:rva(method),code:method.readByteArray(24)});return null;}
const life=reference.readPointer();if(life.add(0xC0).readU32()!==8)return null;
const component=life.add(0xC8).readPointer(),root=component.sub(0x30),array=root.add(0x18).readPointer();
let found=null;
for(let index=0;index<512;index++){
try{
const object=array.add(index*8).readPointer(),table=object.readPointer();
if(!table.add(0x28).readPointer().equals(base.add(PROFILE.poseGetter)))continue;
if(found!==null&&!found.object.equals(object))throw new Error('Ambiguous ship transform');
found={object,life,id:life.add(0xB0).readU64().toString(),index};
}catch(_){}
}
if(found!==null){poseBindings.set(key,found);emit({kind:'player-transform-binding',id:found.id,index:found.index,transform:found.object.toString(),physics:key,ownerMethod:rva(method)});}
return found;
}
function pooled(poolRva,owner,expectedSize) {
const pool=base.add(poolRva),stride=pool.add(0x68).readU64().toNumber();
if(stride!==expectedSize||pool.add(0x90).readU64().toNumber()>128)return null;
let block=pool.add(0x30).readPointer();
for(let chain=0;!block.isNull()&&chain<128;chain++){
const count=block.add(0x38).readU64().toNumber();
if(count<1||count>128)return null;
for(let index=0;index<count;index++){
const object=block.add(0x80+index*stride);
try{if(rva(object.readPointer())!==null&&object.add(0x10).readPointer().equals(owner))return object;}catch(_){}
}
block=block.add(0x20).readPointer();
}
return null;
}
Interceptor.attach(base.add(PROFILE.physicsUpdate),{
onEnter(args){
if(flowState!=='InGame'||Date.now()-lastPose<100||Process.getCurrentThreadId()!==flowThread)return;
lastPose=Date.now();
try {
const physics=args[0],binding=poseFor(physics);if(binding===null)return;
if(binding.life.add(0xC0).readU32()!==8){poseBindings.clear();return;}
const transform=binding.object;
if(!transform.readPointer().add(0x28).readPointer().equals(base.add(PROFILE.poseGetter)))throw new Error('Transform type changed');
const matrix=transform.add(transform.add(0x410).readU8()!==0||transform.sub(8).readPointer().add(0x10).readU32()===0?0xF8:0x1E8);
const position=[0x60,0x68,0x70].map(offset=>matrix.add(offset).readDouble());
const axes=[[0,8,0x10],[0x20,0x28,0x30],[0x40,0x48,0x50]].map(offsets=>offsets.map(offset=>matrix.add(offset).readDouble()));
if(position.some(value=>!Number.isFinite(value))||axes.some(axis=>Math.abs(axis.reduce((sum,v)=>sum+v*v,0)-1)>.01))throw new Error('Invalid native transform');
const owner=physics.add(0x10).readPointer();
const heat=pooled(PROFILE.heatPool,owner,0x190),manager=pooled(PROFILE.heatManagerPool,owner,0x228),fsd=pooled(PROFILE.fsdPool,owner,0xEC0);
const input=physics.add(0x3B0).readPointer();
const sample={kind:'native-flight-sample',id:binding.id,position,axes,speed:physics.add(0x6A8).readFloat(),inputRoot:input.sub(0x60).toString(),inputVtable:rva(input.readPointer()),heatPercent:heat===null?null:heat.add(0x188).readFloat()*100,thermalObject:heat===null?null:heat.toString(),fsdObject:fsd===null?null:fsd.toString()};
if(manager!==null){const ratio=manager.add(0x1F0).readPointer();sample.heatDisplayPercent=ratio.isNull()?null:ratio.readFloat()*100;}
if(fsd!==null){sample.fsdState=fsd.add(0x758).readU8();sample.fsdStage=fsd.add(0x794).readU8();sample.fsdRemaining=fsd.add(0x7A0).readFloat();}
emit(sample);
}catch(error){once({kind:'telemetry-unavailable',error:String(error)});}
}
});
emit({kind:'probe-ready',build:PROFILE.sha256,readOnlyFlight:true,originalSoloMenuAllowed:ENTER_SOLO});