Files
MoHadiShibli ecd2d018f1 Docs and project files for RemotePad NG
- README: about the fork, features, quick start, the original project's
  issues this fixes, other fixes, screenshots, credits and license.
- CHANGELOG for 1.0.0, docs/protocol.md for client authors,
  CONTRIBUTING.md, SECURITY.md, issue templates and Ko-fi funding link.
- CI: read-only by default; pushing a version tag publishes a release
  with the plugin attached (release action pinned to a commit).
2026-10-02 23:38:43 +03:00

210 lines
7.9 KiB
Markdown

# RemotePad NG WebSocket protocol
This page is for people writing their own RemotePad NG client: an app, a PSP homebrew, a script. The web page in
`client/index.html` is the reference client.
The plugin runs one server on port **4263**, and only while a game is running. A plain HTTP `GET` returns the
web page. A WebSocket upgrade on any path opens a connection: `ws://<ps4-ip>:4263`. Messages are JSON-RPC style
text frames.
This describes protocol version **2**. Version 1 clients (the original web page) keep working: they only use
`u`, `info` and `notify`.
## Who may connect
Browsers send an `Origin` header with every WebSocket handshake, and the server uses it:
| Client | May connect | May use `settings.set` and `notify` |
|---|---|---|
| No `Origin` (apps, scripts, homebrew) | yes | yes |
| The console's own page, opened by IP address (`Origin` matches `Host`) | yes | yes |
| The console's page reached through a domain name | yes | no (403) |
| A page saved as a file (`Origin: null`) | yes | no (403) |
| A page from any other site | no (HTTP 403) | |
The server accepts at most 16 connections at once, and at most 64 KB of buffered data per connection.
## Requests
A request has a `method` and `params`. Add an `id` to get a reply. A reply carries the same `id` and either a
`result` or an `error` (`{"code": 400, "message": "..."}`).
```json
{"id": 1, "method": "claim", "params": [0]}
{"id": 1, "result": { ...status object... }}
```
### `u`: controller state
Sends the full state of one pad. There is no reply, even with an `id`.
```json
{"method": "u", "params": [pad, buttons, lx, ly, rx, ry, l2, r2, touches, id1, x1, y1, id2, x2, y2]}
```
| Param | Range | Meaning |
|---|---|---|
| `pad` | 0-3 | Pad 1-4 |
| `buttons` | bit mask | See the table below. Other bits are ignored. |
| `lx`, `ly`, `rx`, `ry` | 0-255 | Stick positions; 128 is the center, 0 is left or up |
| `l2`, `r2` | 0-255 | Analog triggers |
| `touches` | 0-2 | Fingers on the touchpad; each one adds `id, x, y` |
| `id` | 0-127 | Finger ID, the same while the finger stays down |
| `x`, `y` | 0-1919, 0-941 | Finger position on the touchpad |
| Button | Bit | Button | Bit |
|---|---|---|---|
| L3 | `0x0002` | R3 | `0x0004` |
| OPTIONS | `0x0008` | Up | `0x0010` |
| Right | `0x0020` | Down | `0x0040` |
| Left | `0x0080` | L2 | `0x0100` |
| R2 | `0x0200` | L1 | `0x0400` |
| R1 | `0x0800` | Triangle | `0x1000` |
| Circle | `0x2000` | Cross | `0x4000` |
| Square | `0x8000` | Touchpad click | `0x100000` |
How to send:
- **Send on change, plus at least once a second.** Each `u` replaces the whole state: a button stays pressed
until a `u` without it arrives.
- **Hold short presses.** The game reads its controllers once per frame, so keep a press for at least about
50 ms or the game can miss it.
- **Keep the stream small.** Samples that only move sticks or triggers replace the newest unread one in the
plugin. Presses, releases and touches are queued in order, so a game never misses one.
Sending `u` for a pad also claims it for this connection, as if it had sent `claim`.
### `claim` (v2): which pads this connection controls
```json
{"id": 2, "method": "claim", "params": [0, 2]}
```
The params are the exact set of pads this connection controls; `[]` means none. The reply is the status
object.
- A pad's first device makes its guest player join the game.
- The game sees a pad's controller as connected only while a device controls it, unless `always_connected=1`.
- Pads dropped from the set are released: their buttons let go.
- Connections that use `claim` must send something at least every 3 seconds, or the plugin releases their
pads. A phone that goes to sleep with a button held is the typical case. The web page sends `u` every second.
- Closing the connection releases its pads.
### `leave` (v2): the guest player leaves the game
```json
{"id": 3, "method": "leave", "params": [2]}
```
Pad 3's guest signs out of the game, and this connection stops controlling that pad. Error 409 if another
device still uses the pad. Pads that play as a real PS4 user don't sign out. The reply is the status object.
### `status` (v2)
```json
{"id": 4, "method": "status", "params": []}
```
The reply is the status object.
### `info`
```json
{"id": 5, "method": "info", "params": []}
{"id": 5, "result": {"version": "v1.0.0", "protocol": 2, "pads": 4}}
```
Version 1 servers only reply with `version`.
### `notify`
```json
{"method": "notify", "params": ["Hello from the phone"]}
```
Shows a notification on the PS4 with this text.
### `settings` (v2)
```json
{"id": 6, "method": "settings", "params": []}
```
Replies with the settings in effect and the console's users:
```json
{
"title": "CUSA00001", "locked": false, "gameOnly": false,
"joinOnStart": false, "alwaysConnected": false,
"pads": [{"enabled": true, "id": "1A2B3C4D", "share": false, "name": "Alex"}, ...],
"users": [{"id": "1A2B3C4D", "name": "Alex", "signedIn": true}, ...]
}
```
| Field | Meaning |
|---|---|
| `title` | The running game's title ID |
| `locked` | `lock_settings=1`: `settings.set` is refused |
| `gameOnly` | The settings come from the game's own section of `remote_pad.ini` |
| `pads[].id` | The pad's PS4 user in hex, `""` for a guest |
| `pads[].name` | The pad's name, `""` for the default |
| `users` | The registered users if the system lets a game list them, and at least the signed-in ones |
### `settings.set` (v2)
```json
{"id": 7, "method": "settings.set", "params": {
"gameOnly": false, "joinOnStart": false, "alwaysConnected": false,
"pads": [{"enabled": true, "id": "0x1A2B3C4D", "share": true, "name": "Alex"}, ...]}}
```
Applies the settings right away and saves `remote_pad.ini`: for all games, or with `gameOnly` for the running
game only. Leaving a field out keeps its value.
- Send IDs with `0x`. Without it, an ID made only of digits is read as decimal.
- The reply is `{"saved": true, "settings": {...}}`. `saved` is false when the file couldn't be written.
- Error 400, with a message, for:
- a name that isn't 3 to 16 characters or contains `;`;
- an ID that isn't a user ID;
- two pads with the same user;
- locked settings.
## Notifications from the server
Messages without an `id`:
| Method | Params | When |
|---|---|---|
| `s` (v2) | status object | Pads or players changed: sent to every connection |
| `v` | `[pad, large, small]` | The game set vibration (0-255 per motor); `[pad, 0, 0]` stops it |
| `l` | `[pad, r, g, b]` | The game set the light bar color |
| `rl` | `[pad, r, g, b]` | The game reset the light bar; the color is the player color (v1 sent `[pad]`) |
Vibration only goes to connections that control that pad, and to version 1 connections that haven't sent input
yet.
## Status object
```json
{"version": "v1.0.0", "protocol": 2, "pads": [
{"pad": 0, "open": true, "enabled": true, "real": false, "logged": true, "joined": true,
"connected": true, "full": false, "shared": false, "player": 2, "uid": "20000000", "cfg": "",
"name": "Remote1", "color": [255, 48, 64], "clients": 1}, ...]}
```
| Field | Meaning |
|---|---|
| `open` | The game opened this pad (or the shared real controller) |
| `enabled` | The pad is on (`userN_enabled`) |
| `real` | The pad plays as a real PS4 user (configured and signed in) |
| `logged` | The game sees the player: a real user, or a guest that got a player slot |
| `joined` | The player wants to play: picked, or from the start with `join_on_start` |
| `connected` | What the game is told about the controller |
| `full` | A guest wants to play but every player slot is taken |
| `shared` | "Use with the DualShock": the pad adds to the user's real controller |
| `player` | Player number, 0 while the game doesn't see the player |
| `uid` | The user ID the game sees, in hex |
| `cfg` | The configured PS4 user in hex, `""` when none (it may be signed out, see `real`) |
| `name` | The player's name |
| `color` | The light bar color the game set, otherwise the player color |
| `clients` | How many connections control the pad |