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 +39 -0
- ps5dbg/__main__.py +9 -0
- ps5dbg/auth.py +109 -0
- ps5dbg/cli.py +597 -0
- ps5dbg/client.py +309 -0
- ps5dbg/connection.py +157 -0
- ps5dbg/constants.py +260 -0
- ps5dbg/debugger.py +325 -0
- ps5dbg/discover.py +99 -0
- ps5dbg/errors.py +67 -0
- ps5dbg/klog.py +65 -0
- ps5dbg/models.py +180 -0
- ps5dbg/protocol.py +761 -0
- ps5dbg/turboscan.py +296 -0
- ps5dbg/wire.py +81 -0
- ps5dbg-0.1.0.dist-info/METADATA +249 -0
- ps5dbg-0.1.0.dist-info/RECORD +21 -0
- ps5dbg-0.1.0.dist-info/WHEEL +5 -0
- ps5dbg-0.1.0.dist-info/entry_points.txt +2 -0
- ps5dbg-0.1.0.dist-info/licenses/LICENSE +26 -0
- ps5dbg-0.1.0.dist-info/top_level.txt +1 -0
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
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"]
|