Skip to main content

PyPS4debug

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.


⚠️ Disclaimer

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.


⚠️ Important Notes

  • 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.


Features

  • 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

Installation

pip install ps4debug

Quick Start

import asyncio
from ps4debug import PS4Debug

async def main():
    # Discover a PS4 running the debug payload
    ps4 = await PS4Debug.discover()

    # Get version info
    version = await ps4.get_version()
    print("Version:", version)

    # List processes
    processes = await ps4.get_processes()
    for proc in processes:
        print(proc)

asyncio.run(main())

Core Concepts

Client

The PS4Debug class is the main entry point. It manages connections and exposes all high-level functionality.

from ps4debug import PS4Debug

ps4 = PS4Debug("192.168.0.10")

Processes

Retrieve and inspect running processes:

processes = await ps4.get_processes()
info = await ps4.get_process_info(pid)
maps = await ps4.get_process_maps(pid)

Payloads

Raw Payload

await ps4.send_payload(payload_bytes)

ELF Injection

await ps4.send_elf(pid, elf_bytes)

Console Interaction

await ps4.print("Hello from Python")
await ps4.notify("Done")

System Control

await ps4.reboot()

kernel_base = await ps4.get_kernel_base()
data = await ps4.read_kernel_memory(address, length)

Memory Access

Raw Memory

data = await ps4.read_memory(pid, address, length)
await ps4.write_memory(pid, address, b"\x90\x90")

The preferred way to work with memory is through MemoryContext, which manages allocation and cleanup automatically.

async with ps4.memory(pid, length=1024) as mem:
    await mem.write(b"hello")
    data = await mem.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.


Structured Memory Access

MemoryContext supports structured data using ConstructModel:

value = await mem.read_model(MyModel)
await mem.write_model(my_model_instance)

Memory Views (Typed Access)

MemoryView provides a type-safe interface over memory.

Creating a View

view = ps4.view(pid, address)

Or from an allocation:

view = mem.view()

Reading Values

value = await view.uint32(offset=0x10).get()

Shorthand:

flag = await view.boolean(offset=0x20)

Writing Values

await view.uint32(offset=0x10).set(1337)

Shorthand:

await view.uint32(offset=0x10)(1337)

Offsetting Views

sub = view.offset(by=0x100)
value = await sub.uint16()

Supported Types

  • 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)

Strings

text = await view.string(length=32, offset=0x0)

Or null-terminated:

text = await view.read_variable_text()

Memory Protection

await mem.change_protection(prot)

Executing Code from Allocated Memory

Allocated memory can be used as an execution target:

result = await mem.call(params=my_model, result_model=MyReturnModel)

This is a convenience wrapper around PS4Debug.call() using the allocated address.


Remote Procedure Calls (RPC)

Execute functions inside a target process:

result = await ps4.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.

from ps4debug import CallRegisters

registers = CallRegisters(
    rdi=1,
    rsi=2,
    rdx=3,
    rcx=4,
    r8=5,
    r9=6,
)

Return Model Requirements

Return values can be parsed into a model using a custom base class:

from typing import Annotated
from construct import Int32ul
from pydantic_construct import ConstructModel

class MyReturnModel(ConstructModel):
    number: Annotated[int, Int32ul]

Constraints:

  • The model must inherit from ConstructModel
  • The total size must not exceed 8 bytes (size of the RAX register)
  • If no return model is provided, raw bytes are returned

Debugging

Debugging is exposed through an async context manager returning a DebuggingContext.

async with ps4.debugger(pid) as dbg:
    await dbg.resume_process()

Only one debugging session can be active at a time.


Breakpoints

Breakpoints are managed using indexed slots.

index = await dbg.add_breakpoint(address=0x12345678, callback=lambda event: ...)

You can also configure them manually:

await dbg.set_breakpoint(index, address, callback, enabled=True)
  • Limited number of breakpoint slots
  • Managed internally via index and address mapping

Breakpoint Callbacks

Callbacks are async functions triggered when a breakpoint is hit:

async def on_break(event):
    print("Breakpoint hit at", hex(event.interrupt.regs.rip))
    event.resume = True  # resume execution automatically

await dbg.add_breakpoint(address, on_break)

You can also register a global callback:

