ps5dbg 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ps5dbg/__init__.py ADDED
@@ -0,0 +1,39 @@
1
+ """ps5dbg — cross-platform Python client + CLI for the ps5debug-NG wire protocol.
2
+
3
+ Public API:
4
+
5
+ from ps5dbg import PS5Debug
6
+
7
+ with PS5Debug("192.168.1.10") as ps5: # your PS5's LAN IP
8
+ print(ps5.fw_version()) # -> 550
9
+ for p in ps5.procs():
10
+ print(p.pid, p.name)
11
+
12
+ Host resolution: pass host= explicitly, OR set the PS5_HOST env var (then
13
+ PS5Debug() with no argument uses it).
14
+ """
15
+ from __future__ import annotations
16
+
17
+ from .client import PS5Debug
18
+ from .errors import (
19
+ AlreadyAttached,
20
+ AuthFailed,
21
+ BadStatus,
22
+ ConnectionLost,
23
+ PS5DbgError,
24
+ ProtocolError,
25
+ ScanAuthRequired,
26
+ )
27
+
28
+ __version__ = "0.1.0"
29
+ __all__ = [
30
+ "PS5Debug",
31
+ "PS5DbgError",
32
+ "ConnectionLost",
33
+ "ProtocolError",
34
+ "BadStatus",
35
+ "AuthFailed",
36
+ "ScanAuthRequired",
37
+ "AlreadyAttached",
38
+ "__version__",
39
+ ]
ps5dbg/__main__.py ADDED
@@ -0,0 +1,9 @@
1
+ """Allow ``python -m ps5dbg``."""
2
+ from __future__ import annotations
3
+
4
+ import sys
5
+
6
+ from .cli import main
7
+
8
+ if __name__ == "__main__":
9
+ sys.exit(main())
ps5dbg/auth.py ADDED
@@ -0,0 +1,109 @@
1
+ """CMD_PROC_AUTH handshake for ps5debug-NG (PROTOCOL.md §1.8, auth.c).
2
+
3
+ The server gates the stateful scan trio (SCAN_START/COUNT/GET) behind a
4
+ global flag word ``g_proc_auth_state``. To set the scan-enabled bit, the
5
+ client performs this handshake:
6
+
7
+ 1. Client sends { u32 magic = 0xBB40E64D, u32 flags } (raw; magic not swapped).
8
+ 2. Server validates magic (bitswap32(magic) == 0x7780D98E), replies CMD_SUCCESS.
9
+ 3. Server sends u16 challenge_length (=64), then 64 challenge bytes, where
10
+ each challenge byte = (uint8_t)(r + r/255) and r is a 4-register LFSR
11
+ output (auth_lfsr_next).
12
+ 4. Client XORs the challenge against a deterministic 256-byte keystream
13
+ (the same LFSR, seeded [200,300,400,500], low byte of each of the first
14
+ 256 outputs) and sends 64 response bytes back.
15
+ 5. On byte-exact match, server sets the flag (flags & 2 -> scan-enabled)
16
+ and replies CMD_SUCCESS; else CMD_DATA_NULL.
17
+
18
+ This is NOT the classic xorshift128 — it is the custom 4-register LFSR in
19
+ ps5debug-NG's auth.c. Ported bit-for-bit from that source.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import struct
24
+
25
+ from .connection import Connection
26
+ from .constants import CMD_PROC_AUTH_MAGIC, Cmd
27
+ from .errors import AuthFailed
28
+ from .wire import recv_status, recv_u16
29
+
30
+
31
+ class _AuthLFSR:
32
+ """The 4-register LFSR from ps5debug-NG auth.c::auth_lfsr_next.
33
+
34
+ State: s1..s4 (u32 each). Each step applies a per-register
35
+ shift/mask/xor mix and returns s1 ^ s2 ^ s3 ^ s4.
36
+ """
37
+
38
+ __slots__ = ("s1", "s2", "s3", "s4")
39
+
40
+ def __init__(self, a: int = 200, b: int = 300, c: int = 400, d: int = 500) -> None:
41
+ self.s1 = a & 0xFFFFFFFF
42
+ self.s2 = b & 0xFFFFFFFF
43
+ self.s3 = c & 0xFFFFFFFF
44
+ self.s4 = d & 0xFFFFFFFF
45
+
46
+ def next(self) -> int:
47
+ s1 = self.s1
48
+ s2 = self.s2
49
+ s3 = self.s3
50
+ s4 = self.s4
51
+ # C operates in u32 throughout: every shift/xor result truncates to 32 bits.
52
+ # Python ints are arbitrary-precision, so we must mask the LEFT-shift
53
+ # operands to u32 BEFORE the xor/shift that follows, mirroring C overflow.
54
+ M = 0xFFFFFFFF
55
+ s1 = (((s1 << 18) & 0xFFF80000) ^ (((s1 ^ ((s1 << 6) & M)) & M) >> 13)) & M
56
+ s2 = (((s2 << 2) & 0xFFFFFFE0) ^ (((s2 ^ ((s2 << 2) & M)) & M) >> 27)) & M
57
+ s3 = (((s3 << 7) & 0xFFFFF800) ^ (((s3 ^ ((s3 << 13) & M)) & M) >> 21)) & M
58
+ s4 = (((s4 << 13) & 0xFFF00000) ^ (((s4 ^ ((s4 << 3) & M)) & M) >> 12)) & M
59
+ self.s1, self.s2, self.s3, self.s4 = s1, s2, s3, s4
60
+ return (s1 ^ s2 ^ s3 ^ s4) & M
61
+
62
+
63
+ def build_keystream() -> bytes:
64
+ """Build the 256-byte keystream the server XORs challenges against.
65
+
66
+ Mirrors auth.c::auth_keystream_fill: seed [200,300,400,500], then the low
67
+ byte of each of the first 256 LFSR outputs.
68
+ """
69
+ lfsr = _AuthLFSR(200, 300, 400, 500)
70
+ return bytes(lfsr.next() & 0xFF for _ in range(256))
71
+
72
+
73
+ def proc_auth(conn: Connection, flags: int = 2) -> None:
74
+ """Run the CMD_PROC_AUTH handshake.
75
+
76
+ Args:
77
+ conn: open Connection to the command server.
78
+ flags: bit field; bit 1 (value 2) enables stateful scans. Default 2.
79
+
80
+ Raises:
81
+ AuthFailed: if the server rejects the magic or the keystream answer.
82
+ """
83
+ body = struct.pack("<II", CMD_PROC_AUTH_MAGIC, flags)
84
+ conn.send_request(Cmd.PROC_AUTH, body)
85
+
86
+ # Step 2: server validates magic -> SUCCESS
87
+ st = recv_status(conn)
88
+ if not st.ok:
89
+ raise AuthFailed(f"server rejected auth magic (status {st.name})")
90
+
91
+ # Step 3: u16 challenge length (==64), then challenge bytes
92
+ chal_len = recv_u16(conn)
93
+ if chal_len != 64:
94
+ raise AuthFailed(f"unexpected challenge length {chal_len} (expected 64)")
95
+ challenge = conn.recv_exact(chal_len)
96
+
97
+ # Step 4: XOR challenge against the deterministic 256-byte keystream
98
+ # (auth.c::proc_auth_xor_keystream indexes keystream[i & 0xFF]).
99
+ keystream = build_keystream()
100
+ response = bytes(c ^ keystream[i & 0xFF] for i, c in enumerate(challenge))
101
+ conn._sendall(response) # raw bytes after the initial body
102
+
103
+ # Step 5: server checks byte-exact -> SUCCESS or DATA_NULL
104
+ st = recv_status(conn)
105
+ if not st.ok:
106
+ raise AuthFailed(f"keystream answer rejected (status {st.name})")
107
+
108
+
109
+ __all__ = ["build_keystream", "proc_auth"]