"""Bounded observation of original ATC calls made by the running acceptance driver. Sends no input or native calls."""
import argparse,hashlib,json,time
from pathlib import Path
import frida

p=argparse.ArgumentParser(description=__doc__)
p.add_argument('--pid',type=int,required=True);p.add_argument('--output',type=Path,required=True)
p.add_argument('--seconds',type=int,default=25)
a=p.parse_args()
if not 5<=a.seconds<=45:raise ValueError('Use a bounded observation duration')
game=Path(r'D:/SteamLibrary/steamapps/common/Elite Dangerous/Products/elite-dangerous-odyssey-64/EliteDangerous64.exe')
if hashlib.sha256(game.read_bytes()).hexdigest()!='6891f70a021fa9f3a0e00458c284f48d1226e68ae7fc02ad48081aed47857e93':raise ValueError('Game build changed')
source=r'''
const game=Process.getModuleByName('EliteDangerous64.exe');let total=0;const hooks=[];
function state(component){return {address:component.toString(),table:component.readPointer().sub(game.base).toString(),
 docking:component.add(0x4c0).readU32(),request:component.add(0x614).readU32(),pad:component.add(0x4e9).readU8(),
 station:component.add(0x4e0).readPointer().toString(),requestStation:component.add(0x608).readPointer().toString()};}
for(const [name,rva,offset] of [['request',0x2c65cf0,0],['send-request',0x2cb53a0,0x58],['denial',0x2c93d20,-8],['grant',0x2c93e20,-8],['timeout',0x2c94020,-8]]){
 hooks.push(Interceptor.attach(game.base.add(rva),{onEnter(args){
  this.skip=total++>=100;if(this.skip)return;this.name=name;this.component=args[0].add(offset);
  try{
   const row={kind:'enter',name,thread:Process.getCurrentThreadId(),args:[args[0].toString(),args[1].toString(),args[2].toString()],state:state(this.component)};
   if(name==='request'){const life=args[1].readPointer();row.station={life:life.toString(),id:life.add(0xb0).readU64().toString(),type:life.add(0xc0).readU32()};}
   send(row);
  }catch(error){send({name,kind:'observation-error',error:String(error)});}
 },onLeave(result){if(this.skip)return;try{send({kind:'leave',name:this.name,result:result.toString(),state:state(this.component)});}catch(error){send({name:this.name,kind:'observation-error',error:String(error)});}}}));
}
rpc.exports={summary(){return {observedCalls:total,gameInputsSent:0,nativeCallsInvoked:0,screenUse:false};}};
'''
with a.output.open('x',encoding='utf-8') as out:
    session=frida.attach(a.pid)
    try:
        script=session.create_script(source)
        def message(data,binary):
            out.write(json.dumps(data,ensure_ascii=False)+'\n');out.flush()
        script.on('message',message);script.load();time.sleep(a.seconds)
        result=script.exports_sync.summary();out.write(json.dumps(result)+'\n');print(json.dumps(result),flush=True);script.unload()
    finally:session.detach()
