- 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).
7.9 KiB
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": "..."}).
{"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.
{"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
ureplaces the whole state: a button stays pressed until auwithout 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
{"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
claimmust 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 sendsuevery second. - Closing the connection releases its pads.
leave (v2): the guest player leaves the game
{"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)
{"id": 4, "method": "status", "params": []}
The reply is the status object.
info
{"id": 5, "method": "info", "params": []}
{"id": 5, "result": {"version": "v1.0.0", "protocol": 2, "pads": 4}}
Version 1 servers only reply with version.
notify
{"method": "notify", "params": ["Hello from the phone"]}
Shows a notification on the PS4 with this text.
settings (v2)
{"id": 6, "method": "settings", "params": []}
Replies with the settings in effect and the console's users:
{
"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)
{"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": {...}}.savedis 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.
- a name that isn't 3 to 16 characters or contains
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
{"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 |