This project aims to provide a low-level API for creating and playing back playable quotes that spans several emulated game platforms.
Most of the scripts in this repository are intended to integrate with already-running native-code emulators using Frida. For browser based emulators, you will generally use our scripts in <script> tags. Consult emulator-specific documentation for precise steps.
Typical usage, assuming a hypothetical emulator called {emu}:
$ emu system.img &
$ frida -l emu-plumbing.js -l test-feature.js emu
To install frida, you'd typically create a Python virtual environment with venv and then pip install frida-tools.
Here's a list of emulators that we aspire to support very soon:
- RetroArch
- Game Boy, via the gambatte libretro core
- Super Nintendo Entertainment System, via the snes9x libretro core
- Nintendo 64, via the mupen64plus-libretro-nx libretro core
- v86, a PC emulator an x86-to-wasm JIT running in the browser
- QEMU, a high-performance emulator/virtualizer often used to emulate desktop PC platforms on desktop PCs
- MAME, a multi-emulator emphasizing arcade games
A JavaScript file called {emulator}-plumbing.js should, upon execution, provide a QuoteKit object in the global namespace that offers the methods and events below.
getDeviceInfo(): Returns anobjectcontaining name, block size, and block count for devices. The keys of this object will tell you the names of the devices mentioned elsewhere in the API.- TODO: more precisely document the structure of the returned object and maybe change the type from
objecttoMap
- TODO: more precisely document the structure of the returned object and maybe change the type from
saveResidue(): Returns anArrayBuffer. Ideally this contains only information that isn't stored in block devices. However, sloppy implementations may return gigantic residues.restoreResidue(buffer): Restores a previously saved residue (assuming the machine's device configuration has not changed, that the residue is compatible with the current machine).readBlock(device,index): Returns anArrayBufferof size expected from the results ofgetDeviceInfo(). Thedeviceandindexarguments must also apply to one of the available devices.writeBlock(device,index,buffer): Overwrites the contents of a storage block with the given buffer.resetBlockNotifications(): Allowsread-blockandwrite-blockevents for blocks that were reported previously.applyStimulus(obj): Applies an emulator-specific stimulus object (e.g. representing human user input) immediately. If precise timing is required, that timing information should be described within the object itself.
main-loop: Dispatched once per iteration of something like the emulator's main loop (typically a few hundred times per second).frame: Dispatched once per graphical animation frame (typically about 60 frames per second).- TODO: document the
detailsobject expected, assume it is emulator-specific for now
- TODO: document the
read-block: Dispatched before the first read to a block on a device since the last call toresetBlockNotifications(). The handler for this event may callreadBlockto capture the state of this block.{"device": ..., "index": ...}details device id and block index
write-block: Like theread-blockevent, but dispatched before the first write to a block. To recover the data written, callreadBlocklater (e.g. on the nextframeevent)stimulus: Dispatched upon occurence of an interesting environmental stimulus event (e.g. user input) that pushes the emulated system onto a different trajectory than if the event had not happened. UseapplyStimuluswith these objects to approximately replay user interactions.- Do not assume that replaying stimulus events will faithfully recreate a trajectory. Use a sequence of residues to capture that trajectory compactly