# ps5debug-NG - Wire Protocol Reference Developer-facing reference for every command exposed by the debugger payload. All field offsets, sizes, opcodes, and behaviors below are taken directly from the sources under `debugger/`, `installer/`, and `common/` in this tree, with file:line citations. Nothing is summarized from external documentation. --- ## 1. Wire protocol fundamentals ### 1.1 Core constants | Symbol | Value | Source | |---------------------|--------------------------------------|----------------------------| | `PACKET_VERSION` | `"1.3"` | protocol.h:11 | | `PACKET_BRANDING` | `"ps5debug-NG by OSR v1.2.2"` | meta.c (`handle_branding`) | | `PACKET_MAGIC` | `0xFFAABBCC` | protocol.h:13 | | `BROADCAST_MAGIC` | `0xFFFFAAAA` | server.h:19 | ### 1.2 Ports | Port | Protocol | Role | Source | |------|----------|------------------------------------------------------|-------------------| | 744 | TCP | Main command server | main.c | | 755 | TCP | Asynchronous debug-interrupt channel (server→client) | debug.h | | 3232 | TCP | Kernel-log forwarder (server→client, read-only) | main.c | | 1010 | UDP | Discovery broadcast | main.c | ### 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. ### 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 client-side pointer filled in by the server after the variable-length body is received (protocol.h:104-110, server.c:167-185): ``` 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 (server.c:102-203): 1. Server blocking-reads 12 bytes → `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`, server allocates a buffer via `net_alloc_buffer(datalen)` and reads exactly `datalen` bytes into it. 4. Dispatches to the appropriate handler; the handler reads any *additional* trailing data (beyond the fixed request struct) directly from the socket. 5. Handler replies with `CMD_SUCCESS` (or an error code from §1.6) via `net_send_int32`, optionally followed by response payload. ### 1.5 Dispatch routing (meta.c, `cmd_handler`) `cmd_handler` inlines five infrequent commands, then routes everything else by testing the command against `VALID_*_CMD` macros (full-opcode match, no middle-byte shortcut): | 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)` additionally requires the top byte to be `0xBD` (common/include/protocol.h). **Special case:** if `cmd == CMD_DEBUG_ATTACH`, the per-connection handler is invoked with the active client slot so it can attach debug state to the specific TCP connection. ### 1.6 Status codes (common/include/protocol.h) | Code | Value | Meaning | |-----------------------|--------------|---------------------------------------------------| | `CMD_SUCCESS` | `0x80000000` | Command succeeded; response payload may follow. | | `CMD_ERROR` | `0xF0000001` | Generic failure. | | `CMD_TOO_MUCH_DATA` | `0xF0000002` | Client requested more than the server will send. | | `CMD_DATA_NULL` | `0xF0000003` | NULL/missing packet body or allocation failure. | | `CMD_ALREADY_DEBUG` | `0xF0000004` | A debug session is already active. | | `CMD_INVALID_INDEX` | `0xF0000005` | Breakpoint/watchpoint index out of range. | | `CMD_FATAL_STATUS(s)` | macro | `((s >> 28) == 15)` - any `0xFxxxxxxx` is fatal. | **PS5-specific wire quirk: bit-pair-swapped status words.** The server's `net_send_int32` helper (used to transmit `uint32_t` status codes back to the client) writes the value with its even/odd bit positions swapped. Clients must un-swap incoming status `uint32_t`s before comparing to the table above: ```c static inline uint32_t bitswap32(uint32_t x) { return ((x << 1) & 0xAAAAAAAAu) | ((x >> 1) & 0x55555555u); } ``` The swap is an involution (its own inverse). Subsequent response payload bytes (structs, strings, raw memory) are sent **without** the swap - only the `uint32_t` status word is affected. Payload-length prefixes inside response bodies (e.g. the `uint32_t length` before a branding/version string) are also sent raw; the swap is gated specifically to `net_send_int32`. ### 1.7 Discovery (server.c:235-306) A UDP server on port 1010 accepts a single 4-byte datagram. If the payload equals `BROADCAST_MAGIC` (`0xFFFFAAAA`), the server echoes the same 4 bytes back to the sender. No other payload is accepted; there is no authentication on this channel. ### 1.8 Authentication (proc.c:1862-1900) A single connection-scoped flag word `g_proc_auth_bits` gates the scan family. Bit 1 (`0x02`) is the *scan-enabled* bit; all `CMD_PROC_SCAN*` handlers check it on entry (proc.c:1904-1905, 1691, 1860, etc.). `CMD_PROC_AUTH` protocol: 1. Client sends a `struct cmd_proc_auth_packet`: - `uint32_t magic` - must equal `CMD_PROC_AUTH_MAGIC` (`0xBB40E64D`). - `uint32_t flags` - requested bits to OR into `g_proc_auth_bits` on success. 2. Server generates a 64-byte challenge and sends: `uint16_t challenge_length = 64` followed by the 64 bytes of challenge. 3. Client XORs the challenge against a keystream (derived client-side; mechanism intentionally undocumented) and sends 64 response bytes back. 4. On match, server OR-s the requested flags into `g_proc_auth_bits` and replies `CMD_SUCCESS`; otherwise `CMD_DATA_NULL`. State is per-connection: disconnecting resets it. There is *no* server-wide authentication on other command families - reads/writes/debug/kernel RW are unauthenticated. ### 1.9 Asynchronous interrupt channel (port 755) When a client issues `CMD_DEBUG_ATTACH`, the server also opens a TCP connection *outbound* to that client's IP on port 755 (debug.c:25). The client must be listening on 755 before sending `CMD_DEBUG_ATTACH`. Interrupts (hit breakpoints/watchpoints, single-step completions) are delivered asynchronously as fixed-size packets of `DEBUG_INTERRUPT_PACKET_SIZE = 0x4A0` bytes (1184) on this channel (debug.h:105-113): ``` struct debug_interrupt_packet { // 0x4A0 bytes uint32_t lwpid; uint32_t status; char tdname[40]; struct __reg64 reg64; // GP registers; 184 bytes struct savefpu_ymm savefpu; // FPU+YMM; 512 bytes struct __dbreg64 dbreg64; // 16× uint64_t = 128 bytes }; ``` Note: the struct is `packed`, so `savefpu` is not cache-line aligned - the compiler emits warnings for this (observed during build) but the layout is stable. --- ## 2. Command reference 58 total commands. Every `CMD_*` macro defined in `common/include/protocol.h` has a handler, plus the raw-literal opcodes `0xBDAA0024` (assemble) and `0xBDDD0006` (foreground app). ### 2.1 Info & ping (`0xBD000xxx`, `0xBDAACC06`) All five are handled inline in `cmd_handler` (server.c:52-71). #### `CMD_VERSION = 0xBD000001` - **Request body:** none. - **Response:** `uint32_t length`, then `length` bytes of `PACKET_VERSION` ("1.3"). #### `CMD_FW_VERSION = 0xBD000500` - **Request body:** none. - **Response:** `uint16_t` firmware version (BCD, e.g. `0x505` = 5.05). #### `CMD_BRANDING = 0xBD000501` - **Request body:** none. - **Response:** `uint32_t length`, then `length` bytes of `PACKET_BRANDING`. #### `CMD_PLATFORM_ID = 0xBD000502` - **Request body:** none. - **Response:** `uint16_t` platform tag - hardcoded `5` (PS5). Clients display this as the console version (e.g. logged as "Console PS5"). The wire-protocol version reported by `CMD_VERSION` is the server protocol revision (currently `1.3`); this command is the *platform* discriminator. #### `CMD_PROC_NOP = 0xBDAACC06` - **Request body:** none. - **Response:** `CMD_SUCCESS`. Keepalive / liveness probe. --- ### 2.2 Process commands (`0xBDAAxxxx`) - dispatched by `proc_handle` (proc.c:2268-2295) #### `CMD_PROC_LIST = 0xBDAA0001` (proc.c:330-358) - **Request body:** none. - **Response:** `CMD_SUCCESS`, `uint32_t num`, then `num × struct proc_list_entry`: ``` struct proc_list_entry { char p_comm[32]; int pid; }; // 36 bytes each ``` #### `CMD_PROC_READ = 0xBDAA0002` (proc.c:360-397) - **Request body:** `struct cmd_proc_read_packet` (16 bytes). - **Response:** `CMD_SUCCESS`, then up to `length` bytes in 64 KiB chunks. #### `CMD_PROC_WRITE = 0xBDAA0003` (proc.c:399-432) - **Request body:** `struct cmd_proc_write_packet` (16 bytes). - **Trailing data:** `length` bytes to write. - **Response:** `CMD_SUCCESS`. #### `CMD_PROC_MAPS = 0xBDAA0004` (proc.c:434-477) - **Request body:** `struct cmd_proc_maps_packet` (4 bytes, just `pid`). - **Response:** `CMD_SUCCESS`, `uint32_t num`, then `num × struct proc_vm_map_entry` (`{uint64_t start; uint64_t end; uint64_t offset; uint64_t prot; char name[32];}` = 64 bytes each). #### `CMD_PROC_INTALL = 0xBDAA0005` (proc.c:479-505) Note the literal spelling `INTALL` (typo in source, preserved for compat). - **Request body:** `struct cmd_proc_install_packet` (4 bytes, just `pid`). - **Response:** `CMD_SUCCESS`, `struct cmd_proc_install_response` (`uint64_t rpcstub`, 8 bytes). - **Effect:** injects the RPC stub code + per-process thread into the target. The returned `rpcstub` is the stub header address and must be supplied back on every subsequent `CMD_PROC_CALL`. #### `CMD_PROC_CALL = 0xBDAA0006` (proc.c:507-540) - **Request body:** `struct cmd_proc_call_packet` (68 bytes): ``` uint32_t pid; uint64_t rpcstub; // from CMD_PROC_INTALL response uint64_t rpc_rip; // target function address uint64_t rpc_rdi, rpc_rsi, rpc_rdx, rpc_rcx, rpc_r8, rpc_r9; // SysV args ``` - **Response:** `CMD_SUCCESS`, `struct cmd_proc_call_response` (`{uint32_t pid; uint64_t rpc_rax;}`, 12 bytes). #### `CMD_PROC_ELF = 0xBDAA0007` (proc.c:542-579) - **Request body:** `struct cmd_proc_elf_packet` (`{uint32_t pid; uint32_t length;}`, 8 bytes). - **Trailing data:** `length` bytes of ELF image. - **Response:** `CMD_SUCCESS` or error. #### `CMD_PROC_PROTECT = 0xBDAA0008` (proc.c:618-640) - **Request body:** `struct cmd_proc_protect_packet` (20 bytes): `{uint32_t pid; uint64_t address; uint32_t length; uint32_t newprot;}`. - **Response:** `CMD_SUCCESS`. #### `CMD_PROC_SCAN = 0xBDAA0009` (proc.c:1401-1485) - **requires auth bit 1** Legacy single-pass scan; superseded by the `SCAN_START/COUNT/GET` trio. - **Request body:** `struct cmd_proc_scan_packet` (10 bytes). - **Trailing data:** `lenData` bytes of comparison value(s). - **Response:** `uint32_t num`, then `num × uint64_t` addresses. #### `CMD_PROC_INFO = 0xBDAA000A` (proc.c:1487-1511) - **Request body:** `struct cmd_proc_info_packet` (4 bytes, `pid`). - **Response:** `CMD_SUCCESS`, `struct cmd_proc_info_response` (188 bytes): `{uint32_t pid; char name[40]; char path[64]; char titleid[16]; char contentid[64];}`. #### `CMD_PROC_ALLOC = 0xBDAA000B` (proc.c:1513-1534) - **Request body:** `struct cmd_proc_alloc_packet` (8 bytes, `{pid, length}`). - **Response:** `CMD_SUCCESS`, `struct cmd_proc_alloc_response` (`uint64_t address`, 8 bytes). #### `CMD_PROC_FREE = 0xBDAA000C` (proc.c:1536-1554) - **Request body:** `struct cmd_proc_free_packet` (16 bytes, `{pid, address, length}`). - **Response:** `CMD_SUCCESS`. #### `CMD_PROC_ALLOC_HINTED = 0xBDAA000E` (proc.c:1557-1580) - **Request body:** `struct cmd_proc_alloc_hinted_packet` (16 bytes, `{pid, hint, length}`). - **Response:** `CMD_SUCCESS`, `struct cmd_proc_alloc_response` (`uint64_t address`). #### `CMD_PROC_ELF_RPC = 0xBDAA0010` (proc.c:581-616) 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}`). - **Trailing data:** `length` bytes of ELF image. - **Response:** `CMD_SUCCESS`, `struct cmd_proc_elf_rpc_response` (`uint64_t entry`, 8 bytes). #### `CMD_PROC_DISASM_REGION = 0xBDAA0020` (proc.c:145-184) - **Request body:** `struct cmd_proc_disasm_packet` (20 bytes, `{pid, address, length, max_entries}`). - **Response:** `CMD_SUCCESS`, a stream of `struct disasm_instr_entry` (32 bytes each), terminated by a sentinel entry with all fields `0xFF`. - **Backend:** Zydis amalgamation, compiled with `ZYAN_NO_LIBC`. `disasm_instr_entry` layout (protocol.h:143-154): ``` struct disasm_instr_entry { // 32 bytes uint64_t addr; // instruction start uint64_t rip_rel_target; // resolved absolute addr if MEM_IS_RIP_REL; 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 (category hint) uint16_t pad; }; ``` Kind bitmask (protocol.h:139-142): | Bit | Meaning | |------|------------------------------------| | 0x01 | `CALL` | | 0x02 | `RET` | | 0x04 | Unconditional `JMP` | | 0x08 | Conditional branch | | 0x10 | Instruction has a memory operand | | 0x20 | Memory operand is RIP-relative | | 0x40 | Memory read | | 0x80 | Memory write | #### `CMD_PROC_EXTRACT_CODE_XREFS = 0xBDAA0021` (proc.c:220-294) - **Request body:** `struct cmd_proc_disasm_packet` (20 bytes). - **Response:** `CMD_SUCCESS`, `uint32_t num_targets`, `num_targets × uint64_t` (deduplicated RIP-relative branch/call targets inside the scanned range). #### `CMD_PROC_FIND_XREFS_TO = 0xBDAA0022` (proc.c:296-328) - **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`, `uint32_t num`, then `num × (uint64_t address, uint8_t kind)` - the `kind` byte reuses the `disasm_instr_entry.kind` bitmask. #### `CMD_PROC_READ_STACK = 0xBDAA0023` (proc.c) Server-side RBP-chain walk. The server walks the frame chain itself and bundles every frame's data (saved RBP, return address, frame-local bytes, and a code window around the return address) into one response, so clients avoid paying ~4 TCP round-trips per stack frame. Clients can feature-detect this command via the `CMD_BRANDING` version string (added in v1.2.2). - **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 (incl. local). - **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) - read rsp..rbp yourself 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: `CMD_PROC_READ_STACK_MAX_DEPTH = 64`, `CMD_PROC_READ_STACK_LOCALS_CAP = 0x1000` per frame, `CMD_PROC_READ_STACK_CODE_OFF = 10`, `CMD_PROC_READ_STACK_CODE_LEN = 200`. #### `0xBDAA0024` - assemble x86-64 (proc.c, `proc_assemble_handle`) Assemble x86-64 text into machine bytes using the Keystone (LLVM-MC) assembler embedded in the payload. Pure userspace - needs no attached process, no elevation. The on-console equivalent of what Reaper Studio does with its client-side `keystone.dll` when applying `VarType.ASM` patches. **The bare opcode is intentionally NOT given a `CMD_*` macro** - it's a raw literal in `proc_handle`'s switch - so the published `CMD_*` set (which some clients enumerate) stays unchanged. Clients that need it should send the literal. - **Request body:** `struct cmd_proc_assemble_packet` (12 bytes): `{uint64_t base_addr; uint32_t ks_opt_syntax;}` - `base_addr` is what Keystone resolves PC-relative operands against. - `ks_opt_syntax` = 0 keeps the engine default (Intel); pass a Keystone `KS_OPT_SYNTAX_*` value (1=Intel, 2=ATT, 3=NASM, 4=MASM, 5=GAS) to switch. - **Trailing data:** `(datalen - 12)` bytes of asm text. NUL-termination not required. - **Response on success:** `CMD_SUCCESS`, `struct cmd_proc_assemble_ok` (8 bytes, `{uint32_t byte_len; uint32_t insn_count;}`), then `byte_len` bytes of machine code. - **Response on assembler error:** `CMD_ERROR`, `struct cmd_proc_assemble_err` (8 bytes, `{uint32_t ks_errno; uint32_t msg_len;}`), then `msg_len` bytes of the human-readable Keystone error (`ks_strerror`). - **Caveats:** Keystone here was built with `LLVM_ENABLE_THREADS=OFF`; the payload's command dispatcher serialises clients so this is fine for the current usage, but concurrent assembles would need an external mutex. #### `CMD_PROC_SCAN_AOB = 0xBDAA0501` (proc.c:1582-1691) - **requires auth bit 1** - **Request body:** `struct cmd_proc_scan_aob_packet` (22 bytes): `{pid, address, length, max_matches(u8), stop_flag(u8), pattern_length(u32)}`. - **Trailing data:** `pattern_length` bytes. Each byte is either an exact value or the wildcard marker `0x??` (see handler at proc.c:1638). - **Response:** `uint32_t num`, then `num × uint64_t` addresses. #### `CMD_PROC_SCAN_AOB_MULTI = 0xBDAA0502` (proc.c:1693-1860) - **requires auth bit 1** - **Request body:** `struct cmd_proc_scan_aob_multi_packet` (21 bytes): `{pid, address, length, stop_flag(u8), patterns_length(u32)}`. - **Trailing data:** `patterns_length` bytes of concatenated patterns; each pattern is prefixed by its own `uint8_t length` byte. - **Response:** `uint32_t num_patterns`, then for each pattern: `uint32_t num`, `num × uint64_t`. #### `CMD_PROC_AUTH = 0xBDAACCFF` (proc.c:1862-1900) See §1.8. #### `CMD_PROC_SCAN_START = 0xBDAACC01` (proc.c:1922-2044) - **requires auth bit 1** - **Request body:** `struct cmd_proc_scan_start_packet` (23 bytes): `{pid, address, length, valueType(u8), compareType(u8), alignment(u8), lenData(u32)}`. - **Trailing data:** `lenData` bytes of seed value(s). - **Response:** `CMD_SUCCESS`. Server now holds a scan session keyed on `pid`. #### `CMD_PROC_SCAN_COUNT = 0xBDAACC02` (proc.c:2046-2221) - **requires auth bit 1** Narrows the current scan set by applying a second comparison. - **Request body:** `struct cmd_proc_scan_count_packet` (18 bytes): `{pid, base_address, valueType(u8), compareType(u8), lenData(u32)}`. - **Trailing data:** `lenData` bytes. - **Response:** `uint32_t num_results` (the new cardinality). #### `CMD_PROC_SCAN_GET = 0xBDAACC03` (proc.c:2223-2265) - **requires auth bit 1** - **Request body:** `struct cmd_proc_scan_get_packet` (8 bytes, `{pid, count}`). - **Response:** `CMD_SUCCESS`, `uint32_t actual`, then `actual × uint64_t`. For the enum values used by `valueType` and `compareType`, see §7. --- ### 2.3 Debug commands (`0xBDBBxxxx`) - dispatched by `debug_handle` (kern.c:779-803) All debug commands (except `ATTACH`, see §1.5) require an already-attached debug session. The dispatcher returns early with no response if none exists. #### `CMD_DEBUG_ATTACH = 0xBDBB0001` (kern.c:98-166, via server.c:188) - **Request body:** `struct cmd_debug_attach_packet` (8 bytes, `{pid, client_ip}`). - **Response:** `CMD_SUCCESS`, or `CMD_ALREADY_DEBUG` / `CMD_ERROR`. - **Side effect:** server opens outbound TCP to client on port 755 (see §1.9) and `ptrace(PT_ATTACH)`-es the target. `debug_attach_handle` at kern.c:92-96 is a stub that always replies `CMD_ERROR` and returns `1`; it exists so the switch at kern.c:784 is complete, but is unreachable in normal flow because `server.c:188` intercepts `CMD_DEBUG_ATTACH` before `cmd_handler` runs and routes to `debug_attach_handle_svc` instead. #### `CMD_DEBUG_DETACH = 0xBDBB0002` (kern.c:216-461) - **Request body:** none. - **Response:** `CMD_SUCCESS`. Clears all breakpoints/watchpoints, resumes target, closes port 755 connection. #### `CMD_DEBUG_SET_BREAKPOINT = 0xBDBB0003` (kern.c:463-501) - **Request body:** `struct cmd_debug_breakpt_packet` (16 bytes, `{index, enabled, address}`). - **Response:** `CMD_SUCCESS` or `CMD_INVALID_INDEX`. - **Backend:** software breakpoint - writes `0xCC` at `address`, saves original byte. `MAX_BREAKPOINTS = 30` (protocol.h:438). #### `CMD_DEBUG_SET_WATCHPOINT = 0xBDBB0004` (kern.c:504-568) - **Request body:** `struct cmd_debug_watchpt_packet` (24 bytes): `{index, enabled, length, breaktype, address}`. - **Response:** `CMD_SUCCESS` or `CMD_INVALID_INDEX`. - **Backend:** hardware breakpoint via DR0-DR3 / DR7. `MAX_WATCHPOINTS = 4` (protocol.h:439). `breaktype` uses `DBREG_DR7_*` encoding (debug.h:119-126): `EXEC=0`, `WRONLY=1`, `RDWR=3`; length `1/2/4/8` via `DBREG_DR7_LEN_*`. #### `CMD_DEBUG_GET_THREAD_LIST = 0xBDBB0005` (kern.c:570-604) - **Request body:** none. - **Response:** `CMD_SUCCESS`, `uint32_t num`, `num × uint32_t lwpid`. #### `CMD_DEBUG_SUSPEND_THREAD = 0xBDBB0006` (kern.c:606-614) - **Request body:** `struct cmd_debug_stopthr_packet` (4 bytes, `lwpid`). - **Response:** `CMD_SUCCESS`. #### `CMD_DEBUG_RESUME_THREAD = 0xBDBB0007` (kern.c:616-624) - **Request body:** `struct cmd_debug_resumethr_packet` (4 bytes, `lwpid`). - **Response:** `CMD_SUCCESS`. #### `CMD_DEBUG_GETREGS = 0xBDBB0008` (kern.c:626-639) - **Request body:** `struct cmd_debug_getregs_packet` (4 bytes, `lwpid`). - **Response:** `CMD_SUCCESS`, `struct __reg64` (184 bytes; debug.h:11-38). #### `CMD_DEBUG_SETREGS = 0xBDBB0009` (kern.c:641-655) - **Request body:** `struct cmd_debug_setregs_packet` (8 bytes, `{lwpid, length}`). - **Trailing data:** `length` bytes - must be `sizeof(struct __reg64)`. - **Response:** `CMD_SUCCESS`. #### `CMD_DEBUG_GETFPREGS = 0xBDBB000A` (kern.c:657-670) - **Request body:** `struct cmd_debug_getregs_packet` (4 bytes, `lwpid`). - **Response:** `CMD_SUCCESS`, `struct savefpu_ymm` (512 bytes; debug.h:88-97). #### `CMD_DEBUG_SETFPREGS = 0xBDBB000B` (kern.c:672-686) - **Request body:** `struct cmd_debug_setregs_packet` (8 bytes). - **Trailing data:** `length` bytes - `sizeof(struct savefpu_ymm)`. - **Response:** `CMD_SUCCESS`. #### `CMD_DEBUG_GETDBREGS = 0xBDBB000C` (kern.c:688-701) - **Request body:** `struct cmd_debug_getregs_packet` (4 bytes). - **Response:** `CMD_SUCCESS`, `struct __dbreg64` (128 bytes; 16 × uint64_t). #### `CMD_DEBUG_SETDBREGS = 0xBDBB000D` (kern.c:703-717) - **Request body:** `struct cmd_debug_setregs_packet` (8 bytes). - **Trailing data:** `length` bytes (`sizeof(struct __dbreg64)`). - **Response:** `CMD_SUCCESS`. #### `CMD_DEBUG_CONTINUE = 0xBDBB0010` (kern.c:719-729) - **Request body:** `struct cmd_debug_stopgo_packet` (4 bytes, `stop`). - `stop == 0` → resume all threads. `stop == 1` → enter single-step / halted mode. - **Response:** `CMD_SUCCESS`. #### `CMD_DEBUG_THREAD_INFO = 0xBDBB0011` (kern.c:731-744) - **Request body:** `struct cmd_debug_thrinfo_packet` (4 bytes, `lwpid`). - **Response:** `CMD_SUCCESS`, `struct cmd_debug_thrinfo_response` (40 bytes, `{lwpid, priority, name[32]}`). #### `CMD_DEBUG_STEP = 0xBDBB0012` (kern.c:746-756) - **Request body:** none. - **Response:** `CMD_SUCCESS`. Single-step all threads of the debugged process. #### `CMD_DEBUG_STEP_THREAD = 0xBDBB0013` (kern.c:758-776) - **Request body:** `struct cmd_debug_stopthr_packet` (4 bytes, `lwpid`). - **Response:** `CMD_SUCCESS`. #### `CMD_DEBUG_PROCESS_STOP = 0xBDBB0500` (kern.c:54-89) - **Request body:** 5 bytes of raw data (no named struct): `u32 pid; u8 state;` - `state ∈ {0,1,2}` maps to `{resume, stop, kill}` (kern.c:61-77). Returns `CMD_ERROR` if `state > 2` or `pid == 0`. - **Response:** `CMD_SUCCESS`, or `CMD_DATA_NULL`, or `CMD_ERROR`. - **Behavior:** If a debug session is active (`g_debugging != 0`), stashes a pending signal into `g_pending_signal` for the next event dispatch (`0 → 0`, `1 → SIGSTOP(17)`, `2 → SIGKILL(9)`). Otherwise calls `kill(pid, sig)` directly with `SIGSTOP(19 - FreeBSD SIGSTOP)`, `SIGTSTP(17)`, or `SIGKILL(9)`. --- ### 2.4 Kernel R/W commands (`0xBDCCxxxx`) - dispatched by `kern_handle` (console.c:60-67) #### `CMD_KERN_BASE = 0xBDCC0001` (console.c:7-13) - **Request body:** none. - **Response:** `CMD_SUCCESS`, `uint64_t` kernel base. #### `CMD_KERN_READ = 0xBDCC0002` (console.c:16-35) - **Request body:** `struct cmd_kern_read_packet` (12 bytes, `{address, length}`). - **Response:** `CMD_SUCCESS`, then `length` bytes of kernel memory. #### `CMD_KERN_WRITE = 0xBDCC0003` (console.c:38-57) - **Request body:** `struct cmd_kern_write_packet` (12 bytes, `{address, length}`). - **Trailing data:** `length` bytes to write. - **Response pattern** (unique - note the **two** status messages): 1. `CMD_SUCCESS` sent *before* the server receives the write payload (console.c:51). 2. `CMD_SUCCESS` sent *after* the write completes (console.c:54). --- ### 2.5 Console commands (`0xBDDDxxxx`) - dispatched by `console_handle` (debug.c:121-140) #### `CMD_CONSOLE_REBOOT = 0xBDDD0001` (debug.c:110-118) - **Request body:** none. - **Response:** none - connection is closed; system reboots via `syscall(112, 1, 0)`. #### `CMD_CONSOLE_END = 0xBDDD0002` (debug.c:133-136) - **Request body:** none. - **Response:** none - handler returns `1`, which tears down the connection. #### `CMD_CONSOLE_PRINT = 0xBDDD0003` (debug.c:58-82) - **Request body:** `uint32_t length`, then `length` bytes of UTF-8 text. - **Response:** `CMD_SUCCESS`. #### `CMD_CONSOLE_NOTIFY = 0xBDDD0004` (debug.c:85-107) - **Request body:** `struct cmd_console_notify_packet` (8 bytes, `{messageType, length}`). - **Trailing data:** `length` bytes of UTF-8 text (must include trailing `\0`). - **Response:** `CMD_SUCCESS`. - **Effect:** calls `sceSysUtilSendSystemNotificationWithText(messageType, text)`; `messageType = 222` is the generic icon used by main.c:39. #### `CMD_CONSOLE_INFO = 0xBDDD0005` (debug.c:129-132) - **Request body:** none. - **Response:** `CMD_SUCCESS`. No side effect (ping variant). #### `0xBDDD0006` - foreground-app metadata (console.c, `console_foreground_app_handle`) Identify the currently-foregrounded game and return its metadata (pid, titleid, contentid, process name, and version). Resolves the version from the title's `param.sfo`. **Raw literal in `console_handle`'s switch - no `CMD_*` macro** (same rationale as `0xBDAA0024`). - **Request body:** none. - **Response:** `CMD_SUCCESS`, `struct cmd_console_foreground_app_response` (140 bytes): ``` u32 pid; char titleid[16]; char contentid[64]; char name[40]; char app_ver[16]; // "XX.YY" version string from param.sfo ``` - **Behaviour:** if there is no foreground game, all fields are zeroed (`pid==0`). - **Version selection:** when both `APP_VER` and `VERSION` are present in `param.sfo`, the server returns the lexically larger of the two so that packer-mistakes (where the bundled `VERSION` overtakes `APP_VER`) still yield the higher-precedence version string. --- ## 3. In-process kernel/target primitives Unlike PS4-class debuggers that ship a separate kernel module, ps5debug-NG runs the entire wire-protocol server inside SceShellCore (after the umtx-loaded installer injects the embedded debugger ELF as a SceShellCore-internal thread). All "kernel-side" operations are therefore performed from userland using primitives supplied by [ps5-payload-sdk](https://github.com/ps5-payload-dev/sdk) and short kernel patches applied at installer time: | Concern | How ps5debug-NG implements it | |--------------------------------------|----------------------------------------------------------------------------| | Process enumeration (`CMD_PROC_LIST`)| `sysctl(KERN_PROC, KERN_PROC_PROC)` from inside SceShellCore. | | Target memory R/W | `mdbg_call` syscalls + the SDK's `kernel_copyin` / `kernel_copyout`. | | Target `mprotect` / alloc / free | `mdbg_call` PT_TRACE_ME-tainted variant + KEX-state shim from installer. | | Kernel base / kernel R/W | `kernel_get_root` and `kernel_copyin/out` (toggles `CR0.WP` internally). | | RPC stub install + call (`CMD_PROC_INSTALL` / `CMD_PROC_CALL`) | In-target thread creation through `scePthreadCreate` after the installer caches the libkernel.sprx symbol offsets. | | ELF inject (`CMD_PROC_ELF`, `CMD_PROC_ELF_RPC`) | Self-relocating loader inside the debugger; mmap target pages via remote-mmap RPC stub; jump or return entry. | | Console reboot / notify / print | `sceSysUtilSendSystemNotificationWithText`, `kern_reboot`, `klog_printf`. | | Jailbreak (uid=0, escape prison) | Performed by the installer before injecting the debugger; not reachable via the wire protocol. | The installer's role is bootstrap-only: find SceShellCore, apply the FW-specific kpatch (see [installer/source/main.c](installer/source/main.c)), set up the target's KEX state and syscall-origin filter, then call the SCE-side `inject_remote_thread_create` primitive to hand control off to the embedded debugger. After that the installer thread exits and the debugger services TCP. --- ## 4. Full command ID table | Hex | Name | Handler | Auth? | |--------------|----------------------------------|---------------------------------|-------| | `0xBD000001` | `CMD_VERSION` | inline (server.c:53) | | | `0xBD000500` | `CMD_FW_VERSION` | inline (server.c:55) | | | `0xBD000501` | `CMD_BRANDING` | inline (server.c:59) | | | `0xBD000502` | `CMD_PLATFORM_ID` | inline (server.c:64) | | | `0xBDAA0001` | `CMD_PROC_LIST` | `proc_list_handle` | | | `0xBDAA0002` | `CMD_PROC_READ` | `proc_read_handle` | | | `0xBDAA0003` | `CMD_PROC_WRITE` | `proc_write_handle` | | | `0xBDAA0004` | `CMD_PROC_MAPS` | `proc_maps_handle` | | | `0xBDAA0005` | `CMD_PROC_INTALL` | `proc_install_handle` | | | `0xBDAA0006` | `CMD_PROC_CALL` | `proc_call_handle` | | | `0xBDAA0007` | `CMD_PROC_ELF` | `proc_elf_handle` | | | `0xBDAA0008` | `CMD_PROC_PROTECT` | `proc_protect_handle` | | | `0xBDAA0009` | `CMD_PROC_SCAN` | `proc_scan_handle` | bit 1 | | `0xBDAA000A` | `CMD_PROC_INFO` | `proc_info_handle` | | | `0xBDAA000B` | `CMD_PROC_ALLOC` | `proc_alloc_handle` | | | `0xBDAA000C` | `CMD_PROC_FREE` | `proc_free_handle` | | | `0xBDAA000E` | `CMD_PROC_ALLOC_HINTED` | `proc_alloc_hinted_handle` | | | `0xBDAA0010` | `CMD_PROC_ELF_RPC` | `proc_elf_rpc_handle` | | | `0xBDAA0020` | `CMD_PROC_DISASM_REGION` | `proc_disasm_region_handle` | | | `0xBDAA0021` | `CMD_PROC_EXTRACT_CODE_XREFS` | `proc_extract_code_xrefs_handle`| | | `0xBDAA0022` | `CMD_PROC_FIND_XREFS_TO` | `proc_find_xrefs_to_handle` | | | `0xBDAA0023` | `CMD_PROC_READ_STACK` | `proc_read_stack_handle` | | | `0xBDAA0024` | _(raw literal, no macro)_ | `proc_assemble_handle` | | | `0xBDAA0501` | `CMD_PROC_SCAN_AOB` | `proc_scan_aob_handle` | bit 1 | | `0xBDAA0502` | `CMD_PROC_SCAN_AOB_MULTI` | `proc_scan_aob_multi_handle` | bit 1 | | `0xBDAACCFF` | `CMD_PROC_AUTH` | `proc_auth_handle` | | | `0xBDAACC01` | `CMD_PROC_SCAN_START` | `proc_scan_start_handle` | bit 1 | | `0xBDAACC02` | `CMD_PROC_SCAN_COUNT` | `proc_scan_count_handle` | bit 1 | | `0xBDAACC03` | `CMD_PROC_SCAN_GET` | `proc_scan_get_handle` | bit 1 | | `0xBDAACC06` | `CMD_PROC_NOP` | inline (server.c:68) | | | `0xBDBB0001` | `CMD_DEBUG_ATTACH` | `debug_attach_handle_svc` | | | `0xBDBB0002` | `CMD_DEBUG_DETACH` | `debug_detach_handle` | | | `0xBDBB0003` | `CMD_DEBUG_SET_BREAKPOINT` | `debug_set_breakpoint_handle` | | | `0xBDBB0004` | `CMD_DEBUG_SET_WATCHPOINT` | `debug_set_watchpoint_handle` | | | `0xBDBB0005` | `CMD_DEBUG_GET_THREAD_LIST` | `debug_get_thread_list_handle` | | | `0xBDBB0006` | `CMD_DEBUG_SUSPEND_THREAD` | `debug_suspend_thread_handle` | | | `0xBDBB0007` | `CMD_DEBUG_RESUME_THREAD` | `debug_resume_thread_handle` | | | `0xBDBB0008` | `CMD_DEBUG_GETREGS` | `debug_getregs_handle` | | | `0xBDBB0009` | `CMD_DEBUG_SETREGS` | `debug_setregs_handle` | | | `0xBDBB000A` | `CMD_DEBUG_GETFPREGS` | `debug_getfpregs_handle` | | | `0xBDBB000B` | `CMD_DEBUG_SETFPREGS` | `debug_setfpregs_handle` | | | `0xBDBB000C` | `CMD_DEBUG_GETDBREGS` | `debug_getdbregs_handle` | | | `0xBDBB000D` | `CMD_DEBUG_SETDBREGS` | `debug_setdbregs_handle` | | | `0xBDBB0010` | `CMD_DEBUG_CONTINUE` | `debug_continue_handle` | | | `0xBDBB0011` | `CMD_DEBUG_THREAD_INFO` | `debug_thread_info_handle` | | | `0xBDBB0012` | `CMD_DEBUG_STEP` | `debug_step_handle` | | | `0xBDBB0013` | `CMD_DEBUG_STEP_THREAD` | `debug_step_thread_handle` | | | `0xBDBB0500` | `CMD_DEBUG_PROCESS_STOP` | `debug_process_stop_handle` | | | `0xBDCC0001` | `CMD_KERN_BASE` | `kern_base_handle` | | | `0xBDCC0002` | `CMD_KERN_READ` | `kern_read_handle` | | | `0xBDCC0003` | `CMD_KERN_WRITE` | `kern_write_handle` | | | `0xBDDD0001` | `CMD_CONSOLE_REBOOT` | `console_reboot_handle` | | | `0xBDDD0002` | `CMD_CONSOLE_END` | inline (debug.c:133) | | | `0xBDDD0003` | `CMD_CONSOLE_PRINT` | `console_print_handle` | | | `0xBDDD0004` | `CMD_CONSOLE_NOTIFY` | `console_notify_handle` | | | `0xBDDD0005` | `CMD_CONSOLE_INFO` | inline (debug.c:129) | | | `0xBDDD0006` | _(raw literal, no macro)_ | `console_foreground_app_handle` | | --- ## 5. Request packet appendix All structs are `__attribute__((packed))`. Sizes match the `CMD_*_PACKET_SIZE` macros. | 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_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` | 20 | `u32 pid; u64 address; u32 length; u32 newprot;` | | `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_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;` (followed by asm text on the wire) | | `cmd_debug_attach_packet` | 8 | `u32 pid; u32 client_ip;` | | `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_resumethr_packet` | 4 | `u32 lwpid;` | | `cmd_debug_getregs_packet` | 4 | `u32 lwpid;` | | `cmd_debug_setregs_packet` | 8 | `u32 lwpid; u32 length;` | | `cmd_debug_stopgo_packet` | 4 | `u32 stop;` | | `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_notify_packet` | 8 | `u32 messageType; u32 length;` | ## 6. Response packet appendix | Struct | Size | Fields | |-----------------------------------------|------|---------------------------------------------------------------------------| | `cmd_proc_install_response` | 8 | `u64 rpcstub;` | | `cmd_proc_call_response` | 12 | `u32 pid; u64 rpc_rax;` | | `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;` (followed by `byte_len` machine bytes) | | `cmd_proc_assemble_err` | 8 | `u32 ks_errno; u32 msg_len;` (followed by `msg_len` chars of `ks_strerror`) | | `cmd_console_foreground_app_response` | 132 | `u32 pid; char titleid[16], contentid[64], name[40], app_ver[8];` | | `cmd_debug_thrinfo_response` | 40 | `u32 lwpid, priority; char name[32];` | | `disasm_instr_entry` (streamed) | 32 | see §2.2 | --- ## 7. Enum reference ### 7.1 `cmd_proc_scan_valuetype` (protocol.h:221-234) | Value | Name | |-------|--------------------| | 0 | `valTypeUInt8` | | 1 | `valTypeInt8` | | 2 | `valTypeUInt16` | | 3 | `valTypeInt16` | | 4 | `valTypeUInt32` | | 5 | `valTypeInt32` | | 6 | `valTypeUInt64` | | 7 | `valTypeInt64` | | 8 | `valTypeFloat` | | 9 | `valTypeDouble` | | 10 | `valTypeArrBytes` | | 11 | `valTypeString` | ### 7.2 `cmd_proc_scan_comparetype` (protocol.h:236-252) | Value | Name | |-------|------------------------------------| | 0 | `cmpTypeExactValue` | | 1 | `cmpTypeFuzzyValue` | | 2 | `cmpTypeBiggerThan` | | 3 | `cmpTypeSmallerThan` | | 4 | `cmpTypeValueBetween` | | 5 | `cmpTypeIncreasedValue` | | 6 | `cmpTypeIncreasedValueBy` | | 7 | `cmpTypeDecreasedValue` | | 8 | `cmpTypeDecreasedValueBy` | | 9 | `cmpTypeChangedValue` | | 10 | `cmpTypeUnchangedValue` | | 11 | `cmpTypeUnknownInitialValue` | | 12 | `cmpTypeUnknownInitialLowValue` | ### 7.3 DR7 encoding for hardware watchpoints (debug.h:119-126) `breaktype` in `cmd_debug_watchpt_packet`: | Value | Name | |-------|-----------------------| | 0 | `DBREG_DR7_EXEC` | | 1 | `DBREG_DR7_WRONLY` | | 3 | `DBREG_DR7_RDWR` | `length` encoding (reused as DR7 length field): | Value | Bytes | |-------|-------| | 0 | 1 | | 1 | 2 | | 2 | 8 | | 3 | 4 | --- ## 8. Response framing patterns Four reply shapes cover every handler: 1. **Fixed status**: `uint32_t status` (usually `CMD_SUCCESS`). Used by writes, breakpoint/watchpoint management, thread suspend/resume, etc. 2. **Status + fixed struct**: `uint32_t status` then a `struct cmd_*_response` of known size. Used by info/install/call/alloc. 3. **Status + count + array**: `uint32_t status`, `uint32_t count`, `count × element`. Used by list, thread list, maps, scan results. 4. **Streamed with sentinel**: `uint32_t status`, then a sequence of fixed-size records terminated by an all-`0xFF` sentinel. Only `CMD_PROC_DISASM_REGION`. The `CMD_KERN_WRITE` handler is the only exception: it sends `CMD_SUCCESS` *twice* (before and after the data phase). Clients must consume both `u32`s. --- ## 9. Known source-code oddities Documented here so a developer doesn't mistake them for bugs: - `CMD_PROC_INTALL` is misspelled (missing `S`). Preserved on the wire. - `debug_attach_handle` (kern.c:92-96) is a stub that always returns `CMD_ERROR`; the real attach path is `debug_attach_handle_svc`, which `server.c:188` intercepts `CMD_DEBUG_ATTACH` to call directly. Both declarations remain in `include/kern.h`. - `struct debug_interrupt_packet` is `packed` - the compiler warns about `savefpu` being offset 224 instead of a 64-byte boundary. The layout is intentional; clients rely on `DEBUG_INTERRUPT_PACKET_SIZE = 0x4A0` exactly. - `SYS_CONSOLE_CMD_JAILBREAK` has no corresponding `CMD_*` opcode: it is invoked locally by the payload at startup (main.c:22) and is not reachable over the network. - The disassembler amalgamation (`third_party/zydis/Zydis.c`) is compiled with `-O3 -DNDEBUG -w -DZYAN_NO_LIBC`; the rest of the debugger is `-O2`. This is a deliberate Makefile override (debugger/Makefile:45-46). --- *This document reflects the `ps5debug-NG v1.2.2` payload (see `debugger/source/meta.c:27`). Citations elsewhere may point at line numbers that drift as the source evolves - when in doubt, the source under `common/include/protocol.h`, `debugger/source/`, and `installer/source/` is the authoritative reference.*