dbg.register_callback(global_handler)

Execution order:

  1. Global callback
  2. Breakpoint-specific callback

Watchpoints

Hardware watchpoints can be configured:

await dbg.set_watchpoint(index, address, enabled=True)
  • Supports read/write monitoring
  • Limited hardware slots

Process Control

await dbg.stop_process()
await dbg.resume_process()
await dbg.kill_process()

Threads

threads = await dbg.get_threads()
info = await dbg.get_thread_info(thread_id)

Thread-level control exists but may depend on server-side support:

await dbg.stop_thread(thread_id)
await dbg.resume_thread(thread_id)

Registers

General Purpose

regs = await dbg.get_registers(thread_id)
await dbg.set_registers(thread_id, regs)

Floating Point

fp = await dbg.get_fp_registers(thread_id)

Debug Registers

dbg_regs = await dbg.get_debug_registers(thread_id)

Single Stepping

await dbg.single_step()

Executes exactly one instruction.


Event Handling Model

  • 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

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

Basic Usage

There are three ways to run scan queries.

Bounded queries

scanner.query() returns a builder object that can run itself.

scanner = ps4.scan(pid)

results = await (
    scanner
    .query()
    .int32()
    .exact(100)
    .execute()
)

Context builder

async with scanner.executor() as q:
    q.int32()
    q.exact(100)

The query will execute when the context is exited.

Manual

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)

Builder Pattern

The ScanBuilder allows incremental query construction:

builder = scanner.query()

builder.int32().bigger(100).aligned(True)
results = await builder.execute()

Common Scan Types

Exact Match

.int32().exact(1337)

Range

.int32().between(low=100, high=200)

Increased / Decreased

.increased(to=150)
.decreased(to=50)
.changed(None)

Unknown Initial Value

.unknown_initial_value()

Filtering

Memory Bounds

.bounds(start, end)

Module Filtering

.only_module("libSce.*")

Pause Target

.pause(True)

Iterative Scanning

async for addr, value in scanner.query().int32().exact(100).execute_iter():
    print(hex(addr), value)
  • Supports large result sets
  • Updates internal scan state automatically

Scan Lifecycle

Each session tracks:

  • initial values
  • previous values

Subsequent scans operate as refinements rather than full scans.


Error Handling

Operations may raise multiple exception types depending on the failure source:

  • PS4DebugException – protocol-level or server-side failures
  • ValueError, RuntimeError – invalid usage or state
  • asyncio exceptions – timeouts, cancellations
  • Parsing/validation errors from underlying libraries (e.g. model decoding from Pydantic or construct)

Example:

from ps4debug.exceptions import PS4DebugException

try:
    await ps4.reboot()
except PS4DebugException as e:
    print("Operation failed:", e)

License

This project is licensed under the 0BSD License.

You are free to use, modify, and distribute this software with minimal restrictions.


Contributing

The project is considered functionally complete, but improvements and refinements are welcome. Focus areas:

  • Improving API ergonomics
  • Expanding documentation and examples
  • Adding test coverage

Acknowledgements

Metadata

Release files for ps4debug 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ps4debug 1.0.0
File Size Uploaded
ps4debug-1.0.0.tar.gz 30.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ps4debug 1.0.0
File Interpreter ABI Platform
ps4debug-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 71.4 kB

Release files / ps4debug-1.0.0.tar.gz

Download URL ps4debug-1.0.0.tar.gz
Size 30.7 kB
Tags Source
SHA-256 checksum
How to use checksums
cd68bd88a8bfebb8e573cc675d542c32d0442c01bc4c62320f0b43f02d07f094
BLAKE2b-256 checksum
How to use checksums
1cf955128a98b034a8f969c1ac83b8ffbfb3e3d2db581a6e17b56b883b02b590
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / ps4debug-1.0.0-py3-none-any.whl

Download URL ps4debug-1.0.0-py3-none-any.whl
Size 40.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1c827d823d1fa38c50c88285ee4e6e773ab589a2b1c67ee0e177bbfe4c34a41e
BLAKE2b-256 checksum
How to use checksums
cbffd4bd687741acfab435a86db32a077e713a8ae847e3572abfadbf9a45c941
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

0.0.8

2 release files

0.0.7

2 release files

0.0.6

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page