v1.2.8: bulk write CMD 0xBDAACC04 + branding capability level "1.0"

- New 0xBDAACC04 bulk-write command (proc.c proc_write_multi_handle), dispatched
  raw (no name macro). Collapses N CMD_PROC_WRITE round-trips into one: header
  {pid,count,flags} then count streamed {u64 addr,u32 len,bytes} entries; two
  CMD_SUCCESS words. Interleaved streaming keeps server memory bounded to one
  64KiB scratch. Not auth-gated (mirrors single write). Caps: count<=0xFFFF,
  entry<=0x100000 -> CMD_ERROR + abort. Opt-in per-entry status byte array via
  flags bit 0 (PROC_WRITE_MULTI_F_STATUS), using a returning proc_write_mem
  variant (ptwalk ret + mdbg bytes-written); single write + breakpoint callers
  untouched.
- CMD_BRANDING (meta.c) now returns brand + NUL + capability level "1.0" so
  capability-aware clients gate features without probing. Split literal
  "\0" "1.0" (not octal \01).
- version.h 1.2.7 -> 1.2.8. PROTOCOL.md documents both.

HW-validated (wire test). Sources not yet comment-stripped (pre-push step).
This commit is contained in:
Pharaoh2k committed 2026-06-15 03:32:03 +07:00
1 parent f8456241fc
commit fedbd4f6d7
6 files changed
+159 -7

No files matched your search

+34 -4
View File
@@ -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; <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
+1
View File
@@ -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);
+13
View File
@@ -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;
<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));
+1 -1
View File
@@ -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
+7 -2
View File
@@ -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;
+103
View File
@@ -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);