6.2 KiB
Emulator API - raw protocol
This documents the raw sceIoDevctl-based protocol behind PPSSPP's emulator API. Most homebrew
should just use the C wrapper in ppsspp_emu_api.h instead (see the
README and the website docs
for that) - read this page if you're extending the wrapper, writing a binding for another language,
or just curious how it works under the hood.
Important: None of this will work on the real PSP! Always check IS_EMULATOR (see below) before
relying on any of it, and keep a working fallback path for real hardware.
How it works
The whole API is exposed through a single, existing PSP syscall: sceIoDevctl. PPSSPP recognizes
two special, fake device names that don't correspond to any real device:
"emulator:""kemulator:"
Both names are currently handled identically (there's no user/kernel distinction enforced), so
either works from a user-mode homebrew app. The implementation lives in sceIoDevctl() in
Core/HLE/sceIo.cpp - search
for "emulator:" if you want to see exactly what each command does, or if you're adding a new one.
The standard sceIoDevctl signature is used, just with PPSSPP-specific cmd numbers and
argument/output block layouts:
int sceIoDevctl(const char *devicename, unsigned int cmd, void *indata, int inlen, void *outdata, int outlen);
devicename-"emulator:"or"kemulator:".cmd- one of theEMULATOR_DEVCTL__*values below.indata/inlen- input block, meaning depends oncmd. Some commands instead just check whetherindatais NULL/non-NULL as a boolean flag.outdata/outlen- output block, meaning depends oncmd. Most commands write a singleu32orfloathere.
If cmd doesn't match any known command, sceIoDevctl returns an error (UNKNOWN PARAMETERS)
rather than crashing, so probing for support is safe.
Commands
| Command | Value | Direction | Purpose |
|---|---|---|---|
EMULATOR_DEVCTL__GET_HAS_DISPLAY |
1 | out: u32 |
Writes 1 if there's a real display (normal PPSSPP), 0 if running headless (PPSSPPHeadless). Useful to skip presentation/vblank-dependent work when there's nothing to show. |
EMULATOR_DEVCTL__SEND_OUTPUT |
2 | in: bytes | Sends a raw block of text straight to PPSSPP's debug/log output (and to headless's collected output buffer, if any). Handy for logging from homebrew without going through sceIoWrite to a real file. |
EMULATOR_DEVCTL__IS_EMULATOR |
3 | out: u32 |
Writes 1. This is the one to call first: if the sceIoDevctl call itself fails, you're not running under PPSSPP (or a build that doesn't implement this API), and none of the rest of this page applies. |
EMULATOR_DEVCTL__VERIFY_STATE |
4 | none | Asks PPSSPP to do an internal savestate round-trip (save to memory, then verify it reads back correctly) as a consistency check. Runs asynchronously - it doesn't report the pass/fail result back to your code, it just gets logged on the PPSSPP side. Mainly useful for automated testing of the emulator itself. |
EMULATOR_DEVCTL__EMIT_SCREENSHOT |
0x20 | none | Grabs the current framebuffer and delivers it through PPSSPP's internal debug-screenshot hook, which is used by things like the pspautotests/frametest infrastructure to collect result images. Not useful as a general "save a screenshot to memstick" feature - it doesn't write a file. |
EMULATOR_DEVCTL__TOGGLE_FASTFORWARD |
0x30 | in: bool (NULL/non-NULL) | Turns PPSSPP's fast-forward mode on (non-NULL indata) or off (NULL). |
EMULATOR_DEVCTL__GET_ASPECT_RATIO |
0x31 | out: float |
Writes the display's current aspect ratio. Only correct in landscape orientation right now. |
EMULATOR_DEVCTL__GET_SCALE |
0x32 | out: float |
Writes the current display scale factor. Only correct in landscape orientation right now. |
EMULATOR_DEVCTL__GET_AXIS |
0x33 | in: axis index (as indata value, not a pointer), out: float |
Reads an analog axis value that a PPSSPP-side input plugin has injected (see below), by JOYSTICK_AXIS_* index. |
EMULATOR_DEVCTL__GET_VKEY |
0x34 | in: key code (as indata value, not a pointer), out: u8 |
Reads whether a virtual key that a PPSSPP-side input plugin has injected is currently pressed, by PPSSPP's internal key code (see below). |
A couple of notes on quirks that are easy to trip over:
- For
GET_AXISandGET_VKEY, the "input" isn't theindatabuffer contents - it's theindatapointer value itself, used directly as an integer index. This matches how the current PPSSPP implementation reads it (argAddris compared against the axis/key range and used directly), so pass the index as if it were a pointer, e.g.sceIoDevctl("emulator:", EMULATOR_DEVCTL__GET_AXIS, (void *)JOYSTICK_AXIS_X, 0, &value, sizeof(value)). GET_AXISandGET_VKEYdon't read normal controller input (sceCtrl*already does that) - they read state from PPSSPP's HLE plugin system, i.e. values that a native PPSSPP-side plugin PRX has explicitly set for your homebrew to pick up. If no plugin is active, expect these to just come back as 0/unpressed.- The axis index matches PPSSPP's internal
JOYSTICK_AXIS_*enum (Common/Input/KeyCodes.h), and the key code matches PPSSPP's internalNKCODE_*enum (same file), not any PSP SDK enum. These mostly mirror Android's key/axis codes. A handful of the more useful ones:NKCODE_DPAD_UP/DOWN/LEFT/RIGHT,NKCODE_BUTTON_CROSS/CIRCLE/SQUARE/TRIANGLE,JOYSTICK_AXIS_X/Y. See the header for the full list if you need something more obscure. - You'll sometimes see an
EMULATOR_DEVCTL__SEND_CTRLDATA(0x10) constant referenced in older test code. It is not currently implemented by PPSSPP - calling it just gets you the generic "unknown parameters" error. Don't rely on it.
Raw example
#include <pspiofilemgr.h>
#define EMULATOR_DEVCTL__IS_EMULATOR 3
#define EMULATOR_DEVCTL__SEND_OUTPUT 2
int runningOnPPSSPP = sceIoDevctl("emulator:", EMULATOR_DEVCTL__IS_EMULATOR, NULL, 0, NULL, 0) == 0;
if (runningOnPPSSPP) {
const char *msg = "Hello from homebrew, running under PPSSPP!\n";
sceIoDevctl("emulator:", EMULATOR_DEVCTL__SEND_OUTPUT, (void *)msg, strlen(msg), NULL, 0);
}