PPortal logo
Windows 10 / 11 · x64 · AGPL-3.0

PlayStation Remote Play,
rebuilt as a Windows app.

PPortal streams your PS5 or PS4 to a window that behaves like the rest of Windows: WPF on the system Fluent theme, ten bits of colour composed by DirectComposition, and hardware decode on the card the machine already has. It reads the consoles and the pairings you have already set up, so the first run is not a setup.

What a frame goes through
One framefrom the packet the console sent to the light your display makes
receive0 bytes
The packet arrives on the transport, and the receive step spends no allocation on it, because a garbage collection in the middle of a stream is a stutter with no cause on screen.
reordertimed
UDP arrives out of order, so packets wait in a reorder queue. How long each one waits is measured, since a late frame and a lost frame look identical from the sofa.
reassembletimed
The units of one frame are joined back into the frame the encoder produced.
correcttimed
Erasure coding rebuilds what the network dropped, so a missing packet costs arithmetic instead of a whole frame.
decodeyour GPU
H.264 or HEVC through d3d11va, Vulkan or CUDA, chosen from what the machine reports rather than from what the vendor is.
present47 µs
libplacebo renders into a shared texture and DirectComposition puts it on screen, paced by the display's own refresh rate.
47 µs median present, 60 fps heldmeasured on an RTX 4060

Every stage above is timed on every session and written to a file on your machine, so a build that got slower can be compared against the one before it rather than argued about. compare-baselines prints the p50, p99 and maximum of each stage, and the difference between two runs.

Free software, no accountNothing is sent anywhereYour consoles come across
.NET 10 · WPF, Fluent, follows the OS themeTen-bit video through DirectCompositiond3d11va, Vulkan and CUDA decodeOne self-contained executable
Why it was rebuilt

A remote play client that is a Windows program

The stream is the hard part and it was already solved by the Chiaki and chiaki-ng projects. What was left was everything around it: a window that follows the OS theme, a decode path chosen on the machine's own answer, and an installer that does not carry a second browser. PPortal is that half, rewritten on .NET 10.

🪟

It looks like Windows because it is Windows

WPF on the built-in Fluent theme, with ThemeMode="System", so light and dark follow the OS with no extra package and no theme of its own to keep current.

🧩

Nothing bundled that Windows already ships

Signing in to PSN uses the WebView2 runtime that is already on the machine, instead of carrying a copy of Chromium inside the download for one login screen.

📥

Your setup is already there

Registered consoles, manual and hidden hosts, controller mappings and the PSN token are read from the store you already have, resolved against the profile you are actually signed into.

🎯

It runs on the card you own

The tuning goes to NVIDIA first, and first is not only: what must keep working on a machine with no NVIDIA card is written down as a contract rather than left as an intention.

The picture

Ten bits, and an overlay that survives them

The obvious way to get a decoded frame into a WPF window refuses anything wider than eight bits per channel, which rules out HDR before the stream starts. So the video does not go through WPF at all: libplacebo renders it into a shared Direct3D 11 texture, and DirectComposition puts that texture on screen as a ten-bit plane with the window's own overlay composed above it.

THE NETWORK THE GPU THE COMPOSITOR console → UDP packet reorder · reassemble error correction libplacebo · D3D11 shared NT texture d3d11va · vulkan · cuda WPF overlay (HUD, menu) visual: above video plane, 10 bit visual: below the window on screen The frame never passes through WPF's own image path, which refuses anything wider than eight bits per channel. It is composed beside the window's content instead, with the overlay above the plane, because that is the one arrangement that keeps both.
🎞

The overlay is above the video, not behind it

Of the three arrangements that compose, one keeps both the ten-bit plane and the stream HUD visible. That is the one it uses, and the other two were built and looked at before it was chosen.

Paced by the display, not by a guess

The present interval comes from the refresh rate of the display the window is on, and a display that changes rate mid-stream changes the pacing with it rather than drifting a frame at a time.

🎛

The renderer is yours to tune

libplacebo's scalers, debanding, colour mapping and tone curves are exposed on two screens, with presets for the settings nobody wants to learn and every slider underneath for the people who do.

Controllers

The pad is the primary input, including in the menus

A remote play client is used from a sofa, so a screen that can only be reached with a mouse is a screen that cannot be reached. Focus moves by the D-pad and the sticks on every screen, and the button that moves it is the one the mapping screen says it is.

  • Focus navigation everywhere. Directional focus is an attached property rather than a control of its own, so it applies to the ordinary WPF controls the screens are built from instead of a private widget vocabulary.
  • Your own bindings. The mapping screen reads the device's real binding strings through SDL, captures a press against the slot you picked, and writes the mapping back.
  • A pad that is plugged in now. The device list, the button events and the axis positions are read on the thread that owns SDL, so a controller connected mid-session is a controller that works.
  • Not only a PlayStation pad. SDL is called directly rather than rewritten, so anything it can map arrives mapped, an Xbox controller included, and the haptics the console sends fold to plain rumble on a pad with no haptic motors.
  • Keyboard too. Every key binding is a row in the settings, for the machine where the pad is in another room.
