"""Observe only the plugin's original docking getters; no game calls or inputs."""
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)
a = p.parse_args()
game = Path('D:/SteamLibrary/steamapps/common/Elite Dangerous/Products/elite-dangerous-odyssey-64/EliteDangerous64.exe')
expected = 'ed5a8f8cd10f9a80d3bda5badd7ba1d4fcfc15996c8c7e018852cb170e264b63'
if hashlib.sha256(game.read_bytes()).hexdigest() != expected:
raise ValueError('Unsupported game build')
source = r'''
const game = Process.getModuleByName('EliteDangerous64.exe');
if (game.path.toLowerCase() !== IMAGE.toLowerCase()) throw new Error('Selected image differs');
const hooks = []; const counts = {};
for (const [name, rva] of [['moduleAvailable',0xed7e20],['dockingAvailability',0xed7e10],['dockingDuty',0xed6620],['launchDuty',0xed7d10]]) {
counts[name] = 0;
hooks.push(Interceptor.attach(game.base.add(rva), {
onEnter(args) {
this.record = ++counts[name] <= 3;
if (!this.record) return;
const row = {kind:'enter', name, thread:Process.getCurrentThreadId(), object:args[0].toString()};
try {
row.table = args[0].readPointer().sub(game.base).toString();
row.moduleId = args[0].add(0x290).readU32();
row.moduleService = args[0].add(0x168).readPointer().toString();
row.brain = args[0].add(0x1d0).readPointer().toString();
row.dockingModuleId = args[0].add(0x360).readU32();
row.dockingModuleService = args[0].add(0xe0).readPointer().toString();
row.dockingModuleOperational = args[0].add(0x377).readU8();
for (const [label, offset, slot] of [['module',0x168,0xb0],['brain',0x1d0,0xa0]]) {
const object = args[0].add(offset).readPointer();
const table = object.readPointer();
row[label+'Table'] = table.sub(game.base).toString();
row[label+'Method'] = table.add(slot).readPointer().sub(game.base).toString();
}
} catch(error) { row.observationError = String(error); }
send(row);
},
onLeave(result) { if (this.record) send({kind:'leave',name,result:result.toUInt32() & 255}); }
}));
}
rpc.exports = {summary(){return {counts,gameCallsInvoked:0,gameInputsSent:0,screenUse:false};}};
'''.replace('IMAGE', json.dumps(str(game)))
with a.output.open('x', encoding='utf-8') as output:
session = frida.attach(a.pid)
try:
script = session.create_script(source)
script.on('message', lambda message, data: (output.write(json.dumps(message)+'\n'), output.flush()))
script.load()
time.sleep(5)
result = script.exports_sync.summary()
output.write(json.dumps(result)+'\n')
print(json.dumps(result), flush=True)
script.unload()
finally:
session.detach()
"""Observe only the plugin's original docking getters; no game calls or inputs."""
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)
a = p.parse_args()
game = Path('D:/SteamLibrary/steamapps/common/Elite Dangerous/Products/elite-dangerous-odyssey-64/EliteDangerous64.exe')
expected = 'ed5a8f8cd10f9a80d3bda5badd7ba1d4fcfc15996c8c7e018852cb170e264b63'
if hashlib.sha256(game.read_bytes()).hexdigest() != expected:
raise ValueError('Unsupported game build')
source = r'''
const game = Process.getModuleByName('EliteDangerous64.exe');
if (game.path.toLowerCase() !== IMAGE.toLowerCase()) throw new Error('Selected image differs');
const hooks = []; const counts = {};
for (const [name, rva] of [['moduleAvailable',0xed7e20],['dockingAvailability',0xed7e10],['dockingDuty',0xed6620],['launchDuty',0xed7d10]]) {
counts[name] = 0;
hooks.push(Interceptor.attach(game.base.add(rva), {
onEnter(args) {
this.record = ++counts[name] <= 3;
if (!this.record) return;
const row = {kind:'enter', name, thread:Process.getCurrentThreadId(), object:args[0].toString()};
try {
row.table = args[0].readPointer().sub(game.base).toString();
row.moduleId = args[0].add(0x290).readU32();
row.moduleService = args[0].add(0x168).readPointer().toString();
row.brain = args[0].add(0x1d0).readPointer().toString();
row.dockingModuleId = args[0].add(0x360).readU32();
row.dockingModuleService = args[0].add(0xe0).readPointer().toString();
row.dockingModuleOperational = args[0].add(0x377).readU8();
for (const [label, offset, slot] of [['module',0x168,0xb0],['brain',0x1d0,0xa0]]) {
const object = args[0].add(offset).readPointer();
const table = object.readPointer();
row[label+'Table'] = table.sub(game.base).toString();
row[label+'Method'] = table.add(slot).readPointer().sub(game.base).toString();
}
} catch(error) { row.observationError = String(error); }
send(row);
},
onLeave(result) { if (this.record) send({kind:'leave',name,result:result.toUInt32() & 255}); }
}));
}
rpc.exports = {summary(){return {counts,gameCallsInvoked:0,gameInputsSent:0,screenUse:false};}};
'''.replace('IMAGE', json.dumps(str(game)))
with a.output.open('x', encoding='utf-8') as output:
session = frida.attach(a.pid)
try:
script = session.create_script(source)
script.on('message', lambda message, data: (output.write(json.dumps(message)+'\n'), output.flush()))
script.load()
time.sleep(5)
result = script.exports_sync.summary()
output.write(json.dumps(result)+'\n')
print(json.dumps(result), flush=True)
script.unload()
finally:
session.detach()