npm install
$ frida QuakeSpasm --enable-jit -l _agent.js
$ curl -s http://localhost:1337/stats | jq
$ curl -s -X POST http://localhost:1337/attack | jq
npm run build
npm run watch
import * as Koa from "koa"; | |
import * as Router from "koa-router"; | |
const app = new Koa(); | |
const router = new Router(); | |
enum Stat { | |
Health = 0, | |
Shells = 6, | |
} | |
interface Stats { | |
health: number; | |
shells: number; | |
} | |
router | |
.get("/stats", async (ctx, next) => { | |
ctx.body = await readStats(); | |
}) | |
.post("/attack", async (ctx, next) => { | |
await attack(); | |
ctx.body = {}; | |
}); | |
app | |
.use(router.routes()) | |
.use(router.allowedMethods()) | |
.listen(1337); | |
const clientState = importSymbol("cl"); | |
const attackDown: any = new NativeFunction(importSymbol("IN_AttackDown"), "void", []); | |
const attackUp: any = new NativeFunction(importSymbol("IN_AttackUp"), "void", []); | |
function readStats(): Promise<Stats> { | |
return perform(() => { | |
return { | |
health: readStat(Stat.Health), | |
shells: readStat(Stat.Shells), | |
}; | |
}); | |
} | |
function readStat(stat: Stat): number { | |
return Memory.readInt(clientState.add(28 + stat * 4)); | |
} | |
async function attack(): Promise<void> { | |
await perform(() => { | |
attackDown(); | |
}); | |
await sleep(50); | |
await perform(() => { | |
attackUp(); | |
}); | |
} | |
function sleep(duration: number): Promise<void> { | |
return new Promise(resolve => { | |
setTimeout(() => { resolve(); }, duration); | |
}); | |
} | |
type PendingWork = () => any; | |
const pending: PendingWork[] = []; | |
function perform<T>(f: () => T): Promise<T> { | |
return new Promise((resolve, reject) => { | |
pending.push(() => { | |
try { | |
const result = f(); | |
resolve(result); | |
} catch (e) { | |
reject(e); | |
} | |
}); | |
}); | |
} | |
Interceptor.attach(importSymbol("IN_SendKeyEvents"), { | |
onEnter() { | |
while (pending.length > 0) { | |
const f = pending.shift(); | |
f(); | |
} | |
} | |
}); | |
function importSymbol(name: string): NativePointer { | |
return Module.findExportByName("QuakeSpasm", name); | |
} |
{ | |
"name": "quake-rest-api", | |
"version": "1.0.0", | |
"description": "Quake REST API", | |
"private": true, | |
"main": "agent/index.ts", | |
"scripts": { | |
"prepare": "npm run build", | |
"build": "frida-compile agent/index.ts -o _agent.js -x", | |
"watch": "frida-compile agent/index.ts -o _agent.js -x -w" | |
}, | |
"devDependencies": { | |
"@types/koa": "^2.0.46", | |
"@types/koa-router": "^7.0.31", | |
"frida-compile": "^6.0.0", | |
"frida-gum-types": "^2.0.0", | |
"koa": "^2.5.2", | |
"koa-router": "^7.4.0" | |
} | |
} |