mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Document and wrap the PPSSPP "emulator:" devctls (API for homebrew)
This commit is contained in:
1 parent
0e2dfad02d
commit
0dbb79bcd9
3 files changed
+226
No files matched your search
@@ -0,0 +1,25 @@
|
||||
# PPSSPP Emulator API
|
||||
|
||||
`ppsspp_emu_api.h` is a small, header-only C wrapper around the emulator-only tricks PPSSPP exposes
|
||||
to homebrew through the `"emulator:"` device of `sceIoDevctl` (implemented in `Core/HLE/sceIo.cpp`,
|
||||
search for `"emulator:"`).
|
||||
|
||||
None of this works on real PSP hardware - always check `ppsspp_is_emulator()` before relying on
|
||||
anything else in the header, and keep a working fallback path for real hardware.
|
||||
|
||||
C API documentation: https://www.ppsspp.org/docs/development/ppsspp-internals/emu-api/
|
||||
|
||||
Raw `sceIoDevctl` protocol (commands, layouts, quirks): see [protocol.md](protocol.md).
|
||||
|
||||
## Usage
|
||||
|
||||
Just copy `ppsspp_emu_api.h` into a PSPSDK-based homebrew project and `#include` it. It's entirely
|
||||
`static inline` functions over `sceIoDevctl`, so there's no library to build or link.
|
||||
|
||||
```c
|
||||
#include "ppsspp_emu_api.h"
|
||||
|
||||
if (ppsspp_is_emulator()) {
|
||||
ppsspp_send_output_str("Hello from homebrew, running under PPSSPP!\n");
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,125 @@
|
||||
// PPSSPP emulator API - header-only C wrapper.
|
||||
//
|
||||
// PPSSPP exposes a handful of emulator-only tricks to homebrew through the
|
||||
// "emulator:"/"kemulator:" pseudo-devices of sceIoDevctl. None of this exists
|
||||
// on real hardware - always check ppsspp_is_emulator() before depending on
|
||||
// anything else here, and keep a working fallback path for real PSPs.
|
||||
//
|
||||
// C API docs: https://www.ppsspp.org/docs/development/ppsspp-internals/emu-api/
|
||||
//
|
||||
// Raw sceIoDevctl protocol this wraps (cmd numbers, layouts, quirks): see protocol.md
|
||||
// in this same directory, or Core/HLE/sceIo.cpp (search for "emulator:") in the main
|
||||
// PPSSPP source tree, which is the source of truth.
|
||||
//
|
||||
// Usage: just #include this file in a PSPSDK-based homebrew project. It's
|
||||
// entirely static inline functions over sceIoDevctl, so there's nothing to
|
||||
// link.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pspiofilemgr.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum PPSSPPEmulatorDevctlCmd {
|
||||
PPSSPP_DEVCTL__GET_HAS_DISPLAY = 1,
|
||||
PPSSPP_DEVCTL__SEND_OUTPUT = 2,
|
||||
PPSSPP_DEVCTL__IS_EMULATOR = 3,
|
||||
PPSSPP_DEVCTL__VERIFY_STATE = 4,
|
||||
|
||||
PPSSPP_DEVCTL__EMIT_SCREENSHOT = 0x20,
|
||||
|
||||
PPSSPP_DEVCTL__TOGGLE_FASTFORWARD = 0x30,
|
||||
PPSSPP_DEVCTL__GET_ASPECT_RATIO = 0x31,
|
||||
PPSSPP_DEVCTL__GET_SCALE = 0x32,
|
||||
PPSSPP_DEVCTL__GET_AXIS = 0x33,
|
||||
PPSSPP_DEVCTL__GET_VKEY = 0x34,
|
||||
};
|
||||
|
||||
// Either name works - PPSSPP currently treats them identically.
|
||||
#define PPSSPP_EMULATOR_DEVICE "emulator:"
|
||||
|
||||
// Returns non-zero if running under PPSSPP. Call this before trusting any
|
||||
// of the other functions below - on real hardware, or on an emulator that
|
||||
// doesn't implement this API, sceIoDevctl will simply fail here.
|
||||
static inline int ppsspp_is_emulator(void) {
|
||||
return sceIoDevctl(PPSSPP_EMULATOR_DEVICE, PPSSPP_DEVCTL__IS_EMULATOR, NULL, 0, NULL, 0) == 0;
|
||||
}
|
||||
|
||||
// Returns non-zero if there's an actual display to render to (i.e. not
|
||||
// running under PPSSPPHeadless).
|
||||
static inline int ppsspp_has_display(void) {
|
||||
unsigned int hasDisplay = 0;
|
||||
sceIoDevctl(PPSSPP_EMULATOR_DEVICE, PPSSPP_DEVCTL__GET_HAS_DISPLAY, NULL, 0, &hasDisplay, sizeof(hasDisplay));
|
||||
return (int)hasDisplay;
|
||||
}
|
||||
|
||||
// Sends a block of text straight to PPSSPP's debug/log output.
|
||||
static inline void ppsspp_send_output(const char *data, int len) {
|
||||
sceIoDevctl(PPSSPP_EMULATOR_DEVICE, PPSSPP_DEVCTL__SEND_OUTPUT, (void *)data, len, NULL, 0);
|
||||
}
|
||||
|
||||
// Convenience wrapper for ppsspp_send_output() over a NUL-terminated string.
|
||||
static inline void ppsspp_send_output_str(const char *str) {
|
||||
ppsspp_send_output(str, (int)strlen(str));
|
||||
}
|
||||
|
||||
// Asks PPSSPP to do an internal savestate round-trip as a consistency check.
|
||||
// Asynchronous - the pass/fail result is only logged on the PPSSPP side, not
|
||||
// reported back here. Mainly useful when testing the emulator itself.
|
||||
static inline void ppsspp_verify_state(void) {
|
||||
sceIoDevctl(PPSSPP_EMULATOR_DEVICE, PPSSPP_DEVCTL__VERIFY_STATE, NULL, 0, NULL, 0);
|
||||
}
|
||||
|
||||
// Delivers the current framebuffer through PPSSPP's internal debug-screenshot
|
||||
// hook (used by the pspautotests/frametest infrastructure). This does not
|
||||
// write a file to the memory stick - it's not a general screenshot feature.
|
||||
static inline void ppsspp_emit_screenshot(void) {
|
||||
sceIoDevctl(PPSSPP_EMULATOR_DEVICE, PPSSPP_DEVCTL__EMIT_SCREENSHOT, NULL, 0, NULL, 0);
|
||||
}
|
||||
|
||||
// Turns PPSSPP's fast-forward mode on or off.
|
||||
static inline void ppsspp_toggle_fastforward(int enable) {
|
||||
sceIoDevctl(PPSSPP_EMULATOR_DEVICE, PPSSPP_DEVCTL__TOGGLE_FASTFORWARD, enable ? (void *)1 : NULL, 0, NULL, 0);
|
||||
}
|
||||
|
||||
// Current display aspect ratio. Only correct in landscape orientation.
|
||||
static inline float ppsspp_get_aspect_ratio(void) {
|
||||
float ar = 0.0f;
|
||||
sceIoDevctl(PPSSPP_EMULATOR_DEVICE, PPSSPP_DEVCTL__GET_ASPECT_RATIO, NULL, 0, &ar, sizeof(ar));
|
||||
return ar;
|
||||
}
|
||||
|
||||
// Current display scale factor. Only correct in landscape orientation.
|
||||
static inline float ppsspp_get_scale(void) {
|
||||
float scale = 0.0f;
|
||||
sceIoDevctl(PPSSPP_EMULATOR_DEVICE, PPSSPP_DEVCTL__GET_SCALE, NULL, 0, &scale, sizeof(scale));
|
||||
return scale;
|
||||
}
|
||||
|
||||
// Reads an analog axis value injected by a PPSSPP-side input plugin, by
|
||||
// JOYSTICK_AXIS_* index (see PPSSPP's Common/Input/KeyCodes.h). This is NOT
|
||||
// normal controller input - use sceCtrl* for that. Returns 0 if no plugin
|
||||
// has set this axis.
|
||||
static inline float ppsspp_get_axis(int axisIndex) {
|
||||
float value = 0.0f;
|
||||
sceIoDevctl(PPSSPP_EMULATOR_DEVICE, PPSSPP_DEVCTL__GET_AXIS, (void *)(long)axisIndex, 0, &value, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
// Reads whether a virtual key injected by a PPSSPP-side input plugin is
|
||||
// currently pressed, by NKCODE_* value (see PPSSPP's Common/Input/KeyCodes.h,
|
||||
// codes mostly mirror Android's). This is NOT normal controller input - use
|
||||
// sceCtrl* for that. Returns 0 if no plugin has set this key.
|
||||
static inline unsigned char ppsspp_get_vkey(int keyCode) {
|
||||
unsigned char value = 0;
|
||||
sceIoDevctl(PPSSPP_EMULATOR_DEVICE, PPSSPP_DEVCTL__GET_VKEY, (void *)(long)keyCode, 0, &value, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
@@ -0,0 +1,76 @@
|
||||
# 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`](ppsspp_emu_api.h) instead (see the
|
||||
[README](README.md) and the [website docs](https://www.ppsspp.org/docs/development/ppsspp-internals/emu-api/)
|
||||
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`](https://github.com/hrydgard/ppsspp/blob/master/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:
|
||||
|
||||
```c
|
||||
int sceIoDevctl(const char *devicename, unsigned int cmd, void *indata, int inlen, void *outdata, int outlen);
|
||||
```
|
||||
|
||||
* `devicename` - `"emulator:"` or `"kemulator:"`.
|
||||
* `cmd` - one of the `EMULATOR_DEVCTL__*` values below.
|
||||
* `indata`/`inlen` - input block, meaning depends on `cmd`. Some commands instead just check whether `indata` is NULL/non-NULL as a boolean flag.
|
||||
* `outdata`/`outlen` - output block, meaning depends on `cmd`. Most commands write a single `u32` or `float` here.
|
||||
|
||||
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_AXIS` and `GET_VKEY`, the "input" isn't the `indata` buffer contents - it's the `indata` pointer value itself, used directly as an integer index. This matches how the current PPSSPP implementation reads it (`argAddr` is 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_AXIS` and `GET_VKEY` don'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 internal `NKCODE_*` 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
|
||||
|
||||
```c
|
||||
#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);
|
||||
}
|
||||
```
|
||||
Reference in new issue
Block a user