mirror of
https://github.com/Pharaoh2k/ps5debug-NG.git
synced 2026-10-23 04:12:42 -05:00
Update PROTOCOL.md and README for the verified-write semantics, the per-entry write-multi status codes, the 0xFF adjacent-byte behavior, and the v1.3.2 / capability 1.1 / build-fingerprint branding.
1363 lines
85 KiB
Markdown
1363 lines
85 KiB
Markdown
# ps5debug-NG - Wire Protocol Reference
|
|
|
|
Developer-facing reference for every command exposed by the debugger payload.
|
|
The opcodes, struct layouts, sizes, and behaviours below are taken directly from
|
|
the sources under `debugger/source/`, `common/`, and the client
|
|
`Cheater-NG/CheaterNG/PS4DBG.cs`, with file + function citations. Where a server
|
|
value and the on-the-wire value differ (see the bit-swap note in 1.6), both are
|
|
given.
|
|
|
|
This document reflects `ps5debug-NG v1.3.2` (`common/include/version.h`,
|
|
`PS5DEBUG_NG_VERSION_STR`). The on-wire **protocol** version reported by
|
|
`CMD_VERSION` is a separate string, currently `"1.3"`.
|
|
|
|
---
|
|
|
|
## 1. Wire protocol fundamentals
|
|
|
|
### 1.1 Core constants
|
|
|
|
| Symbol | Value | Source |
|
|
|--------------------|----------------------------------|----------------------------------------------------------|
|
|
| protocol version | `"1.3"` | `meta.c` `handle_version` (local `char ver[]="1.3"`) |
|
|
| branding string | `"ps5debug-NG by OSR v1.3.2 [<fingerprint>]<NUL>1.1"` | `version.h` `PS5DEBUG_NG_BRAND_STR` via `meta.c` `handle_branding` (NUL-separated capability level appended - see 2.1) |
|
|
| `PACKET_MAGIC` | `0xFFAABBCC` | `main.c:50` (local `#define`) |
|
|
| broadcast magic | `0xFFFFAAAA` | `main.c` `broadcast_thread` (raw literal) |
|
|
| auth magic | `0xBB40E64D` | `protocol.h:249` (`CMD_PROC_AUTH_MAGIC`) |
|
|
|
|
None of these are defined in `protocol.h` except the auth magic. There is no
|
|
`server.h` / `server.c` in this tree; the framing, dispatch, and discovery code
|
|
lives in `main.c` and `meta.c`.
|
|
|
|
### 1.2 Ports
|
|
|
|
| Port | Protocol | Role | Source |
|
|
|------|----------|----------------------------------------------------------|-------------------------------------|
|
|
| 744 | TCP | Main command server | `main.c` `start_server` (`SERVER_PORT`) |
|
|
| 755 | TCP | Async debug-interrupt channel (server connects out to client) | `debug.c` `connect_debugger` (`htons(755)`) |
|
|
| 3232 | TCP | Kernel-log forwarder (server listens; streams `/dev/klog`) | `console.c` `klog_server_thread` (`sin_port=0xA00C`) |
|
|
| 1010 | UDP | Discovery broadcast | `main.c` `broadcast_thread` (port `0x3F2`) |
|
|
|
|
### 1.3 Byte order
|
|
|
|
All multi-byte integers are **little-endian** (native x86-64 layout).
|
|
`__attribute__((packed))` is used on every wire struct, so there is no implicit
|
|
padding between fields. (Exception: status words are bit-swapped on the wire,
|
|
see 1.6.)
|
|
|
|
### 1.4 Packet framing
|
|
|
|
Every request on port 744 begins with a fixed 12-byte header. `void *data` in the
|
|
C struct is NOT on the wire - it is a server-side pointer assigned after the
|
|
body is received (`protocol.h:26-31`, `main.c` `handle_client`):
|
|
|
|
```
|
|
struct cmd_packet { // 12 bytes on the wire
|
|
uint32_t magic; // must equal PACKET_MAGIC (0xFFAABBCC)
|
|
uint32_t cmd; // command ID (see 4)
|
|
uint32_t datalen; // length of request body that follows the header
|
|
void *data; // NOT transmitted; server-side pointer only
|
|
};
|
|
#define CMD_PACKET_SIZE 12
|
|
```
|
|
|
|
Framing loop (`main.c` `handle_client`, lines 70-155):
|
|
|
|
1. Each connection is serviced on its own thread. The thread `select()`s the
|
|
socket and blocking-reads 12 bytes into a `cmd_packet` header.
|
|
2. Validates `magic == PACKET_MAGIC`; on mismatch the packet is silently dropped
|
|
and the loop continues (no error response).
|
|
3. If `datalen != 0`: when `datalen > 0x100000` (1 MiB) the server sends the raw
|
|
status literal `0xF0000002` (which is `CMD_ERROR` on the wire, see 1.6) and
|
|
continues; otherwise it `malloc(datalen)`s a buffer and reads exactly
|
|
`datalen` bytes into `packet.data`. (It uses `malloc`, not `net_alloc_buffer`.)
|
|
4. If the command is in the debug namespace (`(cmd & 0xFFFF0000) == 0xBDBB0000`),
|
|
it is dispatched while holding both `g_proc_rw_mutex` and `g_server_mutex`.
|
|
5. `cmd_handler` dispatches to the namespace handler. A handler may read
|
|
*additional* trailing data from the socket beyond the fixed request struct
|
|
(writes, ELF images, register blobs, scan payloads), and several handlers
|
|
reply with an intermediate `CMD_SUCCESS` *before* the data phase and a final
|
|
status *after* it (see 8).
|
|
|
|
### 1.5 Dispatch routing (`meta.c` `cmd_handler`, lines 54-73)
|
|
|
|
`cmd_handler` inlines five infrequent commands, then routes everything else by
|
|
testing the command's **middle byte** (bits 16-23) against the `VALID_*_CMD`
|
|
macros:
|
|
|
|
| Namespace | Predicate | Dispatch target | File |
|
|
|----------------|---------------------|------------------|-------------|
|
|
| Info/ping | n/a (inlined) | `cmd_handler` | `meta.c` (5 cases) |
|
|
| Process | `VALID_PROC_CMD` | `proc_handle` | `proc.c` |
|
|
| Debug | `VALID_DEBUG_CMD` | `debug_handle` | `debug.c` |
|
|
| Kernel R/W | `VALID_KERN_CMD` | `kern_handle` | `kern.c` |
|
|
| Console | `VALID_CONSOLE_CMD` | `console_handle` | `console.c` |
|
|
|
|
`VALID_CMD(cmd)` first requires the top byte to be `0xBD`
|
|
(`protocol.h:20`); a command failing that returns immediately. `debug_handle` is
|
|
a weak symbol and is only called if linked in.
|
|
|
|
**Attach special case:** in `main.c handle_client` (lines 112-115), when
|
|
`cmd == CMD_DEBUG_ATTACH` and no session is active (`g_debug_attached == 0`), the
|
|
server records the current client slot into the globals `curdbgcli` /
|
|
`curdbgctx` before dispatching. The attach itself is then handled normally by
|
|
`debug_attach_handle` (see 2.3). There is no separate `_svc` handler.
|
|
|
|
### 1.6 Status codes and the bit-swap
|
|
|
|
The server transmits every `uint32_t` status word through `net_send_int32`
|
|
(`net.c:39`), which bit-swaps adjacent even/odd bit positions:
|
|
|
|
```c
|
|
static inline uint32_t bitswap32(uint32_t x) {
|
|
return ((x >> 1) & 0x55555555u) | ((x << 1) & 0xAAAAAAAAu); // involution
|
|
}
|
|
```
|
|
|
|
So the **server-side macro value** (`protocol.h:9-12`, `CMD_INVALID_INDEX` at
|
|
`:271`) and the **on-the-wire value** a client compares against differ. The swap is applied **only** to status
|
|
words; response payloads, length prefixes, struct bodies, and raw memory are
|
|
sent unswapped. Clients do **not** un-swap - they compare the raw wire word
|
|
directly to the post-swap constants (`PS4DBG.cs` `CMD_STATUS` + `ReceiveStatus`).
|
|
|
|
| Name | Server macro (`protocol.h`) | On-the-wire value (clients compare this) |
|
|
|---------------------|-----------------------------|------------------------------------------|
|
|
| `CMD_SUCCESS` | `0x40000000` | `0x80000000` |
|
|
| `CMD_ERROR` | `0xF0000002` | `0xF0000001` |
|
|
| `CMD_DATA_NULL` | `0xF0000003` | `0xF0000003` |
|
|
| `CMD_ALREADY_DEBUG` | `0xF0000008` | `0xF0000004` |
|
|
| `CMD_INVALID_INDEX` | `0xF000000A` | `0xF0000005` |
|
|
|
|
Notes:
|
|
- There is **no** `CMD_TOO_MUCH_DATA` macro and **no** `CMD_FATAL_STATUS` macro
|
|
in this tree. The over-size guard (1.4 step 3) sends the literal `0xF0000002`,
|
|
which is `CMD_ERROR` on the wire (`0xF0000001`). The client's
|
|
`CMD_TOO_MUCH_DATA = 0xF0000002` enum value is therefore never actually
|
|
received.
|
|
- The auth magic uses the same swap: the server checks
|
|
`bitswap32(packet magic) == CMD_PROC_AUTH_MAGIC_BSWAP (0x7780D98E)`, i.e. the
|
|
client sends the raw `0xBB40E64D`.
|
|
|
|
### 1.7 Discovery (`main.c` `broadcast_thread`, lines 174-218)
|
|
|
|
A UDP server on port 1010 receives 4-byte datagrams. If the payload equals
|
|
`0xFFFFAAAA`, the server echoes the same 4 bytes back to the sender. No other
|
|
payload is acted on; there is no authentication on this channel. (`PS4DBG.cs`
|
|
`FindPlayStation` broadcasts the magic and matches the echo.)
|
|
|
|
### 1.8 Authentication (`auth.c` `proc_auth_handle`)
|
|
|
|
A single **global** flag word `g_proc_auth_state` (`auth.c:13`, default
|
|
`0x14000`) gates the *stateful* scan trio. Bit 1 (`0x02`) is the scan-enabled
|
|
bit; `CMD_PROC_SCAN_START` / `_COUNT` / `_GET` check `g_proc_auth_state & 2` on
|
|
entry (`scan.c:351`, `:475`, `:620`). The legacy/AOB scans
|
|
(`CMD_PROC_SCAN`, `CMD_PROC_SCAN_AOB`, `CMD_PROC_SCAN_AOB_MULTI`) do **not**
|
|
check it. The flag is global and is **not** reset on disconnect.
|
|
|
|
`CMD_PROC_AUTH` (`0xBDAACCFF`) handshake:
|
|
|
|
1. Client sends `struct cmd_proc_auth_packet` (`protocol.h:251`):
|
|
`uint32_t magic` (raw `0xBB40E64D`) + `uint32_t flags`.
|
|
2. Server validates the magic (1.6) and replies `CMD_SUCCESS`.
|
|
3. Server sends `uint16_t challenge_length = 64`, then 64 challenge bytes
|
|
(derived from an internal xorshift-128 generator).
|
|
4. Client XORs the challenge against a fixed keystream (xorshift-128 seeded with
|
|
`200,300,400,500`; see `PS4DBG.cs` `GenerateKeystream`) and sends 64 response
|
|
bytes back.
|
|
5. On a byte-exact match the server sets the flag word - `flags & 1` ->
|
|
`0x14001`, `flags & 2` -> `0x14002` (scan-enabled) - and replies
|
|
`CMD_SUCCESS`; otherwise `CMD_DATA_NULL`. (`PS4DBG.cs` `ValidateProcess`
|
|
passes `flags = maxLength`, whose bit 1 is set, enabling scans.)
|
|
|
|
### 1.9 Asynchronous interrupt channel (port 755)
|
|
|
|
On `CMD_DEBUG_ATTACH`, the server opens a TCP connection *outbound* to the
|
|
client's IP on port 755 (`debug.c` `connect_debugger`, `htons(755)`). The client
|
|
must be listening on 755 before attaching (`PS4DBG.cs` `DebuggerThread` binds
|
|
`0.0.0.0:755`).
|
|
|
|
Breakpoint/watchpoint hits and single-step completions are delivered as
|
|
fixed-size 1184-byte (`0x4A0`) packets. There is **no** named struct or size
|
|
macro in the server source for this - the packet is assembled by hand at fixed
|
|
offsets in `debug.c` `dispatch_debug_events` (lines 1189-1240). The layout is:
|
|
|
|
```
|
|
offset size field
|
|
0x000 4 uint32_t lwpid
|
|
0x004 4 uint32_t status // raw wait4 status word
|
|
0x008 40 char tdname[40] // ~24 bytes copied from PT_LWPINFO pl_tdname
|
|
0x030 176 struct reg reg64 // FreeBSD amd64 GP registers
|
|
0x0E0 832 struct fpu savefpu // FPU + YMM
|
|
0x420 128 struct dbreg dbreg64 // 16 x uint64_t
|
|
// total = 0x4A0 = 1184
|
|
```
|
|
|
|
The matching client struct is `PS4DBG.cs` `DebuggerInterruptPacket`
|
|
(`tdname[40]`, `regs` 176, `fpregs` 832, `dbregs` 128). `savefpu` lands at offset
|
|
`0xE0` (224); the struct is packed so it is not 64-byte aligned, which is
|
|
intentional.
|
|
|
|
---
|
|
|
|
## 2. Command reference
|
|
|
|
69 opcodes have handlers. Only a handful are given `CMD_*` macros in
|
|
`protocol.h` (`CMD_VERSION`, `CMD_FW_VERSION`, `CMD_BRANDING`, `CMD_PLATFORM_ID`,
|
|
`CMD_PROC_NOP`, `CMD_DEBUG_ATTACH`, `CMD_DEBUG_SET_BREAKPOINT`,
|
|
`CMD_DEBUG_SET_WATCHPOINT`); the rest are bare hex literals inside the
|
|
per-namespace `switch` statements. Three opcodes have no symbolic name at all:
|
|
`0xBDAA000D`, `0xBDAA0024`, `0xBDDD0006`.
|
|
|
|
### 2.1 Info & ping - inlined in `cmd_handler` (`meta.c:60-64`)
|
|
|
|
#### `CMD_VERSION = 0xBD000001`
|
|
- **Request body:** none.
|
|
- **Response:** `uint32_t length`, then `length` bytes of the protocol version
|
|
string ("1.3"). No status word precedes it.
|
|
|
|
#### `CMD_FW_VERSION = 0xBD000500`
|
|
- **Request body:** none.
|
|
- **Response:** `uint16_t` firmware as a **decimal** number (the BCD high word of
|
|
`kernel_get_fw_version()` converted digit-by-digit), e.g. `900` for 9.00,
|
|
`1240` for 12.40, `505` for 5.05. No status word precedes it.
|
|
|
|
#### `CMD_BRANDING = 0xBD000501`
|
|
- **Request body:** none.
|
|
- **Response:** `uint32_t length`, then `length` bytes: the human branding string
|
|
(`"ps5debug-NG by OSR v1.3.2 [<16-hex build fingerprint>]"`), a single `NUL`,
|
|
then a **capability level**
|
|
string (`"1.1"`), with no trailing NUL. No status word precedes it. (The client
|
|
calls this `CMD_EXT_VERSION`.)
|
|
- **Capability level:** C-string clients read up to the first `NUL` and see the
|
|
human brand plus fingerprint; capability-aware clients read the bytes past it to
|
|
get the level. The level is bumped as the server gains negotiable features (so a
|
|
client can gate a feature on the server advertising at least a given level).
|
|
Capability `1.0` introduced write-multi and Turbo Scan; `1.1` adds
|
|
content-verified process writes. Built from
|
|
`PS5DEBUG_NG_BRAND_STR "\0" "1.1"` in `meta.c` `handle_branding`.
|
|
|
|
#### `CMD_PLATFORM_ID = 0xBD000502`
|
|
- **Request body:** none.
|
|
- **Response:** `uint16_t`, hardcoded `5`. No status word precedes it. (Present
|
|
in the server only; the CheaterNG client does not invoke it.)
|
|
|
|
#### `CMD_PROC_NOP = 0xBDAACC06`
|
|
- **Request body:** none.
|
|
- **Response:** `CMD_SUCCESS`. Keepalive / liveness probe (client `Ping`).
|
|
|
|
---
|
|
|
|
### 2.2 Process commands - dispatched by `proc_handle` (`proc.c:958-993`)
|
|
|
|
Unless noted, the request body is read into `packet->data` up front; "trailing
|
|
data" is read from the socket by the handler after a leading `CMD_SUCCESS`.
|
|
|
|
#### `CMD_PROC_LIST = 0xBDAA0001` (`proc.c:364`)
|
|
- **Request body:** none.
|
|
- **Response:** `CMD_SUCCESS`, `uint32_t num` (4 bytes), then `num` x
|
|
`struct proc_list_entry { char name[32]; int32_t pid; }` (36 bytes each,
|
|
`protocol.h:43`). The list is built by walking the kernel `allproc` chain.
|
|
|
|
#### `CMD_PROC_READ = 0xBDAA0002` (`proc.c:440`)
|
|
- **Request body:** `struct cmd_proc_read_packet` (16 bytes).
|
|
- **Response:** `CMD_SUCCESS`, then up to `length` bytes in 64 KiB (`0x10000`)
|
|
chunks.
|
|
|
|
#### `CMD_PROC_WRITE = 0xBDAA0003` (`proc.c:563`)
|
|
- **Request body:** `struct cmd_proc_write_packet` (16 bytes).
|
|
- **Response:** `CMD_SUCCESS` (before the data phase), then the server reads
|
|
`length` bytes in 64 KiB chunks, then `CMD_SUCCESS` only if every chunk was
|
|
written successfully; otherwise the second status is `CMD_ERROR`. **Two**
|
|
status words (see 8). DMAP and mdbg writes are read back before acceptance;
|
|
a proven DMAP mismatch never falls through to mdbg. A transfer starting at
|
|
an address whose low byte is `0xFF` preserves the preceding byte and starts
|
|
at `...FE`. This is an adjacent-byte read-modify-write: callers need a stable
|
|
or suspended target when that preceding byte can mutate concurrently.
|
|
|
|
#### `0xBDAACC04` (`proc_write_multi_handle`)
|
|
Bulk write - the write counterpart to `0xBDAACC03` (bulk read). Collapses a
|
|
freeze / multi-poke loop of N single `CMD_PROC_WRITE`s into one exchange. Dispatched
|
|
by `proc_handle`; **not** auth-gated (mirrors single write).
|
|
- **Request body:** `struct cmd_proc_write_multi_packet` (12 bytes,
|
|
`{ uint32_t pid; uint32_t count; uint32_t flags; }`). `flags` bit 0
|
|
(`PROC_WRITE_MULTI_F_STATUS`) requests a per-entry status array in the response.
|
|
- **Trailing data:** after the server's first `CMD_SUCCESS`, the client streams
|
|
`count` entries, each `{ uint64_t address; uint32_t length; <length> bytes data }`
|
|
concatenated. Per-entry `length` is capped at `0x100000`; `count` at `0xFFFF`.
|
|
- **Response:** `CMD_SUCCESS` (ack), then the server reads and applies each entry
|
|
(writing via the same DMAP / mdbg path as `CMD_PROC_WRITE`, in 64 KiB chunks).
|
|
If `flags` bit 0 was set, it then sends a `count`-byte status array (one byte per
|
|
entry: `0` = ok; `1` = invalid; `2` = DMAP failure; `3` = content verification
|
|
failure; `4` = mdbg failure). It then sends a trailing `CMD_SUCCESS`. **Two**
|
|
status words, plus the optional status array between them.
|
|
- **Semantics:** best-effort and non-atomic - entries are applied in order and a
|
|
failed entry does not stop the rest. Without the status flag, any failed entry
|
|
makes the trailing status `CMD_ERROR`. A `count`/`length` cap violation makes
|
|
the server reply `CMD_ERROR` and abort the command (the connection's stream is
|
|
then untrustworthy; well-behaved clients stay within the caps).
|
|
|
|
#### `CMD_PROC_MAPS = 0xBDAA0004` (`proc.c:597`)
|
|
- **Request body:** `struct cmd_proc_maps_packet` (4 bytes, `pid`).
|
|
- **Response:** `CMD_SUCCESS`, `uint32_t num`, then `num` x
|
|
`struct proc_vm_map_entry` (**58 bytes** each, `protocol.h:154`):
|
|
`{ char name[32]; uint64_t start; uint64_t end; uint64_t offset; uint16_t prot; }`.
|
|
The map may be augmented by a kernel page-table walk (`proc_ptwalk_augment`)
|
|
before sending.
|
|
|
|
#### `CMD_PROC_INTALL = 0xBDAA0005` (`proc.c:648`)
|
|
Literal spelling `INTALL` (typo preserved on the wire).
|
|
- **Request body:** `struct cmd_proc_install_packet` (4 bytes, `pid`).
|
|
- **Response:** `CMD_SUCCESS`, then `uint64_t rpcstub = 0`.
|
|
- **Note:** in this build the handler is a **stub** - it returns `rpcstub = 0`
|
|
and performs no injection.
|
|
|
|
#### `CMD_PROC_CALL = 0xBDAA0006` (`proc.c:659`)
|
|
- **Request body:** `struct cmd_proc_call_packet` (68 bytes):
|
|
`uint32_t pid; uint64_t rpcstub, rpc_rip, rpc_rdi, rpc_rsi, rpc_rdx, rpc_rcx, rpc_r8, rpc_r9;`
|
|
- **Response:** `CMD_SUCCESS`, then `struct cmd_proc_call_response`
|
|
`{ uint32_t pid; uint64_t rpc_rax; }` (12 bytes), or `CMD_ERROR` if the
|
|
remote call could not be run.
|
|
|
|
#### `CMD_PROC_ELF = 0xBDAA0007` (`proc.c:691`)
|
|
- **Request body:** `struct cmd_proc_elf_packet` `{ uint32_t pid; uint32_t length; }` (8 bytes).
|
|
- **Response:** `CMD_SUCCESS` (before data), reads `length` bytes of ELF, then
|
|
`CMD_SUCCESS` or `CMD_ERROR`. **Two** status words.
|
|
|
|
#### `CMD_PROC_PROTECT = 0xBDAA0008` (`proc.c:769`)
|
|
- **Request body:** `struct cmd_proc_protect_packet` (20 bytes, defined in
|
|
`proc.c:762`): `{ uint32_t pid; uint64_t address; uint32_t length; uint32_t prot; }`.
|
|
- **Response:** `CMD_SUCCESS` or `CMD_ERROR` (single status).
|
|
|
|
#### `CMD_PROC_SCAN = 0xBDAA0009` (`scan.c:82`)
|
|
Legacy single-pass scan. **No auth required.**
|
|
- **Request body:** `struct cmd_proc_scan_packet` (10 bytes):
|
|
`{ uint32_t pid; uint8_t valueType; uint8_t compareType; uint32_t lenData; }`.
|
|
- **Trailing data:** `lenData` bytes of comparison value(s).
|
|
- **Response:** `CMD_SUCCESS` (ack), the server reads the value, scans, sends
|
|
`CMD_SUCCESS` again, then **streams** matching addresses as `uint64_t`s
|
|
terminated by an all-`0xFF` sentinel `0xFFFFFFFFFFFFFFFF`. No count prefix.
|
|
(Compare via `scan_compare.c` `proc_scan_legacy_compareValues`.)
|
|
|
|
#### `CMD_PROC_INFO = 0xBDAA000A` (`proc.c:790`)
|
|
- **Request body:** `struct cmd_proc_info_packet` (4 bytes, `pid`).
|
|
- **Response:** `CMD_SUCCESS`, `struct cmd_proc_info_response` (**188 bytes**,
|
|
`protocol.h:259`): `{ uint32_t pid; char name[40]; char path[64]; char titleid[16]; char contentid[64]; }`.
|
|
`titleid`/`contentid` are resolved by `sceKernelGetAppInfo` plus a pattern scan
|
|
of the proc struct, falling back to per-firmware offsets
|
|
(`proc_field_offsets.c`).
|
|
|
|
#### `CMD_PROC_ALLOC = 0xBDAA000B` (`proc.c:853`)
|
|
- **Request body:** `struct cmd_proc_alloc_packet` (8 bytes, `{ pid, length }`).
|
|
- **Response:** `CMD_SUCCESS`, `struct cmd_proc_alloc_response` `{ uint64_t address; }` (8 bytes),
|
|
or `CMD_ERROR` on failure.
|
|
|
|
#### `CMD_PROC_FREE = 0xBDAA000C` (`proc.c:880`)
|
|
- **Request body:** `struct cmd_proc_free_packet` (16 bytes, `{ pid, address, length }`).
|
|
- **Response:** `CMD_SUCCESS`, or `CMD_ERROR` if the allocation could not be released.
|
|
|
|
> **Alloc arena (default ON).** A plain `CMD_PROC_ALLOC` is served from a server-side
|
|
> per-pid 16 MB arena: one hijacking `mmap` per segment, then zero-hijack sub-allocation
|
|
> (with `CMD_PROC_FREE` recycling the slice into a free-list). This avoids the thread-hijack
|
|
> crash hazard that pathological concurrent allocation otherwise triggers. It is fully
|
|
> transparent (same wire, valid target addresses); a freed sub-block is recycled rather than
|
|
> `munmap`'d, and the segment is reclaimed when the game exits. `CMD_PROC_ALLOC_HINTED`
|
|
> (which needs a specific address) bypasses the arena and allocates directly. The arena can
|
|
> be toggled at runtime via the raw literal `0xBDAACC24` (`proc_arena_handle`): request body
|
|
> `uint32_t` (`1` = enable, `0` = disable), response `CMD_SUCCESS` + `uint32_t len` + a text
|
|
> status line.
|
|
>
|
|
> **Debugger ownership gate.** Allocation, free, and call operations that require a remote
|
|
> ptrace RPC are rejected with `CMD_ERROR` while the same target is stopped at a debugger
|
|
> event (or is in another non-running debugger transition). The server does not continue or
|
|
> consume the client-owned event. Clients should continue or detach the debugger and retry.
|
|
|
|
#### `0xBDAA000D` - first-map probe (`proc.c:901`, `proc_unknown_d_handle`)
|
|
Raw literal, no `CMD_*` macro.
|
|
- **Request body:** `struct cmd_proc_unknown_d_packet` (4 bytes, `pid`).
|
|
- **Response:** `CMD_SUCCESS` then `uint64_t` = the `start` address of the
|
|
target's first VM map entry, or `CMD_ERROR` if the map could not be read.
|
|
|
|
#### `CMD_PROC_ALLOC_HINTED = 0xBDAA000E` (`proc.c:929`)
|
|
- **Request body:** `struct cmd_proc_alloc_hinted_packet` (16 bytes, `{ pid, hint, length }`).
|
|
- **Response:** `CMD_SUCCESS` + `struct cmd_proc_alloc_response` on success; on
|
|
failure it sends `CMD_ERROR` **and still** the (zeroed/partial) response struct.
|
|
|
|
#### `CMD_PROC_ELF_RPC = 0xBDAA0010` (`proc.c:724`)
|
|
Like `CMD_PROC_ELF` but returns the entry address instead of jumping into it.
|
|
- **Request body:** `struct cmd_proc_elf_rpc_packet` (8 bytes, `{ pid, length }`).
|
|
- **Response:** `CMD_SUCCESS` (before data), reads `length` ELF bytes, then
|
|
`CMD_SUCCESS` + `struct cmd_proc_elf_rpc_response` `{ uint64_t entry; }` (8
|
|
bytes). **Two** status words.
|
|
|
|
#### `CMD_PROC_DISASM_REGION = 0xBDAA0020` (`proc.c:197`)
|
|
- **Request body:** `struct cmd_proc_disasm_packet` (20 bytes, `{ pid, address, length, max_entries }`).
|
|
- **Validation:** `max_entries` must be `1 .. 1000000`, else `CMD_ERROR`.
|
|
- **Response:** `CMD_SUCCESS`, then a stream of `struct disasm_instr_entry` (32
|
|
bytes each), terminated by a 32-byte sentinel with all bytes `0xFF`.
|
|
- **Backend:** Zydis (`third_party/zydis/Zydis.c`).
|
|
|
|
`disasm_instr_entry` (`protocol.h:235`):
|
|
```
|
|
struct disasm_instr_entry { // 32 bytes
|
|
uint64_t addr; // instruction start
|
|
uint64_t rip_rel_target; // resolved absolute addr if RIP-relative; else 0
|
|
int64_t mem_disp; // [base + index*scale + disp]
|
|
uint8_t length;
|
|
uint8_t kind; // bitmask below
|
|
uint8_t mem_base_reg; // ZydisRegister low byte; 0 if none
|
|
uint8_t mem_index_reg;
|
|
uint8_t mem_scale; // 1/2/4/8; 0 if none
|
|
uint8_t mnemonic_lo; // ZydisMnemonic low byte
|
|
uint16_t pad;
|
|
};
|
|
```
|
|
|
|
Kind bitmask (`proc.c` `disasm_fill_entry`):
|
|
| Bit | Meaning |
|
|
|------|------------------------------------|
|
|
| 0x01 | `CALL` |
|
|
| 0x02 | `RET` |
|
|
| 0x04 | Unconditional `JMP` |
|
|
| 0x08 | Conditional branch |
|
|
| 0x10 | Has a memory operand |
|
|
| 0x20 | Memory operand is RIP-relative |
|
|
| 0x40 | Memory read |
|
|
| 0x80 | Memory write |
|
|
|
|
#### `CMD_PROC_EXTRACT_CODE_XREFS = 0xBDAA0021` (`proc.c:262`)
|
|
- **Request body:** `struct cmd_proc_disasm_packet` (20 bytes); if `max_entries`
|
|
is 0 a default of 10000000 is used.
|
|
- **Response:** `CMD_SUCCESS`, then a **stream** of `uint64_t` RIP-relative
|
|
target addresses (one per RIP-relative memory operand encountered; not
|
|
de-duplicated by the server), terminated by a `0xFFFFFFFFFFFFFFFF` sentinel.
|
|
No count prefix.
|
|
|
|
#### `CMD_PROC_FIND_XREFS_TO = 0xBDAA0022` (`proc.c:325`)
|
|
- **Request body:** `struct cmd_proc_xrefs_to_packet` (24 bytes):
|
|
`{ uint32_t pid; uint64_t scan_address; uint32_t scan_length; uint64_t target_address; }`.
|
|
- **Response:** `CMD_SUCCESS`, then a **stream** of `uint64_t` instruction
|
|
addresses whose resolved RIP-relative target equals `target_address`,
|
|
terminated by a `0xFFFFFFFFFFFFFFFF` sentinel. No count prefix, no kind byte.
|
|
|
|
#### `CMD_PROC_READ_STACK = 0xBDAA0023` (`proc.c:485`)
|
|
Server-side RBP-chain walk: the server walks the frame chain and bundles each
|
|
frame's saved RBP, return address, frame-local bytes, and a code window around
|
|
the return address into one response.
|
|
- **Request body:** `struct cmd_proc_read_stack_packet` (24 bytes):
|
|
`{ uint32_t pid; uint64_t rbp; uint64_t rsp; uint32_t depth; }`
|
|
- `rbp`/`rsp` describe the local (top) frame; `depth` caps frames returned.
|
|
- **Response:** `CMD_SUCCESS`, `uint32_t bundle_len`, then `bundle_len` bytes:
|
|
```
|
|
u32 n_frames
|
|
per frame {
|
|
u64 rbp; u64 rsp; u64 saved_rbp; u64 ret_addr;
|
|
u32 flags; // bit0: frame-locals omitted (oversized/invalid)
|
|
u32 frame_locals_len;
|
|
u32 code_len; // bytes of code at (ret_addr - 10); 0 if unavailable
|
|
u8 frame_locals[frame_locals_len];
|
|
u8 code[code_len];
|
|
}
|
|
```
|
|
- Caps (`protocol.h:89-93`): `MAX_DEPTH = 64`, `LOCALS_CAP = 0x1000` per frame,
|
|
`CODE_OFF = 10`, `CODE_LEN = 200`.
|
|
|
|
#### `0xBDAA0024` - assemble x86-64 (`assemble.c` `proc_assemble_handle`)
|
|
Raw literal, no `CMD_*` macro. Assembles x86-64 text into machine bytes using the
|
|
embedded Keystone (LLVM-MC) assembler. Pure userspace; no attached process
|
|
required.
|
|
- **Request body** (in `packet->data`; `datalen` must be `>= 12`): a 12-byte
|
|
header `struct cmd_proc_assemble_packet` `{ uint64_t base_addr; uint32_t ks_opt_syntax; }`
|
|
followed by `(datalen - 12)` bytes of asm text (NUL-termination not required).
|
|
- `base_addr` is what Keystone resolves PC-relative operands against.
|
|
- `ks_opt_syntax = 0` keeps the engine default (Intel); a non-zero value is
|
|
passed straight to `ks_option(KS_OPT_SYNTAX, ...)`.
|
|
- **Response on success:** `CMD_SUCCESS`, `struct cmd_proc_assemble_ok`
|
|
`{ uint32_t byte_len; uint32_t insn_count; }` (8 bytes), then `byte_len`
|
|
machine-code bytes.
|
|
- **Response on assembler error:** `CMD_ERROR`, `struct cmd_proc_assemble_err`
|
|
`{ uint32_t ks_errno; uint32_t msg_len; }` (8 bytes), then `msg_len` bytes of
|
|
the `ks_strerror` message.
|
|
|
|
#### `CMD_PROC_SCAN_AOB = 0xBDAA0501` (`scan.c:151`)
|
|
Array-of-bytes single scan. **No auth required.**
|
|
- **Request body:** `struct cmd_proc_scan_aob_packet` (22 bytes):
|
|
`{ uint32_t pid; uint64_t address; uint32_t length; uint8_t max_matches; uint8_t stop_flag; uint32_t pattern_length; }`.
|
|
- **Trailing data:** `pattern_length` pattern bytes, then `pattern_length` mask
|
|
bytes (mask byte `1` = must match; any other value = wildcard).
|
|
- **Response:** `CMD_SUCCESS` (ack), reads pattern+mask, `CMD_SUCCESS` again,
|
|
scans, then sends a single `uint64_t` = the `max_matches`-th match address
|
|
(or `0` if not found, or if `stop_flag == 1` and the match is not unique),
|
|
then `CMD_SUCCESS`.
|
|
|
|
#### `CMD_PROC_SCAN_AOB_MULTI = 0xBDAA0502` (`scan.c:237`)
|
|
Multi-pattern AOB scan. **No auth required.** (Client name: `CMD_PROC_SCAN_AOB_ALL`.)
|
|
- **Request body:** `struct cmd_proc_scan_aob_multi_packet` (21 bytes):
|
|
`{ uint32_t pid; uint64_t address; uint32_t length; uint8_t stop_flag; uint32_t patterns_length; }`.
|
|
- **Trailing data:** `patterns_length` bytes of concatenated patterns; each
|
|
pattern is `[uint8_t target_count][uint32_t pattern_length][pattern_length pattern bytes][pattern_length mask bytes]`.
|
|
- **Response:** `CMD_SUCCESS` (ack), reads the blob, `CMD_SUCCESS` again, scans,
|
|
then sends `pat_count` x `uint64_t` (one match address per pattern, `0` if
|
|
none / invalidated), then `CMD_SUCCESS`. (The client supplies `pat_count` and
|
|
reads exactly that many `uint64_t`s.)
|
|
|
|
#### `CMD_PROC_AUTH = 0xBDAACCFF` (`auth.c:60`)
|
|
See 1.8.
|
|
|
|
#### `CMD_PROC_SCAN_START = 0xBDAACC01` (`scan.c:350`) - **requires auth bit 1**
|
|
First pass of the staged scanner. (Client name: `CMD_PROC_SCAN_INIT`.)
|
|
- **Request body:** `struct cmd_proc_scan_start_packet` (23 bytes):
|
|
`{ uint32_t pid; uint64_t address; uint32_t length; uint8_t valueType; uint8_t compareType; uint8_t alignment; uint32_t lenData; }`.
|
|
- **Trailing data:** the seed value(s) when the compare type needs them, plus a
|
|
mask when `valueType == 10` (arrBytes).
|
|
- **Response:** `CMD_SUCCESS` (ack), reads value/mask, `CMD_SUCCESS` again, then
|
|
**streams** result blocks - each block is `uint64_t block_len` followed by
|
|
`block_len` bytes of `(uint32_t offset, value)` entries (`offset` is relative
|
|
to `address`) - terminated by a `0xFFFFFFFFFFFFFFFF` sentinel, then a final
|
|
`CMD_SUCCESS`. The result set lives **on the client**; the server keeps no
|
|
per-pid session.
|
|
|
|
#### `CMD_PROC_SCAN_COUNT = 0xBDAACC02` (`scan.c:474`) - **requires auth bit 1**
|
|
Filtering pass: the client streams its current candidate list back and the
|
|
server returns the survivors. (Client name: `CMD_PROC_SCAN_NEXT`.)
|
|
- **Request body:** `struct cmd_proc_scan_count_packet` (18 bytes):
|
|
`{ uint32_t pid; uint64_t base_address; uint8_t valueType; uint8_t compareType; uint32_t lenData; }`.
|
|
- **Trailing data:** value/mask as for `SCAN_START`, then a client-driven loop of
|
|
chunks: `uint32_t chunk_len` + `chunk_len` bytes of `(uint32_t offset[, prev_value])`
|
|
entries. `chunk_len == 0xFFFFFFFF` ends the loop.
|
|
- **Response:** for each chunk the server re-reads memory, re-compares, and sends
|
|
surviving `(offset, value)` entries as `uint64_t block_len`-prefixed blocks
|
|
followed by a per-chunk `0xFFFFFFFFFFFFFFFF` sentinel. After the loop ends it
|
|
sends a final `CMD_SUCCESS`.
|
|
|
|
#### `CMD_PROC_SCAN_GET = 0xBDAACC03` (`scan.c:619`) - **requires auth bit 1**
|
|
Bulk memory fetch for a candidate list. (Client name: `CMD_PROC_READ_MULTI`.)
|
|
- **Request body:** `struct cmd_proc_scan_get_packet` (8 bytes, `{ pid, count }`).
|
|
- **Trailing data:** `count` x `{ uint64_t address; uint32_t length; }` (12 bytes
|
|
each).
|
|
- **Response:** `CMD_SUCCESS`, then the raw memory bytes for each entry
|
|
concatenated in order, then a `0xFFFFFFFFFFFFFFFF` sentinel. (Returns raw
|
|
memory, not addresses.)
|
|
|
|
#### TURBOSCAN family - `0xBDAACC10`-`0xBDAACC17` (`scan_turbo.c`)
|
|
|
|
Additive turbo-scan path (v1.3.0). The legacy (`0xBDAA0009`), AOB
|
|
(`0501`/`0502`), and iterative (`CC01`/`02`/`03`) scans are unchanged; a client
|
|
detects TURBOSCAN via `CMD_PROC_TURBOSCAN_CAPS` and falls back to the iterative
|
|
trio when it (or a specific engine bit) is absent. Result blocks reuse the
|
|
iterative `(offset, value)` shape so clients reuse one parser. IDs, engine bits,
|
|
and request flags are in `protocol.h:22-37`.
|
|
|
|
**Engine bits** (`CAPS` `engines` field):
|
|
|
|
| Bit | Name | Meaning |
|
|
|--------|-----------------------|-------------------------------------------------|
|
|
| `0x01` | `TSE_SIMD_COMPARE` | typed AVX2 (256-bit) comparator |
|
|
| `0x02` | `TSE_ALIASING` | physical-page aliasing read engine (opt-in) |
|
|
| `0x04` | `TSE_SERVER_RESIDENT` | server-side survivor sets |
|
|
| `0x08` | `TSE_SNAPSHOT` | unknown-initial-value bitmap + snapshot scans |
|
|
| `0x10` | `TSE_SNAPSHOT_SEGMENTS` | trailing segment list of disjoint regions in one session (snapshot or resident `START`) |
|
|
| `0x20` | `TSE_SNAPSHOT_CONFIG` | `CMD_PROC_TURBOSCAN_CONFIG` supported (RAM threshold + spill dir) |
|
|
| `0x40` | `TSE_SNAPSHOT_FIRST` | `TS_SNAPSHOT_KEEP_FIRST` + CC13 GET returns the first-scan value (3-value records) |
|
|
| `0x80` | `TSE_SNAPSHOT_PREVIOUS` | `TS_SNAPSHOT_KEEP_PREVIOUS`: CC13 GET `previous` carries the prior-scan value (not the last-scan value) |
|
|
| `0x100` | `TSE_PARALLEL_COMPARE` | `TS_PARALLEL_COMPARE` supported (server-side multi-thread for the aliased SIMD exact-match streaming scan) |
|
|
| `0x200` | `TSE_RESCAN_ALIASING` | `TS_RESCAN_ALIASING` supported (CC12 rescan reads full-size survivor windows via the aliasing engine) |
|
|
| `0x400` | `TSE_FLOAT_POLICY` | server applies the negotiated Simple/exact float policy during START, snapshot seed, and all COUNT paths |
|
|
| `0x800` | `TSE_COMPACT_SIMPLE_SNAPSHOT` | a float/double snapshot seeded with `TS_FLOAT_SIMPLE` filters before storage and keeps survivor records only |
|
|
|
|
**Per-request flags** (`START` / `COUNT` `flags` field):
|
|
|
|
| Bit | Name | Meaning |
|
|
|--------|----------------------|--------------------------------------------------------|
|
|
| `0x01` | `TS_USE_ALIASING` | use the aliasing read engine for this scan (default 0) |
|
|
| `0x02` | `TS_SERVER_RESIDENT` | keep / rescan the survivor set server-side |
|
|
| `0x04` | `TS_SNAPSHOT` | unknown-initial-value snapshot scan (`START` only) |
|
|
| `0x08` | `TS_SNAPSHOT_INCLUDE_ZEROS` | snapshot: seed all-zero slots too - **default drops them** (`START` only) |
|
|
| `0x10` | `TS_SNAPSHOT_SEGMENTS` | scan covers a trailing segment list of disjoint regions, not `[address, length)` (`START` with `TS_SNAPSHOT` or `TS_SERVER_RESIDENT`) |
|
|
| `0x20` | `TS_SNAPSHOT_KEEP_FIRST` | retain an immutable first-scan value store so CC13 GET can return `first` (`START` + `TS_SNAPSHOT` only) |
|
|
| `0x40` | `TS_SNAPSHOT_KEEP_PREVIOUS` | retain a prior-scan value store so CC13 GET `previous` = value at the previous scan, not the just-matched one (`START` + `TS_SNAPSHOT` only) |
|
|
| `0x80` | `TS_PARALLEL_COMPARE` | split the scan across worker threads server-side - `START` + `TS_USE_ALIASING`, exact-match (compareType 0) streaming only. For **single-connection** clients; a multi-connection client should parallelize by opening more connections instead (the server threads per connection), and must **not** also set this (the two compose to over-subscription, which is slower). Ignored on any non-qualifying scan. |
|
|
| `0x100` | `TS_RESCAN_ALIASING` | `COUNT` rescans read full-size (gap-bridged, contiguous) survivor windows via the aliasing read engine instead of mdbg; tiny scattered windows and any alias failure fall back to mdbg (the floor). ~2-3x faster on dense/moderate-density rescans (the win grows as survivors stay dense; past ~32KB inter-survivor gaps windows fragment and it stays mdbg). Applies to all three CC12 rescan paths (snapshot-resident, list-resident, client-driven). The survivor set is **per-connection**, so this is single-connection by nature - a multi-connection client must **not** enable it on many connections at once (over-subscribes the aliasing setup, same caveat as `TS_PARALLEL_COMPARE`). |
|
|
| `0x200` | `TS_FLOAT_SIMPLE` | float/double only: retain positive zero or values whose IEEE exponent is within the packed threshold of 1.0's exponent; applies to every compare type and snapshot seeding |
|
|
| `0x400` | `TS_FLOAT_EXACT` | exact-value float/double only: use numeric IEEE equality (`+0 == -0`, NaN unequal) instead of the legacy fuzzy comparator |
|
|
|
|
When `TS_FLOAT_SIMPLE` is set, bits 16-22 carry a 7-bit exponent-distance
|
|
threshold (`TS_FLOAT_EXPONENT_MASK = 0x007F0000`, shift 16). Values 1-127 are
|
|
literal; zero defensively means the default 11. Clients must only send these
|
|
policy fields when CAPS advertises `TSE_FLOAT_POLICY`. Servers ignore the
|
|
unused high field when Simple is clear.
|
|
|
|
When CAPS also advertises `TSE_COMPACT_SIMPLE_SNAPSHOT`, snapshot creation with
|
|
`TS_FLOAT_SIMPLE` filters each float/double before it is stored. The session keeps
|
|
one address-sorted record per survivor (`address`, current, and optional Previous/
|
|
First values), allocates no raw-slot bitmap, and compacts that record stream again
|
|
after every narrow. Initial RAM/file size and write I/O therefore scale with the
|
|
survivor count, not the raw slot count. Clients must not infer this storage contract
|
|
from `TSE_FLOAT_POLICY` alone: an older policy-capable server may still use dense
|
|
snapshot backing.
|
|
|
|
##### `CMD_PROC_TURBOSCAN_CAPS = 0xBDAACC10` (`proc_turboscan_caps_handle`)
|
|
Capability probe. **No auth required.**
|
|
- **Request body:** none.
|
|
- **Response:** `CMD_SUCCESS`, then `struct cmd_proc_turboscan_caps_response`
|
|
(16 bytes): `{ u32 version; u32 engines; u32 max_threads; u32 reserved; }`.
|
|
`version = 1`; `engines` is the OR of the supported `TSE_*` bits; `max_threads`
|
|
is the compare stage's worker count. An old server without TURBOSCAN never
|
|
answers this opcode (or replies `CMD_ERROR`) - the client's fallback signal.
|
|
|
|
##### `CMD_PROC_TURBOSCAN_START = 0xBDAACC11` (`proc_turboscan_start_handle`) - **requires auth bit 1**
|
|
First scan; mirrors `CMD_PROC_SCAN_START` plus a trailing `flags` field and the
|
|
optional resident / snapshot / aliasing modes.
|
|
- **Request body:** `struct cmd_proc_turboscan_start_packet` (27 bytes):
|
|
`{ u32 pid; u64 address; u32 length; u8 valueType; u8 compareType; u8 alignment; u32 lenData; u32 flags; }`.
|
|
- **Trailing data:** the seed value(s) when the compare type needs them, plus a
|
|
mask when `valueType == 10` (as `SCAN_START`). `TS_SNAPSHOT` needs no seed. When
|
|
`TS_SNAPSHOT_SEGMENTS` is set (with either `TS_SNAPSHOT` or `TS_SERVER_RESIDENT`),
|
|
a segment list follows the value/mask (see the snapshot sub-protocol for the exact
|
|
wire format; the resident variant reuses it verbatim).
|
|
- **Response (default, or `TS_USE_ALIASING`):** `CMD_SUCCESS` (ack), reads
|
|
value/mask, `CMD_SUCCESS` again, then streams `(offset, value)` result blocks
|
|
(`u64 block_len`-prefixed) terminated by a `0xFFFFFFFFFFFFFFFF` sentinel, then a
|
|
final `CMD_SUCCESS` - the same shape as `SCAN_START`. `TS_USE_ALIASING` only
|
|
changes how the server **reads** target memory (in place vs copied); the wire
|
|
result is identical, and the engine silently falls back to the normal read path
|
|
on any page it cannot alias. `TS_PARALLEL_COMPARE` (with `TS_USE_ALIASING`, exact-match
|
|
streaming) additionally splits the scan across server worker threads - intended for
|
|
single-connection clients; multi-connection clients should instead open more connections
|
|
(the server threads per connection) and leave this flag clear. The wire result is identical
|
|
either way.
|
|
- **Response (`TS_SERVER_RESIDENT`):** after the two acks the server stores
|
|
survivors server-side and replies `struct cmd_proc_turboscan_resident_summary`
|
|
(12 bytes): `{ u32 resident_stored; u64 count; }`, streaming **no** result
|
|
blocks. If storage fails (overflow / too-wide value / mmap fail) it replies
|
|
`resident_stored = 0` and then streams the full result set the normal way
|
|
(transparent fallback). Closes with `CMD_SUCCESS`.
|
|
- **Response (`TS_SERVER_RESIDENT | TS_SNAPSHOT_SEGMENTS`, `TS_SNAPSHOT` clear):**
|
|
multi-segment resident. After the two acks the server reads the trailing segment
|
|
list (`u32 segment_count`, then `segment_count` x `cmd_proc_turboscan_snap_segment`,
|
|
the **same** wire format and `1 .. 1048576` bound as the snapshot sub-protocol; the
|
|
packet `address`/`length` are ignored), scans each disjoint segment, and appends all
|
|
matches into the one resident session buffer (`count` accumulates across segments).
|
|
Reply shape is unchanged: the 12-byte summary with `resident_stored = 1` and
|
|
`count` = total matches, then `CMD_SUCCESS`. Survivors are stored as absolute
|
|
`{ u64 addr; value }` records, so `CC12` narrow and `CC13` GET are unaffected by the
|
|
segmentation. **Decline (no stream fallback):** if storage overflows
|
|
`TS_RESIDENT_CAP`, the segment count is malformed, or an allocation fails, the server
|
|
replies `resident_stored = 0, count = 0` followed by an **empty** result stream (an
|
|
immediate `0xFFFFFFFFFFFFFFFF` sentinel, i.e. zero result blocks) and `CMD_SUCCESS` -
|
|
byte-identical framing to the single-range transparent fallback, just with no blocks.
|
|
A transparent server stream is **not** used here because a `u32` block offset cannot
|
|
carry the absolute addresses of `>4 GiB`-scattered segments; the client treats
|
|
`resident_stored = 0` on a segmented request as "server declined" and falls back to
|
|
its own per-section streaming.
|
|
- **Response (`TS_SNAPSHOT`):** (with `TS_SNAPSHOT_SEGMENTS`, reads the trailing
|
|
segment list first) streams the snapshot plan / progress / summary (see the
|
|
snapshot sub-protocol below), then `CMD_SUCCESS`. If `TS_SNAPSHOT_KEEP_FIRST` is set,
|
|
the server also writes an immutable **first-scan** value store at create (a spill-only
|
|
file, never updated by narrowing) so CC13 GET can return the `first` column; it is
|
|
needed for the count-only resident path where the client never saw the first-scan
|
|
per-result values (a large unknown-value scan). It roughly doubles the value-store
|
|
*disk* footprint (not RAM, since it is spill-only) and is freed when the session
|
|
materialises to a list or ends. On first-store creation failure the snapshot still
|
|
succeeds and GET falls back to the 2-value shape. If `TS_SNAPSHOT_KEEP_PREVIOUS` is set,
|
|
the server also keeps a **prior-scan** value store (another spill-only file, rewritten
|
|
each rescan with the baseline it held *before* that rescan), so CC13 GET's `previous`
|
|
reflects the value at the scan before the most recent one (matching console/async
|
|
semantics) instead of the just-matched value. Without it, `previous` is the last-scan
|
|
value store (~= `current`). It is the costliest of the three stores (written every
|
|
narrow); freed on materialise/end; degrades to the legacy `previous` on create failure.
|
|
|
|
##### `CMD_PROC_TURBOSCAN_COUNT = 0xBDAACC12` (`proc_turboscan_count_handle`) - **requires auth bit 1**
|
|
Rescan. Mirrors `CMD_PROC_SCAN_COUNT` (client-driven candidate chunks) unless
|
|
`TS_SERVER_RESIDENT` is set, in which case it rescans the server-resident set in
|
|
place (no candidate upload) and refreshes each survivor's baseline (so the next
|
|
rescan is "since last scan"). With `TS_RESCAN_ALIASING` set, full-size survivor
|
|
windows are read via the aliasing engine instead of mdbg (mdbg floor on small
|
|
windows / alias failure); wire format and results are unchanged.
|
|
- **Request body:** `struct cmd_proc_turboscan_count_packet` (22 bytes):
|
|
`{ u32 pid; u64 base_address; u8 valueType; u8 compareType; u32 lenData; u32 flags; }`.
|
|
- **Trailing data:** value/mask as for `START` when needed; then, in the
|
|
**client-driven** mode only, the same chunked candidate loop as `SCAN_COUNT`
|
|
(`u32 chunk_len` + entries; `0xFFFFFFFF` ends it).
|
|
- **Response (client-driven):** identical to `SCAN_COUNT` - per-chunk
|
|
`(offset, value)` blocks + per-chunk `0xFFFFFFFFFFFFFFFF` sentinel, then a final
|
|
`CMD_SUCCESS`.
|
|
- **Response (`TS_SERVER_RESIDENT`):** **mode-independent** - both a list session and a
|
|
snapshot session reply with the same shape: a **progress stream** - repeated
|
|
`u64 slots_scanned` records (cumulative bitmap-walk index, relative to the session's
|
|
`slot_count` from the create plan; throttled to <=256 records, evenly spaced) terminated
|
|
by an 8-byte `0xFFFFFFFFFFFFFFFF` sentinel - then the `u64 new_survivor_count`, then
|
|
`CMD_SUCCESS`. A snapshot session narrows the bitmap and streams real progress records;
|
|
a list session has no progress to stream and so sends an **immediate sentinel (zero
|
|
records)** before the count. The empty case for a snapshot (value-width mismatch) also
|
|
sends the sentinel with zero records. Because the wire shape is identical either way,
|
|
the client parses any resident narrow without knowing the session mode - which matters
|
|
because once a snapshot's survivors get sparse the server transparently materialises it
|
|
into a compact record list, flipping the session to list mode mid-stream.
|
|
|
|
##### `CMD_PROC_TURBOSCAN_GET = 0xBDAACC13` (`proc_turboscan_get_handle`) - **requires auth bit 1**
|
|
Fetch a window of the server-resident survivor set with live current values, plus the
|
|
stored last-scan (`previous`) value and - when the session retained it - the first-scan
|
|
(`first`) value.
|
|
- **Request body:** `struct cmd_proc_turboscan_get_packet` (12 bytes):
|
|
`{ u32 start_index; u32 count; u32 flags; }`.
|
|
- **Response:** `CMD_SUCCESS`, then `u32 hdr`, then `(hdr & 0x7FFFFFFF)` records, then
|
|
`CMD_SUCCESS`. The header's low 31 bits are `actual_count` (`<= count`); **bit 31** flags
|
|
the 3-value record shape so the client picks the stride before reading:
|
|
- **bit 31 clear:** `{ u64 addr; value current; value previous }` - `8 + 2*value_length`.
|
|
- **bit 31 set:** `{ u64 addr; value current; value previous; value first }` - `8 + 3*value_length`.
|
|
|
|
`current` is the **matched** value - the value the most recent scan read/matched (the
|
|
session value store), **not** a live re-read - so it agrees with the scan and matches every
|
|
other scan path (the client gets live values via its Refresh); `previous` is the **prior-scan**
|
|
value (the value at the scan before the most recent one) when the session was started with
|
|
`TS_SNAPSHOT_KEEP_PREVIOUS`,
|
|
otherwise the last-matched value (~= `current`, the legacy default); `first` is the value
|
|
at snapshot create and is present (bit 31 set) **only** for a session started with
|
|
`TS_SNAPSHOT_KEEP_FIRST` whose first store was created successfully (it degrades to the
|
|
2-value shape otherwise). The shape is uniform across single-range,
|
|
multi-segment, list-mode and snapshot-mode sessions. `valueType` / `value_length` come
|
|
from the session, not the request.
|
|
|
|
##### `CMD_PROC_TURBOSCAN_END = 0xBDAACC14` (`proc_turboscan_end_handle`) - **requires auth bit 1**
|
|
Free this connection's server-resident / snapshot session (also freed
|
|
automatically on disconnect).
|
|
- **Request body:** none.
|
|
- **Response:** `CMD_SUCCESS`.
|
|
|
|
##### `CMD_PROC_TURBOSCAN_CONFIG = 0xBDAACC15` (`proc_turboscan_config_handle`) - **requires auth bit 1**
|
|
Tune the snapshot value-store backing for subsequent snapshot scans (advertised via
|
|
`TSE_SNAPSHOT_CONFIG`). Lets the client trade RAM for speed and choose where a too-large
|
|
snapshot spills. Settings are global (snapshots run on a single connection) and apply to
|
|
the next `CC11 + TS_SNAPSHOT` create.
|
|
- **Request body:** `struct cmd_proc_turboscan_config_packet` (8 bytes):
|
|
`{ u32 ram_thresh_mb; u32 spill_path_len; }`, then `spill_path_len` bytes of a spill
|
|
**directory** path (no NUL, `<= 63` bytes).
|
|
- **Semantics:**
|
|
- `ram_thresh_mb` - how much of the value store is **fully committed to RAM** (anon
|
|
`mmap`). `0` resets to the **512 MiB** default. The cap is now a RAM-cache size, not an
|
|
all-or-nothing switch:
|
|
- store `<=` cap -> **pure anon RAM** (committed up front, fastest).
|
|
- store `>` cap -> **hybrid**: the first `cap` bytes (whole slots) are cached in anon
|
|
RAM and only the overflow spills to the file; the kernel page cache may keep
|
|
additional spill pages warm opportunistically, so RAM caching is no longer
|
|
all-or-nothing and a too-large scan never goes 100% to disk.
|
|
|
|
Raising it commits more of the console's shared memory while a game runs (a client-
|
|
chosen tradeoff); if the RAM commit/cache fails, the server degrades safely to the
|
|
pure-file path. (File-backed `mmap` is **not** used - faulting a `MAP_SHARED` file page
|
|
sleeps under a held VM lock and panics PS5 - so the spill is explicit `pread`/`pwrite`.)
|
|
- spill directory - where the spill file (`ps5dbg_snap_NN.bin`) is written when it
|
|
doesn't fit in RAM. Empty resets to `/data`. The internal user partition (`/data`,
|
|
`/user`) is encrypted and can be slow (HW-measured ~35-240 MB/s depending on console
|
|
state); an extended NVMe (`/mnt/ext1`) or USB (`/mnt/usb0`) is typically faster
|
|
(~240 / ~550 MB/s) when present. The spill write itself uses 16 MiB chunks.
|
|
- **Response:** `CMD_SUCCESS` (or `CMD_DATA_NULL` on a malformed body / `spill_path_len > 63`).
|
|
|
|
##### `0xBDAACC16` - region classify (`proc_turboscan_regions_handle`) - **requires auth bit 1**
|
|
Enumerate every **readable** region of the target with its cache attribute and a
|
|
**measured** read throughput, so the client can offer the user a per-region "exclude
|
|
uncached/slow" choice (e.g. the PS5 GPU/Garlic blob: a large region that is uncached
|
|
and reads ~40 MB/s, usually not worth scanning - but **the server never drops anything**,
|
|
exclusion is the client's opt-in, user-overridable decision; the default is to scan
|
|
everything). The opcode is deliberately a raw literal (no `CMD_*` macro) so the published
|
|
`CMD_*` set that some clients enumerate stays unchanged.
|
|
- **Request body:** `struct cmd_proc_turboscan_regions_packet` (16 bytes):
|
|
`{ u32 pid; u32 max; u32 probe_bytes; u32 reserved; }`.
|
|
- `max` - cap on regions returned (`0` => server default 1024; hard cap 8192).
|
|
- `probe_bytes` - bytes probe-read per region to time the MB/s measure (`0` => 64 KiB;
|
|
capped at 1 MiB). The measure is **always** taken (one `proc_read_mem` per region).
|
|
- **Response:** `CMD_SUCCESS`, then `u32 count`, then `count` x
|
|
`struct cmd_proc_turboscan_region_info` (32 bytes each), then `CMD_SUCCESS`.
|
|
Each record: `{ u64 start; u64 end; u32 prot; u32 flags; u32 mbps; u32 reserved; }`
|
|
where `flags` bit0 = leaf-PTE `PCD=1` (uncached) and `mbps` is the measured whole-MB/s
|
|
read throughput of the region. Read-only; no aliasing / PTE writes.
|
|
|
|
##### `0xBDAACC17` - cancel (`proc_turboscan_cancel_handle`) - **requires auth bit 1**
|
|
Abort an in-flight turbo scan (`START` or `COUNT`) for a given pid. The command dispatcher
|
|
is serial per connection, so the connection running the long scan is busy streaming and
|
|
cannot receive a command - **CANCEL must be sent on a second, separately authed
|
|
connection**. It arms a pid-scoped flag the scan loops poll (all parallel workers observe
|
|
it), so they stop early and emit their normal terminator; the waiting client on the first
|
|
connection then unblocks. Raw literal (no `CMD_*` macro).
|
|
- **Request body:** `u32 pid` (the pid whose in-flight scan to cancel; must be non-zero).
|
|
- **Response:** `CMD_SUCCESS` (the flag is armed - it does not wait for the scan to notice).
|
|
`CMD_DATA_NULL` if not authed / no body / `pid == 0`.
|
|
- **Effect on the cancelled scan:**
|
|
- **Snapshot `START` (unknown-initial-value):** stops cleanly, frees the half-built
|
|
snapshot, and returns the snapshot summary with `snapshot_ok = 0`.
|
|
- **`START` (known-value) and `COUNT` (rescan):** stop early and return their normal
|
|
terminator with **partial** results; the resident/snapshot session is left
|
|
**indeterminate** - issue `END` (`0xBDAACC14`) or a fresh `START` before trusting results.
|
|
- **Staleness:** a cancel arriving before the scan starts is discarded (`START`/`COUNT`
|
|
clear a stale same-pid flag on entry), so only a running scan can be cancelled. The
|
|
client-driven streaming `COUNT` is already stoppable via its end sentinel and is unaffected.
|
|
|
|
##### Snapshot sub-protocol (`CC11 + TS_SNAPSHOT`, narrowed by `CC12 + TS_SERVER_RESIDENT`)
|
|
For unknown-initial-value scans. **By default the seed drops all-zero slots** (most
|
|
memory is zeroed and rarely a useful target), so `survivor_count` reflects only the
|
|
nonzero slots kept; pass `TS_SNAPSHOT_INCLUDE_ZEROS` to seed every slot instead.
|
|
|
|
**Single range vs. multi-segment.** By default the snapshot covers the single
|
|
`[address, address+length)` range from the `START` packet. With
|
|
`TS_SNAPSHOT_SEGMENTS` set (server must advertise `TSE_SNAPSHOT_SEGMENTS`), the
|
|
snapshot instead covers a client-supplied list of disjoint segments in **one**
|
|
resident session: right after the value/mask trailing data (and before the plan),
|
|
the client streams `u32 segment_count`, then `segment_count` x
|
|
`struct cmd_proc_turboscan_snap_segment` (12 bytes: `{ u64 address; u32 length; }`).
|
|
The packet's `address`/`length` are ignored in this mode. The slot space is the
|
|
concatenation of the segments, so unmapped gaps between them are never read and
|
|
storage scales with the selected bytes, not the bounding span - this is the correct
|
|
way to scan a scattered module/section selection (the alternative, spanning
|
|
`[minStart, maxEnd)` as one range, reads and stores the gaps too). Survivor
|
|
addresses are absolute, so `slot_count`/progress, `CC12` narrow, `CC13` GET, and
|
|
materialize are all unaffected by the segmentation. `segment_count` must be in
|
|
`1 .. 1048576` (an overflow guard, far beyond any real layout); outside that, or on
|
|
a server-side allocation failure, the server replies `snapshot_ok = 0` and the
|
|
client falls back to its own path.
|
|
|
|
After the two leading `CMD_SUCCESS` acks (and the segment list when
|
|
`TS_SNAPSHOT_SEGMENTS` is set), `CC11 + TS_SNAPSHOT` streams:
|
|
1. `struct cmd_proc_turboscan_snap_plan` (16 bytes): `{ u64 slot_count; u64 total_bytes; }`
|
|
(`total_bytes` = sum of the segment lengths in multi-segment mode).
|
|
2. progress: repeated `u64 bytes_done` records, terminated by an 8-byte
|
|
`0xFFFFFFFFFFFFFFFF`.
|
|
3. `struct cmd_proc_turboscan_snap_summary` (12 bytes):
|
|
`{ u32 snapshot_ok; u64 survivor_count; }` - `snapshot_ok = 0` means
|
|
ENOSPC / too large, and the client falls back to its own unknown-value path.
|
|
4. a final `CMD_SUCCESS`.
|
|
|
|
Subsequent narrowing uses `CC12 + TS_SERVER_RESIDENT` (rescans the snapshot in
|
|
place, since-last-scan) - which streams its own throttled `u64 slots_scanned` progress
|
|
records (relative to `slot_count`), terminated by the same sentinel, before the survivor
|
|
count (see CC12 above; the reply shape is mode-independent, so once the survivors get
|
|
sparse and the session materialises into a list, later narrows still parse identically) -
|
|
and `CC13 GET` fetches survivors. With `TS_FLOAT_SIMPLE` plus
|
|
`TSE_COMPACT_SIMPLE_SNAPSHOT`, the value snapshot is a survivor-record stream with
|
|
no bitmap; optional First/Previous values live in each record, and rejected raw
|
|
slots are never written. With Simple off, the legacy raw-slot-dense value stores and
|
|
RAM bitmap are unchanged. Either store is **RAM-backed when it fits under the RAM
|
|
threshold** (default 512 MiB, tunable via `CC15`); a larger store is **hybrid** - the
|
|
first threshold bytes (whole slots or records) stay in anon RAM and only the overflow goes to a
|
|
`ps5dbg_snap_NN.bin` file under the spill directory (default `/data`, also `CC15`-tunable
|
|
to e.g. `/mnt/ext1` or `/mnt/usb0`). The spill is accessed by windowed `pread`/`pwrite`
|
|
**written in 16 MiB chunks** (amortizes the per-`write()` FS overhead - HW-measured ~1.5x
|
|
over 1 MiB); file-backed `mmap` is avoided because faulting a `MAP_SHARED` page sleeps
|
|
under a held VM lock and panics the console. The store is read/written sequentially, so
|
|
at most one create-chunk / narrow-window straddles the RAM/file boundary (split
|
|
transparently). Dense sessions keep their membership bitmap in RAM; compact Simple
|
|
sessions have no bitmap. The file is unlinked on `END` / disconnect / new scan,
|
|
and `/data` is swept at startup (spill files left on a removable/expansion mount are not
|
|
swept, but are bounded to one per client slot and truncated on reuse).
|
|
|
|
For the `valueType` / `compareType` values, see 7.
|
|
|
|
---
|
|
|
|
### 2.3 Debug commands - dispatched by `debug_handle` (`debug.c:1594-1619`)
|
|
|
|
Most handlers check `g_debug_attached` (and a non-zero pid) themselves and reply
|
|
`CMD_ERROR` if no session is active (`CMD_DEBUG_PROCESS_STOP` is the exception -
|
|
it works without a session). An unknown debug opcode replies `CMD_ERROR`. The
|
|
dispatcher does not silently drop.
|
|
|
|
#### `CMD_DEBUG_ATTACH = 0xBDBB0001` (`debug.c:549`, `debug_attach_handle`)
|
|
This is the real attach handler (there is no stub / `_svc` variant).
|
|
- **Request body:** `struct cmd_debug_attach_packet` (**4 bytes**, `{ uint32_t pid; }`).
|
|
The client's IP comes from the connection slot recorded in 1.5, not from the
|
|
packet.
|
|
- **Side effects:** elevates, `ptrace(PT_ATTACH)`s the target, resumes the app,
|
|
and opens the outbound interrupt connection to the client on port 755 (1.9).
|
|
- **Response:** `CMD_SUCCESS`, or `CMD_ALREADY_DEBUG` / `CMD_DATA_NULL` /
|
|
`CMD_ERROR`.
|
|
|
|
#### `CMD_DEBUG_DETACH = 0xBDBB0002` (`debug.c:609`)
|
|
- **Request body:** none.
|
|
- **Response:** `CMD_SUCCESS`. Runs `debug_full_teardown` (clears breakpoints /
|
|
watchpoints, resumes, detaches, closes the 755 connection).
|
|
|
|
#### `CMD_DEBUG_SET_BREAKPOINT = 0xBDBB0003` (`debug.c:618`)
|
|
- **Request body:** `struct cmd_debug_breakpt_packet` (16 bytes, `{ index, enabled, address }`).
|
|
- **Response:** `CMD_SUCCESS`, or `CMD_INVALID_INDEX` if `index > 29`.
|
|
- **Backend:** software breakpoint - writes `0xCC` via `proc_write_mem` (which
|
|
uses the DMAP path on FW >= 8.40), saving the original byte.
|
|
`MAX_BREAKPOINTS = 30` (`protocol.h:273`). (The CheaterNG client caps its own
|
|
index at 10.)
|
|
|
|
#### `CMD_DEBUG_SET_WATCHPOINT = 0xBDBB0004` (`debug.c:676`)
|
|
- **Request body:** `struct cmd_debug_watchpt_packet` (24 bytes):
|
|
`{ index, enabled, length, breaktype, address }`.
|
|
- **Response:** `CMD_SUCCESS`, or `CMD_INVALID_INDEX` if `index > 3`.
|
|
- **Backend:** hardware breakpoint via DR0-DR3 / DR7 (written through the kernel
|
|
dbreg path on firmwares that need it). `MAX_WATCHPOINTS = 4` (`protocol.h:274`).
|
|
`breaktype` and `length` use the x86 DR7 encodings in 7.3.
|
|
|
|
#### `CMD_DEBUG_GET_THREAD_LIST = 0xBDBB0005` (`debug.c:769`)
|
|
- **Request body:** none.
|
|
- **Response:** `CMD_SUCCESS`, `uint32_t num`, then `num` x `uint32_t lwpid`.
|
|
|
|
#### `CMD_DEBUG_SUSPEND_THREAD = 0xBDBB0006` (`debug.c:809`)
|
|
- **Request body:** `struct cmd_debug_stopthr_packet` (4 bytes, `lwpid`).
|
|
- **Response:** `CMD_SUCCESS` or `CMD_ERROR` (`PT_SUSPEND`).
|
|
|
|
#### `CMD_DEBUG_RESUME_THREAD = 0xBDBB0007` (`debug.c:822`)
|
|
- **Request body:** `struct cmd_debug_resumethr_packet` (4 bytes, `lwpid`).
|
|
- **Response:** `CMD_SUCCESS` or `CMD_ERROR` (`PT_RESUME`).
|
|
|
|
#### `CMD_DEBUG_GETREGS = 0xBDBB0008` (`debug.c:835`)
|
|
- **Request body:** `{ uint32_t lwpid; }` (4 bytes).
|
|
- **Response:** `CMD_SUCCESS`, then a **176-byte** GP-register blob (FreeBSD
|
|
amd64 `struct reg`).
|
|
|
|
#### `CMD_DEBUG_SETREGS = 0xBDBB0009` (`debug.c:855`)
|
|
- **Request body:** `struct cmd_debug_setregs_packet` (8 bytes, `{ lwpid, length }`).
|
|
- **Response:** `CMD_SUCCESS` (ack), reads `length` bytes (must be 176), then
|
|
`CMD_SUCCESS`. **Two** status words.
|
|
|
|
#### `CMD_DEBUG_GETFPREGS = 0xBDBB000A` (`debug.c:876`)
|
|
- **Request body:** `{ uint32_t lwpid; }` (4 bytes).
|
|
- **Response:** `CMD_SUCCESS`, then an **832-byte** FPU/YMM blob.
|
|
- The handler reads the thread's full xsave image from the kernel
|
|
(`kern_get_fpregs`, the thread's `pcb_save` area) so the **YMM (AVX) upper
|
|
halves are populated**, not just the 512-byte FXSAVE/XMM legacy region.
|
|
If the kernel path is unavailable it falls back to `PT_GETFPREGS`, which
|
|
fills only the first 512 bytes and leaves the YMM tail zero.
|
|
|
|
#### `CMD_DEBUG_SETFPREGS = 0xBDBB000B` (`debug.c:901`)
|
|
- **Request body:** `{ uint32_t lwpid; uint32_t length; }` (8 bytes).
|
|
- **Response:** `CMD_SUCCESS` (ack), reads `length` bytes (832), then
|
|
`CMD_SUCCESS`. **Two** status words.
|
|
- A full **832-byte** image is written back to the kernel xsave area
|
|
(`kern_set_fpregs`), so **YMM (AVX) state is applied**. A client must send a
|
|
complete xsave image (typically round-tripped from `GETFPREGS`) so the
|
|
xstate header stays valid. A legacy **512-byte** blob is applied via
|
|
`PT_SETFPREGS` instead (FXSAVE only), leaving YMM untouched.
|
|
|
|
#### `CMD_DEBUG_GETFSGSBASE = 0xBDBB000E` (`debug.c:997`)
|
|
- **Request body:** `{ uint32_t lwpid; }` (4 bytes).
|
|
- **Response:** `CMD_SUCCESS`, then a **16-byte** blob
|
|
`{ uint64_t fs_base; uint64_t gs_base }`.
|
|
- The 64-bit FS/GS segment **base** addresses (TLS / per-thread pointers).
|
|
These are not part of `struct reg` (`GETREGS` carries only the 16-bit
|
|
selectors), so they are read from the thread's `pcb` via the kernel R/W
|
|
primitive. **Kernel-path only** - no ptrace fallback; returns `CMD_ERROR`
|
|
if the thread cannot be resolved.
|
|
|
|
#### `CMD_DEBUG_SETFSGSBASE = 0xBDBB000F` (`debug.c:1019`)
|
|
- **Request body:** `{ uint32_t lwpid; uint32_t length; }` (8 bytes).
|
|
- **Response:** `CMD_SUCCESS` (ack), reads `length` bytes (must be **16**),
|
|
then `CMD_SUCCESS`. **Two** status words. Writes `fs_base`/`gs_base` into
|
|
the thread's `pcb`; takes effect when the stopped thread is resumed.
|
|
|
|
#### `CMD_DEBUG_GETDBREGS = 0xBDBB000C` (`debug.c:932`)
|
|
- **Request body:** `{ uint32_t lwpid; }` (4 bytes).
|
|
- **Response:** `CMD_SUCCESS`, then a **128-byte** debug-register blob.
|
|
|
|
#### `CMD_DEBUG_SETDBREGS = 0xBDBB000D` (`debug.c:969`)
|
|
- **Request body:** `{ uint32_t lwpid; uint32_t length; }` (8 bytes).
|
|
- **Response:** `CMD_SUCCESS` (ack), reads `length` bytes (128), then
|
|
`CMD_SUCCESS`. **Two** status words.
|
|
|
|
#### `CMD_DEBUG_CONTINUE = 0xBDBB0010` (`debug.c:1042`)
|
|
- **Request body:** `struct cmd_debug_stopgo_packet` (4 bytes); the first byte is
|
|
the action.
|
|
- `0` -> resume; `1` -> stop (`SIGSTOP`); `2` -> kill (`SIGKILL`) - routed
|
|
through `debug_stopgo_handle`. (Client name: `CMD_DEBUG_STOPGO`.)
|
|
- **Response:** `CMD_SUCCESS`.
|
|
|
|
#### `CMD_DEBUG_THREAD_INFO = 0xBDBB0011` (`debug.c:1087`)
|
|
- **Request body:** `struct cmd_debug_thrinfo_packet` (4 bytes, `lwpid`).
|
|
- **Response:** `CMD_SUCCESS`, `struct dbg_thrinfo_response` (40 bytes,
|
|
`{ uint32_t lwpid; uint32_t priority; char tdname[32]; }`).
|
|
|
|
#### `CMD_DEBUG_STEP = 0xBDBB0012` (`debug.c:1115`)
|
|
- **Request body:** none.
|
|
- **Response:** `CMD_SUCCESS`. `PT_STEP` on the whole process. (Client name:
|
|
`CMD_DEBUG_SINGLESTEP`.)
|
|
|
|
#### `CMD_DEBUG_STEP_THREAD = 0xBDBB0013` (`debug.c:1131`)
|
|
- **Request body:** `struct cmd_debug_stopthr_packet` (4 bytes, `lwpid`).
|
|
- **Response:** `CMD_SUCCESS` or `CMD_ERROR`. `PT_STEP` on a single LWP.
|
|
(Server-only; not invoked by the CheaterNG client.)
|
|
|
|
#### `CMD_DEBUG_PROCESS_STOP = 0xBDBB0500` (`debug.c:531`)
|
|
- **Request body:** 5 raw bytes: `uint32_t pid; uint8_t state;`. Returns
|
|
`CMD_ERROR` if `state > 2` or `pid == 0`. (Client name: `CMD_DEBUG_EXT`.)
|
|
- **Response:** `CMD_SUCCESS`, `CMD_DATA_NULL`, or `CMD_ERROR`.
|
|
- **Behaviour** (`debug.c` `debug_stopgo_handle`): if a session is active, a
|
|
signal is staged for the next event dispatch (`0` -> deferred resume,
|
|
`1` -> `SIGSTOP` (17), `2` -> `SIGKILL` (9)). Otherwise `kill(pid, sig)` is
|
|
called directly using the table `{ 19 = SIGCONT (resume), 17 = SIGSTOP, 9 = SIGKILL }`.
|
|
|
|
---
|
|
|
|
### 2.4 Kernel R/W commands - dispatched by `kern_handle` (`kern.c:53-60`)
|
|
|
|
#### `CMD_KERN_BASE = 0xBDCC0001` (`kern.c:8`)
|
|
- **Request body:** none.
|
|
- **Response:** `CMD_SUCCESS`, then `uint64_t` = the kernel `.data` base
|
|
(`KERNEL_ADDRESS_DATA_BASE`).
|
|
|
|
#### `CMD_KERN_READ = 0xBDCC0002` (`kern.c:16`)
|
|
- **Request body:** `struct cmd_kern_read_packet` (12 bytes, `{ address, length }`).
|
|
- **Response:** `CMD_SUCCESS`, then `length` bytes (`kernel_copyout_fast`).
|
|
|
|
#### `CMD_KERN_WRITE = 0xBDCC0003` (`kern.c:34`)
|
|
- **Request body:** `struct cmd_kern_write_packet` (12 bytes, `{ address, length }`).
|
|
- **Response:** `CMD_SUCCESS` (before the data phase), reads `length` bytes,
|
|
`kernel_copyin_fast`, then `CMD_SUCCESS` again. **Two** status words.
|
|
|
|
---
|
|
|
|
### 2.5 Console commands - dispatched by `console_handle` (`console.c:312-322`)
|
|
|
|
#### `CMD_CONSOLE_REBOOT = 0xBDDD0001` (`console.c:30`)
|
|
- **Request body:** none.
|
|
- **Behaviour:** calls `sceKernelReboot()` and returns; the console reboots.
|
|
|
|
#### `CMD_CONSOLE_END = 0xBDDD0002` (`console.c:36`, `console_end_handle`)
|
|
- **Request body:** none.
|
|
- **Response:** `CMD_SUCCESS`.
|
|
|
|
#### `CMD_CONSOLE_PRINT = 0xBDDD0003` (`console.c:42`)
|
|
- **Request body:** `struct cmd_console_print_packet` `{ uint32_t length; }` (4 bytes).
|
|
- **Trailing data:** `length` bytes of text.
|
|
- **Response:** `CMD_SUCCESS`. (Writes via `klog_puts`.)
|
|
|
|
#### `CMD_CONSOLE_NOTIFY = 0xBDDD0004` (`console.c:61`)
|
|
- **Request body:** `struct cmd_console_notify_packet` `{ uint32_t messageType; uint32_t length; }` (8 bytes).
|
|
- **Trailing data:** `length` bytes of text (the client appends a trailing `\0`).
|
|
- **Response:** `CMD_SUCCESS`.
|
|
- **Behaviour:** calls `sceKernelSendNotificationRequest` with the text.
|
|
`messageType` is read off the wire but **not used** by this handler.
|
|
|
|
#### `CMD_CONSOLE_INFO = 0xBDDD0005` (`console.c:88`, `console_info_handle`)
|
|
- **Request body:** none.
|
|
- **Response:** `CMD_SUCCESS`. No side effect (ping variant).
|
|
|
|
#### `0xBDDD0006` - foreground-app metadata (`console.c:183`, `console_foreground_app_handle`)
|
|
Raw literal, no `CMD_*` macro. (Client name: `CMD_CONSOLE_FOREGROUND_APP`.)
|
|
- **Request body:** none.
|
|
- **Response:** `CMD_SUCCESS`, `struct cmd_console_foreground_app_response`
|
|
(**140 bytes**, `console.c:144`):
|
|
```
|
|
u32 pid;
|
|
char titleid[16];
|
|
char contentid[64];
|
|
char name[40];
|
|
char app_ver[16]; // version string (app_ver at byte offset 124)
|
|
```
|
|
- **Behaviour:** the foreground `eboot.bin` process is found by walking
|
|
`allproc`. `titleid`/`contentid` are resolved by pattern-scanning the proc
|
|
struct (FW-agnostic), falling back to per-firmware offsets. The version is read
|
|
from the title's `param.sfo` (`APP_VER` / `VERSION`, the lexically larger of
|
|
the two), falling back to `param.json` (`contentVersion` /
|
|
`originContentVersion`, else `masterVersion`). If there is no foreground game
|
|
all fields are zeroed (`pid == 0`).
|
|
|
|
---
|
|
|
|
## 3. In-process kernel/target primitives
|
|
|
|
ps5debug-NG runs the entire wire-protocol server inside SceShellCore (the
|
|
installer injects the embedded debugger ELF as a SceShellCore-internal thread).
|
|
"Kernel-side" operations are performed from userland using primitives from
|
|
[ps5-payload-sdk](https://github.com/ps5-payload-dev/sdk) plus short kernel
|
|
patches applied at install time:
|
|
|
|
| Concern | How ps5debug-NG implements it |
|
|
|--------------------------------------|----------------------------------------------------------------------------|
|
|
| Process enumeration (`CMD_PROC_LIST`)| Walk the kernel `allproc` linked list with `kernel_copyout_fast` (`proc.c` `proc_list_handle`). |
|
|
| Target memory read | `sys_proc_rw_w0` (mdbg), with a DMAP page-table-walk read for Sony-aux regions (`proc.c` `proc_read_mem`). |
|
|
| Target memory write | Content-verified DMAP page-table-walk write on FW >= 8.40, with an independently read-back mdbg fallback; low-byte-`0xFF` starts are preserve-and-shift normalized (`proc.c`, `kern_rw_fast.c`, `sys_proc_rw.c`). |
|
|
| `mprotect` / alloc / free / call / ELF | `sys_proc_cmd` syscall variants (`kdbg.h` `SYS_PROC_*`). |
|
|
| Kernel base / kernel R/W | `KERNEL_ADDRESS_DATA_BASE`, `kernel_copyout_fast` / `kernel_copyin_fast` (`kern.c`). |
|
|
| VM map (`CMD_PROC_MAPS`) | Read `vmspace` + map entries via `kernel_copyout_fast`, augmented by a page-table walk (`scan.c` `sys_proc_vm_map`, `proc_ptwalk_augment`). |
|
|
| Console reboot / notify / print | `sceKernelReboot`, `sceKernelSendNotificationRequest`, `klog_puts`. |
|
|
|
|
The installer's role is bootstrap-only: find SceShellCore, apply the FW-specific
|
|
kernel patch, set up the target's execution state, and hand control to the
|
|
embedded debugger. After that the installer exits and the debugger services TCP.
|
|
|
|
---
|
|
|
|
## 4. Full command ID table
|
|
|
|
Auth = requires `g_proc_auth_state & 2` (set via `CMD_PROC_AUTH`).
|
|
|
|
| Hex | Server handler name | File / location | Auth? |
|
|
|--------------|----------------------------------|--------------------------|-------|
|
|
| `0xBD000001` | `handle_version` | `meta.c` (inline) | |
|
|
| `0xBD000500` | `handle_fw_version` | `meta.c` (inline) | |
|
|
| `0xBD000501` | `handle_branding` | `meta.c` (inline) | |
|
|
| `0xBD000502` | `handle_platform_id` | `meta.c` (inline) | |
|
|
| `0xBDAACC06` | `CMD_PROC_NOP` | `meta.c` (inline) | |
|
|
| `0xBDAA0001` | `proc_list_handle` | `proc.c` | |
|
|
| `0xBDAA0002` | `proc_read_handle` | `proc.c` | |
|
|
| `0xBDAA0003` | `proc_write_handle` | `proc.c` | |
|
|
| `0xBDAA0004` | `proc_maps_handle` | `proc.c` | |
|
|
| `0xBDAA0005` | `proc_install_handle` (stub) | `proc.c` | |
|
|
| `0xBDAA0006` | `proc_call_handle` | `proc.c` | |
|
|
| `0xBDAA0007` | `proc_elf_handle` | `proc.c` | |
|
|
| `0xBDAA0008` | `proc_protect_handle` | `proc.c` | |
|
|
| `0xBDAA0009` | `proc_scan_handle` | `scan.c` | |
|
|
| `0xBDAA000A` | `proc_info_handle` | `proc.c` | |
|
|
| `0xBDAA000B` | `proc_alloc_handle` | `proc.c` | |
|
|
| `0xBDAA000C` | `proc_free_handle` | `proc.c` | |
|
|
| `0xBDAA000D` | `proc_unknown_d_handle` | `proc.c` (raw literal) | |
|
|
| `0xBDAA000E` | `proc_alloc_hinted_handle` | `proc.c` | |
|
|
| `0xBDAA0010` | `proc_elf_rpc_handle` | `proc.c` | |
|
|
| `0xBDAA0020` | `proc_disasm_region_handle` | `proc.c` | |
|
|
| `0xBDAA0021` | `proc_extract_code_xrefs_handle` | `proc.c` | |
|
|
| `0xBDAA0022` | `proc_find_xrefs_to_handle` | `proc.c` | |
|
|
| `0xBDAA0023` | `proc_read_stack_handle` | `proc.c` | |
|
|
| `0xBDAA0024` | `proc_assemble_handle` | `assemble.c` (raw literal) | |
|
|
| `0xBDAA0501` | `proc_scan_aob_handle` | `scan.c` | |
|
|
| `0xBDAA0502` | `proc_scan_aob_multi_handle` | `scan.c` | |
|
|
| `0xBDAACCFF` | `proc_auth_handle` | `auth.c` | |
|
|
| `0xBDAACC01` | `proc_scan_start_handle` | `scan.c` | bit 1 |
|
|
| `0xBDAACC02` | `proc_scan_count_handle` | `scan.c` | bit 1 |
|
|
| `0xBDAACC03` | `proc_scan_get_handle` | `scan.c` | bit 1 |
|
|
| `0xBDAACC04` | `proc_write_multi_handle` | `proc.c` | |
|
|
| `0xBDAACC10` | `proc_turboscan_caps_handle` | `scan_turbo.c` | |
|
|
| `0xBDAACC11` | `proc_turboscan_start_handle` | `scan_turbo.c` | bit 1 |
|
|
| `0xBDAACC12` | `proc_turboscan_count_handle` | `scan_turbo.c` | bit 1 |
|
|
| `0xBDAACC13` | `proc_turboscan_get_handle` | `scan_turbo.c` | bit 1 |
|
|
| `0xBDAACC14` | `proc_turboscan_end_handle` | `scan_turbo.c` | bit 1 |
|
|
| `0xBDAACC15` | `proc_turboscan_config_handle` | `scan_turbo.c` | bit 1 |
|
|
| `0xBDAACC16` | `proc_turboscan_regions_handle` | `scan_turbo.c` | bit 1 |
|
|
| `0xBDAACC17` | `proc_turboscan_cancel_handle` | `scan_turbo.c` | bit 1 |
|
|
| `0xBDAACC24` | `proc_arena_handle` | `proc.c` | |
|
|
| `0xBDBB0001` | `debug_attach_handle` | `debug.c` | |
|
|
| `0xBDBB0002` | `debug_detach_handle` | `debug.c` | |
|
|
| `0xBDBB0003` | `debug_set_breakpoint_handle` | `debug.c` | |
|
|
| `0xBDBB0004` | `debug_set_watchpoint_handle` | `debug.c` | |
|
|
| `0xBDBB0005` | `debug_get_thread_list_handle` | `debug.c` | |
|
|
| `0xBDBB0006` | `debug_suspend_thread_handle` | `debug.c` | |
|
|
| `0xBDBB0007` | `debug_resume_thread_handle` | `debug.c` | |
|
|
| `0xBDBB0008` | `debug_getregs_handle` | `debug.c` | |
|
|
| `0xBDBB0009` | `debug_setregs_handle` | `debug.c` | |
|
|
| `0xBDBB000A` | `debug_getfpregs_handle` | `debug.c` | |
|
|
| `0xBDBB000B` | `debug_setfpregs_handle` | `debug.c` | |
|
|
| `0xBDBB000C` | `debug_getdbregs_handle` | `debug.c` | |
|
|
| `0xBDBB000D` | `debug_setdbregs_handle` | `debug.c` | |
|
|
| `0xBDBB000E` | `debug_getfsgsbase_handle` | `debug.c` | |
|
|
| `0xBDBB000F` | `debug_setfsgsbase_handle` | `debug.c` | |
|
|
| `0xBDBB0010` | `debug_continue_handle` | `debug.c` | |
|
|
| `0xBDBB0011` | `debug_thread_info_handle` | `debug.c` | |
|
|
| `0xBDBB0012` | `debug_step_handle` | `debug.c` | |
|
|
| `0xBDBB0013` | `debug_step_thread_handle` | `debug.c` | |
|
|
| `0xBDBB0500` | `debug_process_stop_handle` | `debug.c` | |
|
|
| `0xBDCC0001` | `kern_base_handle` | `kern.c` | |
|
|
| `0xBDCC0002` | `kern_read_handle` | `kern.c` | |
|
|
| `0xBDCC0003` | `kern_write_handle` | `kern.c` | |
|
|
| `0xBDDD0001` | `console_reboot_handle` | `console.c` | |
|
|
| `0xBDDD0002` | `console_end_handle` | `console.c` | |
|
|
| `0xBDDD0003` | `console_print_handle` | `console.c` | |
|
|
| `0xBDDD0004` | `console_notify_handle` | `console.c` | |
|
|
| `0xBDDD0005` | `console_info_handle` | `console.c` | |
|
|
| `0xBDDD0006` | `console_foreground_app_handle` | `console.c` (raw literal) | |
|
|
|
|
---
|
|
|
|
## 5. Request packet appendix
|
|
|
|
All structs are `__attribute__((packed))`; most are defined in
|
|
`common/include/protocol.h` (a few are local to their handler's `.c`).
|
|
|
|
| Struct | Size | Fields |
|
|
|-----------------------------------------|------|---------------------------------------------------------------------------|
|
|
| `cmd_packet` (wire portion) | 12 | `u32 magic; u32 cmd; u32 datalen;` |
|
|
| `cmd_proc_read_packet` | 16 | `u32 pid; u64 address; u32 length;` |
|
|
| `cmd_proc_write_packet` | 16 | `u32 pid; u64 address; u32 length;` |
|
|
| `cmd_proc_maps_packet` | 4 | `u32 pid;` |
|
|
| `cmd_proc_install_packet` | 4 | `u32 pid;` |
|
|
| `cmd_proc_unknown_d_packet` | 4 | `u32 pid;` |
|
|
| `cmd_proc_call_packet` | 68 | `u32 pid; u64 rpcstub,rpc_rip,rpc_rdi,rpc_rsi,rpc_rdx,rpc_rcx,rpc_r8,rpc_r9;` |
|
|
| `cmd_proc_elf_packet` | 8 | `u32 pid; u32 length;` |
|
|
| `cmd_proc_elf_rpc_packet` | 8 | `u32 pid; u32 length;` |
|
|
| `cmd_proc_protect_packet` (in `proc.c`) | 20 | `u32 pid; u64 address; u32 length; u32 prot;` |
|
|
| `cmd_proc_scan_packet` | 10 | `u32 pid; u8 valueType, compareType; u32 lenData;` |
|
|
| `cmd_proc_scan_start_packet` | 23 | `u32 pid; u64 address; u32 length; u8 valueType, compareType, alignment; u32 lenData;` |
|
|
| `cmd_proc_scan_count_packet` | 18 | `u32 pid; u64 base_address; u8 valueType, compareType; u32 lenData;` |
|
|
| `cmd_proc_scan_aob_packet` | 22 | `u32 pid; u64 address; u32 length; u8 max_matches, stop_flag; u32 pattern_length;` |
|
|
| `cmd_proc_scan_aob_multi_packet` | 21 | `u32 pid; u64 address; u32 length; u8 stop_flag; u32 patterns_length;` |
|
|
| `cmd_proc_auth_packet` | 8 | `u32 magic (0xBB40E64D); u32 flags;` |
|
|
| `cmd_proc_scan_get_packet` | 8 | `u32 pid; u32 count;` |
|
|
| `cmd_proc_turboscan_start_packet` | 27 | `u32 pid; u64 address; u32 length; u8 valueType, compareType, alignment; u32 lenData; u32 flags;` |
|
|
| `cmd_proc_turboscan_count_packet` | 22 | `u32 pid; u64 base_address; u8 valueType, compareType; u32 lenData; u32 flags;` |
|
|
| `cmd_proc_turboscan_get_packet` | 12 | `u32 start_index; u32 count; u32 flags;` |
|
|
| `cmd_proc_info_packet` | 4 | `u32 pid;` |
|
|
| `cmd_proc_alloc_packet` | 8 | `u32 pid; u32 length;` |
|
|
| `cmd_proc_alloc_hinted_packet` | 16 | `u32 pid; u64 hint; u32 length;` |
|
|
| `cmd_proc_free_packet` | 16 | `u32 pid; u64 address; u32 length;` |
|
|
| `cmd_proc_disasm_packet` | 20 | `u32 pid; u64 address; u32 length; u32 max_entries;` |
|
|
| `cmd_proc_xrefs_to_packet` | 24 | `u32 pid; u64 scan_address; u32 scan_length; u64 target_address;` |
|
|
| `cmd_proc_read_stack_packet` | 24 | `u32 pid; u64 rbp; u64 rsp; u32 depth;` |
|
|
| `cmd_proc_assemble_packet` | 12 | `u64 base_addr; u32 ks_opt_syntax;` (asm text follows in the same body) |
|
|
| `cmd_debug_attach_packet` | 4 | `u32 pid;` |
|
|
| `cmd_debug_breakpt_packet` | 16 | `u32 index, enabled; u64 address;` |
|
|
| `cmd_debug_watchpt_packet` | 24 | `u32 index, enabled, length, breaktype; u64 address;` |
|
|
| `cmd_debug_stopthr_packet` | 4 | `u32 lwpid;` |
|
|
| `cmd_debug_setregs_packet` | 8 | `u32 lwpid; u32 length;` |
|
|
| `cmd_debug_stopgo_packet` | 4 | `u32 stop;` (first byte = action 0/1/2) |
|
|
| `cmd_debug_thrinfo_packet` | 4 | `u32 lwpid;` |
|
|
| `cmd_kern_read_packet` | 12 | `u64 address; u32 length;` |
|
|
| `cmd_kern_write_packet` | 12 | `u64 address; u32 length;` |
|
|
| `cmd_console_print_packet` (in `console.c`) | 4 | `u32 length;` |
|
|
| `cmd_console_notify_packet` (in `console.c`) | 8 | `u32 messageType; u32 length;` |
|
|
|
|
## 6. Response packet appendix
|
|
|
|
| Struct | Size | Fields |
|
|
|-----------------------------------------|------|---------------------------------------------------------------------------|
|
|
| `proc_list_entry` (streamed) | 36 | `char name[32]; int32_t pid;` |
|
|
| `proc_vm_map_entry` (streamed) | 58 | `char name[32]; u64 start; u64 end; u64 offset; u16 prot;` |
|
|
| `cmd_proc_call_response` | 12 | `u32 pid; u64 rpc_rax;` |
|
|
| `cmd_proc_turboscan_caps_response` | 16 | `u32 version; u32 engines; u32 max_threads; u32 reserved;` |
|
|
| `cmd_proc_turboscan_regions_packet` | 16 | `u32 pid; u32 max; u32 probe_bytes; u32 reserved;` |
|
|
| `cmd_proc_turboscan_region_info` (streamed)| 32 | `u64 start; u64 end; u32 prot; u32 flags; u32 mbps; u32 reserved;` |
|
|
| `cmd_proc_turboscan_resident_summary` | 12 | `u32 resident_stored; u64 count;` |
|
|
| `cmd_proc_turboscan_snap_plan` | 16 | `u64 slot_count; u64 total_bytes;` |
|
|
| `cmd_proc_turboscan_snap_summary` | 12 | `u32 snapshot_ok; u64 survivor_count;` |
|
|
| `cmd_proc_elf_rpc_response` | 8 | `u64 entry;` |
|
|
| `cmd_proc_alloc_response` | 8 | `u64 address;` |
|
|
| `cmd_proc_info_response` | 188 | `u32 pid; char name[40], path[64], titleid[16], contentid[64];` |
|
|
| `cmd_proc_assemble_ok` | 8 | `u32 byte_len; u32 insn_count;` (then `byte_len` machine bytes) |
|
|
| `cmd_proc_assemble_err` | 8 | `u32 ks_errno; u32 msg_len;` (then `msg_len` chars of `ks_strerror`) |
|
|
| `cmd_console_foreground_app_response` | 140 | `u32 pid; char titleid[16], contentid[64], name[40], app_ver[16];` |
|
|
| `dbg_thrinfo_response` | 40 | `u32 lwpid, priority; char tdname[32];` |
|
|
| `disasm_instr_entry` (streamed) | 32 | see 2.2 |
|
|
| register blobs (raw, not structs) | 176 / 832 / 128 | GP regs / FPU+YMM / debug regs |
|
|
|
|
(`CMD_PROC_INTALL` returns a bare `u64 rpcstub`, which is always `0` in this build.)
|
|
|
|
---
|
|
|
|
## 7. Enum reference
|
|
|
|
The `valueType` / `compareType` / DR7 enums are **not** defined in
|
|
`protocol.h`. The server uses bare integers (`scan_compare.c`); the symbolic
|
|
names live in the client (`PS4DBG.cs`). The values below are the wire integers.
|
|
|
|
### 7.1 Scan value type (server sizes from `scan_compare.c` `proc_scan_getSizeOfValueType`)
|
|
|
|
| Value | Name | Size |
|
|
|-------|--------------------|------|
|
|
| 0 | `valTypeUInt8` | 1 |
|
|
| 1 | `valTypeInt8` | 1 |
|
|
| 2 | `valTypeUInt16` | 2 |
|
|
| 3 | `valTypeInt16` | 2 |
|
|
| 4 | `valTypeUInt32` | 4 |
|
|
| 5 | `valTypeInt32` | 4 |
|
|
| 6 | `valTypeUInt64` | 8 |
|
|
| 7 | `valTypeInt64` | 8 |
|
|
| 8 | `valTypeFloat` | 4 |
|
|
| 9 | `valTypeDouble` | 8 |
|
|
| 10 | `valTypeArrBytes` | from `lenData` (mask-driven) |
|
|
| 11 | `valTypeString` | from `lenData` |
|
|
|
|
(The client additionally defines `valTypePointer = 12` and `valTypeNone = 13`
|
|
sentinels; the server treats any value `> 9` as size 0.)
|
|
|
|
### 7.2 Scan compare type (`scan_compare.c` `proc_scan_compareValues`, cmpType 0-12)
|
|
|
|
| Value | Name |
|
|
|-------|------------------------------------|
|
|
| 0 | `ExactValue` |
|
|
| 1 | `FuzzyValue` |
|
|
| 2 | `BiggerThan` |
|
|
| 3 | `SmallerThan` |
|
|
| 4 | `ValueBetween` |
|
|
| 5 | `IncreasedValue` |
|
|
| 6 | `IncreasedValueBy` |
|
|
| 7 | `DecreasedValue` |
|
|
| 8 | `DecreasedValueBy` |
|
|
| 9 | `ChangedValue` |
|
|
| 10 | `UnchangedValue` |
|
|
| 11 | `UnknownInitialValue` |
|
|
| 12 | `UnknownInitialLowValue` |
|
|
|
|
(The client additionally defines `PointerValue = 13` and `None = 14`.)
|
|
|
|
### 7.3 DR7 encoding for hardware watchpoints (`PS4DBG.cs` `WATCHPT_*`; server applies raw shifts in `debug.c`)
|
|
|
|
`breaktype` in `cmd_debug_watchpt_packet`:
|
|
|
|
| Value | Name |
|
|
|-------|-------------------|
|
|
| 0 | `DBREG_DR7_EXEC` |
|
|
| 1 | `DBREG_DR7_WRONLY`|
|
|
| 3 | `DBREG_DR7_RDWR` |
|
|
|
|
`length` (reused as the DR7 length field):
|
|
|
|
| Value | Bytes |
|
|
|-------|-------|
|
|
| 0 | 1 |
|
|
| 1 | 2 |
|
|
| 2 | 8 |
|
|
| 3 | 4 |
|
|
|
|
---
|
|
|
|
## 8. Response framing patterns
|
|
|
|
Reply shapes used across handlers:
|
|
|
|
1. **Fixed status:** a single `uint32_t` status (usually `CMD_SUCCESS`). Used by
|
|
protect, breakpoint/watchpoint management, thread suspend/resume, continue,
|
|
step, etc.
|
|
2. **Status + fixed struct:** status then a `struct cmd_*_response` of known
|
|
size. Used by info, call, alloc, alloc-hinted, elf-rpc, thread-info,
|
|
foreground-app.
|
|
3. **Status + count + array:** status, `uint32_t count`, then `count` elements.
|
|
Used by process list, VM maps, and the debug thread list.
|
|
4. **Streamed with sentinel:** status, then a sequence of records terminated by a
|
|
sentinel. `CMD_PROC_DISASM_REGION` uses a 32-byte all-`0xFF` record;
|
|
`CMD_PROC_EXTRACT_CODE_XREFS` / `CMD_PROC_FIND_XREFS_TO`, the legacy
|
|
`CMD_PROC_SCAN`, and `CMD_PROC_SCAN_START` / `_COUNT` / `_GET` stream
|
|
`uint64_t`s (or `uint64_t`-length-prefixed blocks) terminated by
|
|
`0xFFFFFFFFFFFFFFFF`. (The two AOB scans instead return a fixed count of
|
|
`uint64_t` results with no sentinel.)
|
|
5. **Ack + data phase + final status (two status words):** the handler sends
|
|
`CMD_SUCCESS`, reads the trailing payload, then sends a final status. Used by
|
|
`CMD_PROC_WRITE`, `CMD_PROC_ELF`, `CMD_PROC_ELF_RPC`, `CMD_KERN_WRITE`, and
|
|
`CMD_DEBUG_SETREGS` / `SETFPREGS` / `SETDBREGS`. Clients must consume both
|
|
status words.
|
|
6. **Multi-phase (scans and the auth handshake):** these interleave several
|
|
`CMD_SUCCESS` acks with read/stream phases. The number and placement of status
|
|
words varies per command (two to three for the legacy and AOB scans and
|
|
`SCAN_START` / `_COUNT`; a single status for `SCAN_GET`); see each command's
|
|
entry in 2.2 and the auth handshake in 1.8 for the exact sequence.
|
|
|
|
---
|
|
|
|
## 9. Known source-code oddities
|
|
|
|
Documented so a developer does not mistake them for bugs:
|
|
|
|
- `CMD_PROC_INTALL` is misspelled (missing `S`), preserved on the wire; the
|
|
handler is also a stub that returns `rpcstub = 0` without injecting anything.
|
|
- Three opcodes have no `CMD_*` macro and are bare hex literals in their switch:
|
|
`0xBDAA000D` (first-map probe), `0xBDAA0024` (assemble), `0xBDDD0006`
|
|
(foreground app).
|
|
- Status words are bit-swapped on the wire by `net_send_int32` (1.6); payloads
|
|
are not. The server-side `CMD_SUCCESS` macro is `0x40000000`, which is
|
|
`0x80000000` on the wire.
|
|
- The 1184-byte debug interrupt packet (1.9) has no named struct or size macro in
|
|
the server; it is built by hand at fixed offsets. `savefpu` lands at offset
|
|
224, which is not 64-byte aligned (intentional).
|
|
- `g_proc_auth_state` is a single global (not per-connection) and is not reset on
|
|
disconnect.
|
|
- The disassembler amalgamation (`third_party/zydis/Zydis.c`) is built with a
|
|
Makefile override (`-O3 -DNDEBUG -w -DZYAN_NO_LIBC`) distinct from the rest of
|
|
the payload.
|
|
|
|
---
|
|
|
|
*This document reflects the `ps5debug-NG v1.3.2` payload
|
|
(`common/include/version.h`; v1.3.1 added the Turbo Scan cancel command `0xBDAACC17`).
|
|
Line numbers cite the sources as of this writing
|
|
and may drift; the source under `common/include/protocol.h`,
|
|
`debugger/source/`, and `common/source/` is the authoritative reference.*
|