A modern, asynchronous Python client for interacting with a PlayStation 4 running a compatible debug payload.
This library provides high-level APIs for process inspection, memory access, debugging, and remote code execution over a TCP interface.
Tested with ps4debug v1.0.15 and v1.1.19.
Interacting with system memory and debugging processes can damage your device, cause instability, or lead to permanent hardware failure.
This project does not implement the payload or server running on the target device—it only provides a client for communicating with it.
You assume all responsibility for any damage, data loss, or unintended behavior resulting from the use of this library.
Code examples in this documentation are not directly runnable.
Values such as:
- IP addresses
- Process IDs (PID)
- Memory addresses will differ depending on your environment and target system.
You are expected to adapt examples to your specific setup.
- Automatic device discovery on local network
- Async TCP client with connection pooling
- Process enumeration and inspection
- Memory read/write and allocation
- Kernel memory access
- Remote procedure calls (RPC)
- ELF and raw payload injection
- Debugging session management
- Memory scanning utilities
- Console output and notifications
pip install ps4debugimportasynciofromps4debugimportPS4Debugasyncdefmain():
# Discover a PS4 running the debug payloadps4=awaitPS4Debug.discover()
# Get version infoversion=awaitps4.get_version()
print("Version:", version)
# List processesprocesses=awaitps4.get_processes()
forprocinprocesses:
print(proc)
asyncio.run(main())The PS4Debug class is the main entry point. It manages connections and exposes all high-level functionality.
fromps4debugimportPS4Debugps4=PS4Debug("192.168.0.10")Retrieve and inspect running processes:
processes=awaitps4.get_processes()
info=awaitps4.get_process_info(pid)
maps=awaitps4.get_process_maps(pid)awaitps4.send_payload(payload_bytes)awaitps4.send_elf(pid, elf_bytes)awaitps4.print("Hello from Python")
awaitps4.notify("Done")awaitps4.reboot()
kernel_base=awaitps4.get_kernel_base()
data=awaitps4.read_kernel_memory(address, length)data=awaitps4.read_memory(pid, address, length)
awaitps4.write_memory(pid, address, b"\x90\x90")The preferred way to work with memory is through MemoryContext, which manages allocation and cleanup automatically.
asyncwithps4.memory(pid, length=1024) asmem:
awaitmem.write(b"hello")
data=awaitmem.read(5)Key properties:
- Memory is allocated on
__aenter__ - Memory is always freed on
__aexit__ - Safe bounds checking is enforced for reads/writes
- Prevents leaks and invalid memory reuse
Important: Once the context exits, the memory is no longer valid on the target system.
MemoryContext supports structured data using ConstructModel:
value=awaitmem.read_model(MyModel)
awaitmem.write_model(my_model_instance)MemoryView provides a type-safe interface over memory.
view=ps4.view(pid, address)Or from an allocation:
view=mem.view()value=awaitview.uint32(offset=0x10).get()Shorthand:
flag=awaitview.boolean(offset=0x20)awaitview.uint32(offset=0x10).set(1337)Shorthand:
awaitview.uint32(offset=0x10)(1337)sub=view.offset(by=0x100)
value=awaitsub.uint16()- Integers:
int8,uint8,int16,uint16,int32,uint32,int64,uint64 - Floating point:
floating,double - Boolean:
boolean - Raw bytes:
bytes(size) - Strings:
string(length) - Structured models:
model(MyModel)
text=awaitview.string(length=32, offset=0x0)Or null-terminated:
text=awaitview.read_variable_text()awaitmem.change_protection(prot)Allocated memory can be used as an execution target:
result=awaitmem.call(params=my_model, result_model=MyReturnModel)This is a convenience wrapper around PS4Debug.call() using the allocated address.
Execute functions inside a target process:
result=awaitps4.call(
pid=pid,
address=0x12345678,
params=my_model,
return_model=MyReturnModel
)There is a CallRegisters model that can be used to get started.
Its values are serialized to 8-byte unsigned integers each.
fromps4debugimportCallRegistersregisters=CallRegisters(
rdi=1,
rsi=2,
rdx=3,
rcx=4,
r8=5,
r9=6,
)Return values can be parsed into a model using a custom base class:
fromtypingimportAnnotatedfromconstructimportInt32ulfrompydantic_constructimportConstructModelclassMyReturnModel(ConstructModel):
number: Annotated[int, Int32ul]Constraints:
- The model must inherit from
ConstructModel - The total size must not exceed 8 bytes (size of the
RAXregister) - If no return model is provided, raw bytes are returned
Debugging is exposed through an async context manager returning a DebuggingContext.
asyncwithps4.debugger(pid) asdbg:
awaitdbg.resume_process()Only one debugging session can be active at a time.
Breakpoints are managed using indexed slots.
index=awaitdbg.add_breakpoint(address=0x12345678, callback=lambdaevent: ...)You can also configure them manually:
awaitdbg.set_breakpoint(index, address, callback, enabled=True)- Limited number of breakpoint slots
- Managed internally via index and address mapping
Callbacks are async functions triggered when a breakpoint is hit:
asyncdefon_break(event):
print("Breakpoint hit at", hex(event.interrupt.regs.rip))
event.resume=True# resume execution automaticallyawaitdbg.add_breakpoint(address, on_break)You can also register a global callback:
dbg.register_callback(global_handler)Execution order:
- Global callback
- Breakpoint-specific callback
Hardware watchpoints can be configured:
awaitdbg.set_watchpoint(index, address, enabled=True)- Supports read/write monitoring
- Limited hardware slots
awaitdbg.stop_process()
awaitdbg.resume_process()
awaitdbg.kill_process()threads=awaitdbg.get_threads()
info=awaitdbg.get_thread_info(thread_id)Thread-level control exists but may depend on server-side support:
awaitdbg.stop_thread(thread_id)
awaitdbg.resume_thread(thread_id)regs=awaitdbg.get_registers(thread_id)
awaitdbg.set_registers(thread_id, regs)fp=awaitdbg.get_fp_registers(thread_id)dbg_regs=awaitdbg.get_debug_registers(thread_id)awaitdbg.single_step()Executes exactly one instruction.
- Debug events are received over a local TCP server
- Events are processed asynchronously
- Callbacks are awaited, the debugger will not continue until all callbacks ran
- Execution can optionally resume automatically via
event.resume = True(default behavior)
Memory scanning is performed using a builder-based query system.
PS4Debug.scan returns a local scanner, meaning:
- Memory is downloaded from the target
- Scanning happens on the client side
There are three ways to run scan queries.
scanner.query() returns a builder object that can run itself.
scanner=ps4.scan(pid)
results=await (
scanner
.query()
.int32()
.exact(100)
.execute()
)asyncwithscanner.executor() asq:
q.int32()
q.exact(100)The query will execute when the context is exited.
If the above two methods don't suit your needs you can simply create a builder.
builder=ScanBuilder()
builder.int32().exact(100)
scanner.execute(builder)The ScanBuilder allows incremental query construction:
builder=scanner.query()
builder.int32().bigger(100).aligned(True)
results=awaitbuilder.execute().int32().exact(1337).int32().between(low=100, high=200).increased(to=150)
.decreased(to=50)
.changed(None).unknown_initial_value().bounds(start, end).only_module("libSce.*").pause(True)asyncforaddr, valueinscanner.query().int32().exact(100).execute_iter():
print(hex(addr), value)- Supports large result sets
- Updates internal scan state automatically
Each session tracks:
initialvaluespreviousvalues
Subsequent scans operate as refinements rather than full scans.
Operations may raise multiple exception types depending on the failure source:
PS4DebugException– protocol-level or server-side failuresValueError,RuntimeError– invalid usage or stateasyncioexceptions – timeouts, cancellations- Parsing/validation errors from underlying libraries (e.g. model decoding from
Pydanticorconstruct)
Example:
fromps4debug.exceptionsimportPS4DebugExceptiontry:
awaitps4.reboot()
exceptPS4DebugExceptionase:
print("Operation failed:", e)This project is licensed under the 0BSD License.
You are free to use, modify, and distribute this software with minimal restrictions.
The project is considered functionally complete, but improvements and refinements are welcome. Focus areas:
- Improving API ergonomics
- Expanding documentation and examples
- Adding test coverage