diff --git a/PROTOCOL.md b/PROTOCOL.md index 8884387..529539e 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -20,7 +20,7 @@ This document reflects `ps5debug-NG v1.2.6` (`common/include/version.h`, | Symbol | Value | Source | |--------------------|----------------------------------|----------------------------------------------------------| | protocol version | `"1.3"` | `meta.c` `handle_version` (local `char ver[]="1.3"`) | -| branding string | `"ps5debug-NG by OSR v1.2.6"` | `version.h` `PS5DEBUG_NG_BRAND_STR` via `meta.c` `handle_branding` | +| branding string | `"ps5debug-NG by OSR v1.2.8\01.0"` | `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`) | @@ -223,9 +223,17 @@ per-namespace `switch` statements. Three opcodes have no symbolic name at all: #### `CMD_BRANDING = 0xBD000501` - **Request body:** none. -- **Response:** `uint32_t length`, then `length` bytes of the branding string - ("ps5debug-NG by OSR v1.2.6"). No status word precedes it. (The client calls - this `CMD_EXT_VERSION`.) +- **Response:** `uint32_t length`, then `length` bytes: the human branding string + (`"ps5debug-NG by OSR v1.2.8"`), a single `NUL`, then a **capability level** + string (`"1.0"`), 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 only + the unchanged brand; capability-aware clients read the bytes past the `NUL` 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). + Built from `PS5DEBUG_NG_BRAND_STR "\0" "1.0"` in `meta.c` `handle_branding` - + the literals are split (`"\0" "1.0"`) so `\0` is a NUL byte, not the octal + escape `\01`. #### `CMD_PLATFORM_ID = 0xBD000502` - **Request body:** none. @@ -260,6 +268,28 @@ data" is read from the socket by the handler after a leading `CMD_SUCCESS`. `length` bytes in 64 KiB chunks, then `CMD_SUCCESS` again. **Two** status words (see 8). +#### `0xBDAACC04` (`proc_write_multi_handle`) +Bulk write - the write counterpart to `0xBDAACC03` (bulk read). Collapses a +freeze / multi-poke loop of N single `CMD_PROC_WRITE`s into one exchange. Dispatched +by `proc_handle`; **not** auth-gated (mirrors single write). +- **Request body:** `struct cmd_proc_write_multi_packet` (12 bytes, + `{ uint32_t pid; uint32_t count; uint32_t flags; }`). `flags` bit 0 + (`PROC_WRITE_MULTI_F_STATUS`) requests a per-entry status array in the response. +- **Trailing data:** after the server's first `CMD_SUCCESS`, the client streams + `count` entries, each `{ uint64_t address; uint32_t length; 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` = write failed). Finally 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, individual write + failures are not reported (same as single `CMD_PROC_WRITE`, whose underlying + `proc_write_mem` is `void`). 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 diff --git a/common/include/proc.h b/common/include/proc.h index a61cf80..74283bf 100644 --- a/common/include/proc.h +++ b/common/include/proc.h @@ -13,6 +13,7 @@ int proc_list_handle(int fd, struct cmd_packet *packet); int proc_read_handle(int fd, struct cmd_packet *packet); int proc_read_stack_handle(int fd, struct cmd_packet *packet); int proc_write_handle(int fd, struct cmd_packet *packet); +int proc_write_multi_handle(int fd, struct cmd_packet *packet); int proc_maps_handle(int fd, struct cmd_packet *packet); int proc_info_handle(int fd, struct cmd_packet *packet); int proc_protect_handle(int fd, struct cmd_packet *packet); diff --git a/common/include/protocol.h b/common/include/protocol.h index 6179bef..41a99d3 100644 --- a/common/include/protocol.h +++ b/common/include/protocol.h @@ -112,6 +112,19 @@ struct cmd_proc_write_packet { uint32_t length; } __attribute__((packed)); +/* Bulk write / freeze batching (opcode 0xBDAACC04). Header body below is + followed by `count` streamed entries, each { uint64 address; uint32 length; + bytes }. flags bit 0 asks the server for a per-entry status byte + array (0 = ok) sent just before the trailing CMD_SUCCESS. */ +struct cmd_proc_write_multi_packet { + uint32_t pid; + uint32_t count; + uint32_t flags; +} __attribute__((packed)); +#define PROC_WRITE_MULTI_F_STATUS 0x1u +#define PROC_WRITE_MULTI_MAX_COUNT 0xFFFFu +#define PROC_WRITE_MULTI_MAX_ENTRY 0x100000u + struct cmd_proc_maps_packet { uint32_t pid; } __attribute__((packed)); diff --git a/common/include/version.h b/common/include/version.h index 785ab36..b3db0b3 100644 --- a/common/include/version.h +++ b/common/include/version.h @@ -2,5 +2,5 @@ #pragma once -#define PS5DEBUG_NG_VERSION_STR "1.2.7" +#define PS5DEBUG_NG_VERSION_STR "1.2.8" #define PS5DEBUG_NG_BRAND_STR "ps5debug-NG by OSR v" PS5DEBUG_NG_VERSION_STR diff --git a/debugger/source/meta.c b/debugger/source/meta.c index a7850e4..f44177d 100644 --- a/debugger/source/meta.c +++ b/debugger/source/meta.c @@ -25,8 +25,13 @@ int handle_version(int fd, struct cmd_packet *packet) { int handle_branding(int fd, struct cmd_packet *packet) { (void)packet; - char brand[] = PS5DEBUG_NG_BRAND_STR; - uint32_t len = (uint32_t)(sizeof(brand) - 1); + /* Human brand, a NUL, then the capability LEVEL. C-string clients stop at the + NUL so the displayed brand is unchanged; capability-aware clients read the + level past it. NOTE: the literals are split ("\0" "1.0") on purpose - "\01.0" + would be parsed as the octal escape \01, not a NUL followed by "1.0". Bump the + level (and add the feature on the client) as new capabilities are added. */ + static const char brand[] = PS5DEBUG_NG_BRAND_STR "\0" "1.0"; + uint32_t len = (uint32_t)(sizeof(brand) - 1); net_send_all(fd, &len, sizeof(uint32_t)); net_send_all(fd, brand, (int)len); return 0; diff --git a/debugger/source/proc.c b/debugger/source/proc.c index 76332ef..6d76367 100644 --- a/debugger/source/proc.c +++ b/debugger/source/proc.c @@ -595,6 +595,108 @@ int proc_write_handle(int fd, struct cmd_packet *packet) { return 0; } +/* Like proc_write_mem but reports 0 on success / 1 on failure. Used only by + the bulk-write handler so it can fill an optional per-entry status array; + proc_write_mem and its breakpoint callers stay void. The signal is real: + proc_ptwalk_write returns 0/1, and the mdbg fallback exposes bytes-written + through its arg5 out-param (sys_proc_rw_inner sets it to length on success). */ +static int proc_write_mem_status(uint32_t pid, uint64_t addr, uint64_t len, const void *buf) { + static int s_fw_needs_dmap = -1; + if (s_fw_needs_dmap < 0) + s_fw_needs_dmap = ((kernel_get_fw_version() & 0xffff0000u) >= 0x08400000u) ? 1 : 0; + + if (s_fw_needs_dmap && proc_ptwalk_write(pid, addr, len, buf) == 0) + return 0; + + uint64_t wrote = 0; + sys_proc_rw_w1((uint64_t)pid, addr, len, (void *)buf, (uint64_t)(uintptr_t)&wrote); + return (wrote == len) ? 0 : 1; +} + +int proc_write_multi_handle(int fd, struct cmd_packet *packet) { + struct cmd_proc_write_multi_packet *mp = + (struct cmd_proc_write_multi_packet *)packet->data; + if (!mp) { + net_send_int32(fd, CMD_DATA_NULL); + return 1; + } + + uint32_t pid = mp->pid; + uint32_t count = mp->count; + int want_status = (mp->flags & PROC_WRITE_MULTI_F_STATUS) ? 1 : 0; + + if (count > PROC_WRITE_MULTI_MAX_COUNT) { + net_send_int32(fd, CMD_ERROR); + return 1; + } + + uint8_t *buf = (uint8_t *)net_alloc_buffer(0x10000); + if (!buf) { + net_send_int32(fd, CMD_DATA_NULL); + return 1; + } + + uint8_t *status = NULL; + if (want_status && count > 0) { + status = (uint8_t *)net_alloc_buffer(count); + if (!status) { + free(buf); + net_send_int32(fd, CMD_DATA_NULL); + return 1; + } + } + + net_send_int32(fd, CMD_SUCCESS); + + for (uint32_t i = 0; i < count; i++) { + uint8_t hdr[12]; + if (net_recv_all(fd, hdr, 12, 1) < 0) { + /* socket broken mid-stream; bail without further sends */ + if (status) free(status); + free(buf); + return 1; + } + uint64_t addr; + uint32_t len32; + memcpy(&addr, hdr, 8); + memcpy(&len32, hdr + 8, 4); + + if (len32 > PROC_WRITE_MULTI_MAX_ENTRY) { + /* protocol violation: we can't know how many data bytes follow, + so the stream is no longer trustworthy - abort the command. */ + if (status) free(status); + free(buf); + net_send_int32(fd, CMD_ERROR); + return 1; + } + + uint64_t length = len32; + uint64_t a = addr; + uint8_t failed = 0; + while (length > 0) { + uint64_t to_recv = (length > 0x10000ULL) ? 0x10000ULL : length; + if (net_recv_all(fd, buf, (int)to_recv, 1) < 0) { + if (status) free(status); + free(buf); + return 1; + } + if (proc_write_mem_status(pid, a, to_recv, buf) != 0) + failed = 1; + a += to_recv; + length -= to_recv; + } + if (status) status[i] = failed; + } + + if (status) { + net_send_all(fd, status, (int)count); + free(status); + } + net_send_int32(fd, CMD_SUCCESS); + free(buf); + return 0; +} + int proc_maps_handle(int fd, struct cmd_packet *packet) { struct cmd_proc_maps_packet *mp = (struct cmd_proc_maps_packet *)packet->data; @@ -966,6 +1068,7 @@ int proc_handle(int fd, struct cmd_packet *packet, unsigned char client_idx) { case 0xBDAA0023u: return proc_read_stack_handle(fd, packet); case 0xBDAA0024u: return proc_assemble_handle(fd, packet); case 0xBDAA0003u: return proc_write_handle(fd, packet); + case 0xBDAACC04u: return proc_write_multi_handle(fd, packet); case 0xBDAA0004u: return proc_maps_handle(fd, packet); case 0xBDAA0005u: return proc_install_handle(fd, packet); case 0xBDAA0006u: return proc_call_handle(fd, packet);