Files
Pharaoh2k 23f1471e62 Document content-verified writes and capability 1.1
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.
2026-07-28 12:32:09 +00:00

85 KiB

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:

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_WRITEs 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_ts 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_ts.)

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 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_ts (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.