Controller mapping DualSense Wireless Controller Cross a Circle b L2 a2 R2 a5 Press the button for Square Escape cancels. The capture takes the press, not the release. The bindings are the device's own strings, read through SDL, so a pad that already works elsewhere arrives already mapped.
pportal.exe --help
PPortal opens the application when you run it with nothing. The rest are the diagnostics for when a pad, a driver or a machine disagrees with you:

  --selftest            run the host's own assertions and exit with the verdict
  --controllers         print what SDL sees, for a pad that is plugged in now
  --capture-controller  log presses for twenty seconds; add --analog for the sticks
  --analog              with --capture-controller: include the axes, which flood the log
  --map-controller      open the mapping screen against a real pad

An unrecognised flag is refused with this list and a non-zero exit, rather than silently opening the window.
The screens

The front door, the dialogs, and every setting

The console list is where the application opens: the consoles discovery found, the ones you added by address, and the ones you hid, merged into one list that says which is which. Everything else is reached from it.

PPortal ─☐✕ Consoles Add by address CONSOLE ADDRESS FOUND BY STATE PS5-385 192.168.0.24 discovery ready Stream PS5 upstairs 192.168.0.31 discovery standby Wake PS4 (office) 10.0.4.19 added by address not found Retry PS5 (hidden) 192.168.0.77 hidden by you muted Unhide Three sources in one list: what discovery found on the network, what you added by address, and what you hid. A hidden console is still yours, so it says so rather than disappearing and being registered again a month later.
The console list. Discovery, manual hosts and hidden consoles in one view, each row saying what it can do rather than what it is.
Console states

A row offers what its state allows

Ready

The console answered and is awake, so the row streams. This is the only state where that button does anything, and it is the only state where it is offered.

Standby

The console answered and is asleep. The row wakes it, and the wake and the connection are two steps rather than one that silently retries.

Not found

Nothing answered at that address. The console you registered stays in the list saying so, because a console that disappears when the network hiccups is one you register again.

Settings

Nine tabs, and none of them invented

  • General, Video, Stream, Audio, Consoles, Keys, Controllers, Remote and Config. Each tab keeps the rules its own store spells, including the ones that are inconsistent, because a setting that reads back differently after the rewrite is a bug the user finds and not a tidy-up.
  • Written when you finish. A setting is committed when the field is done rather than on every keystroke, which is what the screen it replaces did.
  • Registration is a dialog with rules. Manual host, console PIN and profile each validate what they take, and the PIN rules for the two console generations stay apart because they are not the same rule.
  • Signing in to PSN. The login runs in a WebView2 panel, and what it clears when it closes is a decision the screen makes explicitly rather than a browser default.
Measured, not argued

Every session writes down what it did

Latency claims are cheap. This one leaves evidence: each session appends one line to a record on your machine naming the decoder, the renderer, the requested bitrate and both loss settings, with the timing of every stage of the frame path beside them.

  • The stages. Receive, reorder dwell, reassemble, error correction and the decoder's send-to-pull are timed separately, so a slower build says which stage and not just which build.
  • A latency floor. Input queueing plus the console's own reported round trip, in milliseconds, which is the part of glass-to-glass the client can actually see.
  • The configuration. A number without the settings that produced it compares with nothing, so the row carries them and the card the run was taken on.
  • Two runs, one table. compare-baselines reads two records and prints p50, p99 and maximum per stage with the difference, and refuses a comparison whose two halves are not the same shape.

The record is a file in your own application data. Nothing is uploaded, and there is no account to sign in to for any of it.

What it is not

Five things this deliberately does not do

A list of what a program refuses is more useful than another list of what it does, because it is the half you cannot find out by reading the features.

No Linux, macOS, Android or Switch build

The target is Windows by construction, not by default. Keeping a second platform alive would mean a second application with a shared name.

No cross-platform UI toolkit

A portable toolkit would give back none of the Win32, DXGI and WebView2 access the screens and the video path depend on, which is the whole reason WPF was chosen.

No vendor path whose absence you can see

A machine with no NVIDIA card keeps hardware decode, a neutral renderer and an SDR present. What is missing is missing from the menu, not explained in a warning.

No redesign of what already worked

Screens were reproduced rather than reimagined, down to the settings whose stored values do not match their labels, because a screen that changes shape cannot be judged against the one it replaced.

No telemetry

The session record exists so that two builds can be compared on your machine. It is a file on your disk, it is not uploaded, and there is no account anywhere in the application.

Get it

One installer, and it sits beside what you already have

Version 1.10.0, x64, Windows 10 or 11. It installs under an identifier of its own, so an existing chiaki-ng installation is left where it is rather than upgraded in place, and the two can be run one after the other on the same machine.

Version 1.10.0Windows 10 / 11, x64AGPL-3.0

The application is a single self-contained executable with the native libraries it loads placed beside it. Signing in to PSN uses the WebView2 runtime Windows already carries, so there is no browser inside the download.