mirror of
https://github.com/griches/PS5_DevLoop.git
synced 2026-10-25 08:00:49 +01:00
153 lines
7.4 KiB
Markdown
153 lines
7.4 KiB
Markdown
# PS5 DevLoop
|
|
|
|
[](https://github.com/griches/PS5_DevLoop/stargazers)
|
|
[](LICENSE)
|
|
|
|
**Let an AI agent, or a script, drive a jailbroken PS5 by itself: press buttons,
|
|
see the screen, repeat.**
|
|
|
|
Porting homebrew to the PS5 has had a gap in the middle of every loop. A machine
|
|
can build a title, deploy it, launch it and read its log, but it cannot press
|
|
Cross on the menu, and it cannot see that the screen is black. So a person sits
|
|
with a controller, and every long task stops to wait for them.
|
|
|
|
DevLoop closes that gap. It is one small payload that stays resident on the
|
|
console, and one Python client:
|
|
|
|
- **Input**: a system-wide virtual DualSense. The home screen and every title
|
|
see a real controller.
|
|
- **Sight**: the running title's own frame in about a second, or a screenshot of
|
|
the whole screen, system UI included, in about three. Both arrive as a small
|
|
PNG an agent can read, and neither leaves anything on the console.
|
|
|
|
With those two, an agent can launch a port, walk its menus, notice what is
|
|
wrong, change the code, and try again, with nobody at the pad.
|
|
|
|
```bash
|
|
tools/ps5loop.py load # once per console boot
|
|
tools/ps5loop.py open # connect the virtual controller
|
|
tools/ps5loop.py seq l1 wait:1000 cross wait:14000 # Games tab, launch
|
|
tools/ps5loop.py grab # the title's frame -> captures/shot-<time>.png
|
|
tools/ps5loop.py seq down down cross:300 wait:1000
|
|
tools/ps5loop.py shot # the whole screen, for the home screen and dialogs
|
|
tools/ps5loop.py close
|
|
```
|
|
|
|
That sequence, on a real console: switch to the Games tab, launch a Quake III
|
|
port, go through its menus, quit through "Exit game?", and land back on the
|
|
home screen, every step checked from a picture.
|
|
|
|
> For homebrew development on a console you own. It is tested on **firmware
|
|
> 13.60 only**; the calls it relies on are undocumented and may differ elsewhere.
|
|
> `payload/main.c` reports what every step returned so that another firmware can
|
|
> be brought up.
|
|
|
|
## For agents
|
|
|
|
`AGENTS.md` is the operating guide: the loop, which picture to take when, the
|
|
rules that keep the console safe, and how to give a new title the `grab` hook
|
|
(a few lines). `CLAUDE.md` includes it, so Claude Code reads it on entering the
|
|
folder. Point any other agent at it first.
|
|
|
|
## What you need
|
|
|
|
- A jailbroken PS5 on your network, with an **ELF loader** (port 9021) and a
|
|
**klog server** (port 3232) running, as the usual payload chains provide.
|
|
- Python 3 on the host, and for pictures `JxrDecApp` (jxrlib) and `magick`
|
|
(ImageMagick): `brew install jxrlib imagemagick`, or your distribution's
|
|
packages.
|
|
- To build: the [PS5 payload SDK](https://github.com/ps5-payload-dev/sdk).
|
|
`tools/build.sh` finds it through `PS5_PAYLOAD_SDK`.
|
|
- The console's address in `PS5_HOST`, or in `.env` (copy `.env.example`).
|
|
|
|
DevLoop presses buttons and looks. Launching and closing titles from a script,
|
|
and reading klog, are jobs for the tools you already have; mine are
|
|
[PS5_Vulkan](https://github.com/mihawk-99/PS5_Vulkan)'s `ps5vkctl` and run
|
|
tools. The pad can launch anything from the home screen without them.
|
|
|
|
## Commands
|
|
|
|
| Command | What it does |
|
|
| --- | --- |
|
|
| `load [ELF]` | send the payload to the console's ELF loader |
|
|
| `open` | create the virtual pad on the foreground user and make it the one titles read |
|
|
| `press BUTTONS [--ms N]` | press and release: `cross`, `l1+r1` |
|
|
| `hold BUTTONS [--lx N ...]` | hold buttons, sticks (0-255) and triggers until the next `hold` or `release` |
|
|
| `seq STEPS...` | presses and waits in order: `down cross:300 wait:1000` |
|
|
| `grab [FILE]` | the running title's frame (`--title`, from `titles.json`) |
|
|
| `shot [FILE]` | the whole screen, through the console's screenshot |
|
|
| `close` | remove the pad |
|
|
|
|
`tools/ps5loop.py --help` lists the rest; `payload/main.c` documents the wire
|
|
protocol (one text command per connection, port 9112).
|
|
|
|
## How it works
|
|
|
|
- **Input** is the system's virtual pad device (`scePadVirtualDevice*`, what
|
|
Remote Play uses). The payload holds a state and inserts it every 8 ms;
|
|
presses are timed on the console, not across the network.
|
|
- **A title's frame** (`grab`) comes from the title. The payload creates a
|
|
request file in the title's folder; the title removes it and writes a picture
|
|
of its next frame; the payload sends that and deletes it. About 1 s end to end
|
|
at 3840x2160. A title needs a hook of a few lines (`AGENTS.md`, "Giving a
|
|
title grab"), and an entry in `titles.json`.
|
|
- **The whole screen** (`shot`) is the console's own screenshot: the payload
|
|
holds the virtual Create button for 1.2 s, waits for the new file under
|
|
`/user/av_contents/photo`, sends it, and deletes it through
|
|
`sceContentDeleteByPath` (libSceContentDelete), as the gallery's own Delete
|
|
does: the file, its thumbnail and its row in the media database all go. The
|
|
client decodes it (JPEG XR when HDR is on) and scales it to 1280 wide.
|
|
|
|
## What the console showed (firmware 13.60)
|
|
|
|
- `AddDevice` for the real user answers `0x803b0001` and creates nothing. For
|
|
user 1 it answers `0x803b0006` and the device appears in klog
|
|
(`DEVICE_ADDED ... subType:22`); its id is the handle `InsertData` takes.
|
|
- `sceMbusBindDeviceWithUserId`, called in the payload's own process, binds it
|
|
(returns 0, `DEVICE_OWNER_CHANGED` follows). No injection into the shell is
|
|
needed.
|
|
- The console still raises "Who's using this controller?". Circle from the
|
|
virtual pad closes it; `open` does that 1.5 s after opening.
|
|
- A title opens its user's first controller (`scePadOpen(user, 0, 0)`). With a
|
|
real DualSense connected the virtual pad is the second and the title never
|
|
reads it. `sceMbusDisconnectDevice` on the real one leaves the virtual pad
|
|
first, and a title already running picks it up at once. `open` finds the real
|
|
pad in klog's `[CIM]` table and disconnects it; PS on the real pad reconnects
|
|
it.
|
|
- Confirmed by picture, on the home screen and in a title's menus: Cross,
|
|
Circle, the d-pad, L1, Create. Not yet exercised: sticks, triggers, PS,
|
|
Options, the touchpad.
|
|
- `sceScreenShotCapture` (libSceScreenShot), called from the payload, answers
|
|
"busy" until it is cancelled, with either type: no way round the Create hold
|
|
was found.
|
|
|
|
## Limits
|
|
|
|
- Pictures are stills, not video. Nothing known reads the composited display
|
|
from a payload.
|
|
- `shot` takes about 3 s: Create held for less than 1.2 s opens the Create menu
|
|
instead.
|
|
- While the pad drives a title, the real controller is disconnected
|
|
(`open --beside` keeps it, for the home screen only).
|
|
- The pad's device id is read from the klog server while it opens: another klog
|
|
client connecting at that moment can make `open` miss it.
|
|
- The payload listens on the local network with no authentication. Run it on a
|
|
network you trust.
|
|
|
|
## Credits
|
|
|
|
The virtual pad's call sequence and its 120-byte sample follow what
|
|
[SplashDown](https://github.com/seregonwar/SplashDown),
|
|
[Control4Free](https://github.com/MoHadiShibli/Control4Free),
|
|
[OmniPad-PS5](https://github.com/diegobarbosaa/OmniPad-PS5) and
|
|
[Ghostpad](https://github.com/srbraboo/Ghostpad) established on hardware. The
|
|
payload's command server follows `ps5vkctl` from
|
|
[PS5_Vulkan](https://github.com/mihawk-99/PS5_Vulkan) by Mihawk. Built with the
|
|
[PS5 payload SDK](https://github.com/ps5-payload-dev/sdk).
|
|
|
|
Not affiliated with Sony Interactive Entertainment.
|
|
|
|
## License
|
|
|
|
GPL-3.0-or-later. See `LICENSE`.
|