re:psn by undecuple, v0.6.0, pre-release

Overview

re-psn answers the matchmaking and leaderboard calls of a statically recompiled PS3 game, for a group running its own server: a LAN, a VPN, or friends connecting to one person's machine. The server keeps rooms and leaderboards and tells each player where the others are. Game traffic goes directly between players, and through the server only when two players cannot reach each other and the host has turned the relay on.

It is a fork of psnr by sp00nz: a Go server and a one-file C client, with no accounts and no public instance.

Version 0.6.0 (pre-release). 80 features: 43 available, 3 not yet verified, 9 partial, 15 planned, 9 later, 1 declined.

Unofficial; not affiliated with Sony. No game files, keys or firmware are in the repository or needed from it. Play only games you own.

Features

One row per feature. A status rests on a test or a recorded run in the repository; the features page has the evidence for each row.

Rooms and matchmaking

FeatureStatusSince
Rooms and search rooms-basic
Create, search, join and leave rooms modelled on sceNpMatching2. A search sees only the rooms of its own title, with each room's external data. Room messages, owner kicks, owner handoff when the owner leaves, and member pushes with each peer's address and P2P port.
Upstream work. KICK_MEMBER removes a member but does not prevent rejoining.
available0.1.0
Room and member data rooms-data
External and internal room data (only the owner writes it), room flags that searchers see (SET_ROOM_DATA which 2), and member data sent with CREATE_ROOM and JOIN_ROOM and returned in every member entry.
External and internal data are upstream work (0.1.0). Room flags and member data are newer, so clients built against 0.1.0 no longer match.
available0.2.0
Invite-only rooms rooms-codes
An 8-character code per room (SET_ROOM_ACCESS, JOIN_BY_CODE, GET_ROOM_ACCESS). The room leaves search and the status page, which counts unlisted rooms. A connection that sends too many wrong codes is closed.
Protocol 0.4: HELLO_ACK flags bit 1 says the server has them. Codes are never logged.
available0.2.0
Private rooms rooms-private
Matching2 room passwords, private slots and join by room ID. A relay-only variant that keeps members' addresses hidden from each other is also wanted.
The relay-only variant is not designed yet.
plannednot released

Leaderboards

FeatureStatusSince
Leaderboards scores-boards
Modelled on sceNpScore: each player's best score is kept, a tie goes to whoever scored first, boards are higher-is-better or lower-is-better, and ranks are read by range or by online ID.
Upstream work. Kept in memory: a restart empties every board. Opt-in storage is planned (store-sqlite).
available0.1.0

Players

FeatureStatusSince
One player per name players-names
Names are compared case-insensitively. A HELLO with a name held from another address gets ERROR 8 (name taken); from the same address it is the same player reconnecting, and the old connection is closed.
No passwords: a name is not authenticated.
available0.2.0
Friends, presence and invites players-friends
sceNpBasic: a friends list (everyone on the same server), presence (online, title, room), invites and join-a-friend, player history and a block list.
Not in the server today.
plannednot released
Optional sign-in players-accounts
Optional accounts next to the default of a name per player: a credential per server kept on the player's machine, sign-in through a generic OpenID Connect provider, or with Discord or GitHub.
Not started. Needs the opt-in database (store-sqlite) first, and the scope in ROADMAP.md says no accounts or passwords, so that needs changing too. The default would stay a name per player.
laternot released

Limits

FeatureStatusSince
Request and connection limits limits-requests
-max-rooms, -max-player-rooms, -max-conns-per-addr, -max-req-rate (requests per second per connection) and a size cap on room data. A request over a limit is refused; a connection over the rate is closed. Each refusal is logged at warn level.
These limits cover TCP. Main also has bounded UDP admission and drop counters; see the partial net-guardrails row.
available0.2.0

Logging

FeatureStatusSince
Log levels and formats logs-levels
-log-level (debug, info, warn, error), -v as debug, -log-format text or json, and -log-file to append to a file (mode 0600). Connection and room refusals are logged at warn level; UDP drops use aggregate counters, with one warning per player for a mismatched probe. Tokens, room codes, peer keys and query strings are never logged, at any level (TestLogsHoldNoSecrets drives a session at debug level in both formats).
Not checked: no test covers -log-file. The server does not rotate the file.
available0.2.0

Status page

FeatureStatusSince
Status page and /api/stats, with a token status-page
A read-only page and JSON on a separate listener, off unless -http names an address (127.0.0.1:36101 keeps it on localhost). -http-token requires a token (Authorization: Bearer, or ?token=), and the server warns at start when the page is reachable from elsewhere without one. Since v0.4.0 the page uses the project palette (dark, with the gruvbox-material light mode), Rooms/Leaderboards tabs, System/Light/Dark and browser-local text size, spacing and contrast-checked custom colours.
The page and /api/stats are upstream work (0.1.0); the token is the fork's.
available0.2.0
Known games by name status-games
/api/stats and the status page label rooms and leaderboards with the game's name and serials, for example [Killzone 3 (BCUS98234, BCES01007)], from a small table embedded in the server (four games so far); a game the table does not know keeps its communication ID. The protocol is unchanged.
Not in v0.4.0. The table is never fetched at run time.
available0.5.0
Verified player count status-verified
Each public room has verified (members whose UDP probe the server accepted on this connection) and the top level has verified_players; the page shows '(N verified)' beside a room's member count. The protocol is unchanged.
Not in v0.4.0. A count of probes the server accepted, not a check of who the players are.
available0.5.0
Server state in /api/stats status-server-state
/api/stats reports the version, whether the relay is on, the payload the relay has carried, the limits in force and the TCP connections refused (per address, request rate). Aggregate only; no address or player. The protocol is unchanged.
Not in v0.4.0. Counts are since start. relay_bytes is payload, not interface traffic.
available0.5.0
Player-reported latency status-latency
HEARTBEAT may carry the player's own last round trip (rtt_ms, 2 bytes); the server shows it next to each member as reported by the player. The C client and the app's Go client send it once a title opts in (recommended, off by default).
Not in v0.4.0. Reported by the player and not verified; display only. The round trip includes the server's own time.
available0.5.0
Experimental admin API, read-only part admin-api
An opt-in listener of its own (-admin, -admin-read-token-file, TLS for a non-loopback address) with one route, GET /admin/v1/state, for a read token: version, uptime, relay, limits, TCP and UDP refusal counts and the ban count (always 0 for now), no address or player. Failed requests get a growing capped delay and one fail2ban-shaped log line; the token file is read again on SIGHUP.
Experimental, first in v0.6.0. Read-only: no action, no ban, no limit change, and no app view yet; those are separate changes. Not run against a reverse proxy, on Windows, or on a container secret mount.
partial0.6.0

Metrics

FeatureStatusSince
Metrics endpoint metrics-endpoint
/metrics in the Prometheus text format, off by default, with the status page's token rules: connections, rooms, refusals, UDP probes and relay traffic. No names, addresses, room codes or tokens in any label.
Not started. Needs the counters from net-guardrails first.
laternot released
Grafana dashboard and alerts metrics-dashboard
Example Prometheus scrape config, alert rules and a Grafana dashboard in deploy/, written from the real metric names.
After metrics-endpoint.
laternot released

Reverse proxy

FeatureStatusSince
PROXY protocol and trusted proxies proxy-support
-trusted-proxies (CIDRs or addresses) and -proxy-protocol (a PROXY v1 or v2 header required on the game TCP port from a listed proxy), and forwarded headers for the status page log. The real client address then counts for the limits, the name rule, public_ip and the log.
Off by default. UDP is not proxied. See docs/running.md.
available0.2.0

Deployment

FeatureStatusSince
Settings file, service unit and Traefik example deploy-examples
deploy/ holds re-psn.env.example (every setting, commented), a systemd unit and a Traefik config that sends PROXY v2. The release archives carry them as examples/.
scripts/check-deploy.sh checks the env file and the unit against the server's flags. The Traefik run (Traefik 3.1 under Podman) is recorded in docs/running.md; no run of the systemd unit is recorded.
available0.2.0
nginx and HAProxy examples deploy-nginx-haproxy
An nginx stream block with proxy_protocol on; (PROXY v1) and an HAProxy config with send-proxy-v2.
Not checked: neither config has been run against the server (docs/running.md says so).
not yet verified0.2.0
Caddy status-page example deploy-caddy
A stock Caddy 2 HTTP reverse-proxy example in deploy/caddy/, copied into future release archives with the other deployment examples. Defaults to loopback and leaves game TCP/UDP direct.
Written from the official Caddy documentation; not run or validated with Caddy. No layer-4 plugin is included. The existing HTTP token check is preserved and client-supplied CF-Connecting-IP is removed before forwarding.
not yet verified0.4.0
Optional fail2ban status-token jail deploy-fail2ban
A disabled systemd-journal filter/jail example for repeated HTTP status-token refusals. Uses JSON logs and a host TCP status-port action, with setup, firewall placement, validation and recovery instructions.
Offline synthetic HTTP/logger and native fail2ban-engine checks do not establish live journal/firewall enforcement. Game TCP/UDP, proxy/CDN and bridged-container enforcement are outside the sample's scope; install and verify fail2ban separately.
not yet verified0.4.0
Docker and Podman deploy-container
A two-stage Dockerfile and a compose file in docker/, configured only by RE_PSN_* variables. The status page is off until RE_PSN_HTTP is set.
A Podman 6.1 run is recorded in docker/README.md, and the NAT lab builds the image with Docker in CI. The v0.2.0 image is pushed to ghcr.io (amd64 and arm64, unsigned; arm64 not run). The server runs as a non-root user and the bases are pinned by digest. Opt-in host-networking compose files, one hardened for a shared test host, are for Linux only. Compose under Podman is not tested.
available0.2.0

Settings

FeatureStatusSince
Flags and RE_PSN_* variables settings-env
Every flag can be set as RE_PSN_<FLAG>; the old PSNR_<FLAG> names still work as aliases. A flag wins, then RE_PSN_, then PSNR_. An old name logs a warning that names the variables, never the value. -tcp-addr and -udp-addr give the two game sockets their own addresses. A bad value stops the server at start.
No config file yet.
available0.2.0

Storage

FeatureStatusSince
Storage interface store-interface
A storage layer with an in-memory backend, the leaderboards wired through it, and a -db (RE_PSN_DB) setting. Off by default, so nothing changes unless a host turns it on.
Only off and memory exist, so scores are still kept in memory and lost on restart; other schemes stop the start. First step of opt-in storage (store-sqlite is next).
available0.2.0
SQLite storage (opt-in) store-sqlite
An SQLite backend with a pure-Go driver, so the release archives stay static, and migrations built into the binary. With it on, scores survive a restart. The connection string is never logged.
Not started. The server's first third-party Go module. Player names and bans would follow with accounts.
plannednot released
Backup and restore store-backup
Documented backup and restore for each database: a backup command or a copy procedure for SQLite; a dump command with timer and cron examples for PostgreSQL and MariaDB. How long backups are kept is up to the host.
Not started. Comes with store-sqlite.
plannednot released
PostgreSQL storage store-postgres
A PostgreSQL backend for hosts who bring their own database; the server does not run or manage it.
After store-sqlite.
laternot released
MariaDB storage store-mariadb
A MariaDB backend for hosts who bring their own database; the server does not run or manage it.
After store-sqlite.
laternot released

High availability

FeatureStatusSince
Health check and clean restarts ha-restart
A data-free /healthz endpoint on the opt-in status listener, guarded by the existing HTTP token, and bounded shutdown that closes listeners and accepted client/relay sockets. A guide covers probe results, supervisors and what players lose on restart.
Health is 200 only while both game serving loops are active, 503 during startup/shutdown or after a loop exits. Loopback and subprocess tests cover shutdown; systemd/container restarts and remote failover are not run. Rooms and scores remain in memory and are lost on restart. No database retry is needed until a network backend exists.
available0.4.0
Active/passive failover ha-failover
Docs and example files for a standby server: a floating address (keepalived) or a proxy's health checks, both servers on the same database, one active at a time. No election or replication inside the server.
Not started. After ha-restart; the shared database needs store-postgres or store-mariadb. Live rooms are lost on failover.
plannednot released
Several servers ha-multi
Several servers behind one address, each room wholly on one server, with a routing layer that sends a new room or a join to the right one.
On the back burner: revisited after ha-restart and ha-failover exist and one server is not enough.
laternot released
Live room handover ha-handover
Rooms that survive the loss of a server.
Needs replicated live state; out of scope for a server of this size.
declinednot released

Network

FeatureStatusSince
Address discovery and hole punching net-punch
A player's P2P socket sends the server a UDP PROBE. The server records the public endpoint and gives each viewer the address that works from where they are: the public one from another network, the LAN address from the same one.
Upstream work. The NAT lab runs it in Docker in CI.
available0.2.0
Relay net-relay
-relay, off by default: streams into a player behind a router go through the server, and datagrams go through the server's UDP port when punching does not get through (as with a symmetric NAT).
Upstream work. Uses the server's bandwidth; there is no per-player budget yet (net-guardrails).
available0.2.0
UDP source address check net-udp-guard
PROBE and RELAY packets are accepted only from the IP address of the player's TCP connection. A probe's local_ip is kept only if it is private, carrier-grade NAT, link-local or the probe's own source. -udp-any-source turns the check off.
A Podman run is recorded in docker/README.md.
available0.2.0
More UDP and relay guardrails net-guardrails
Bounded per-source-IP UDP packet admission shared by probes, relay and invalid traffic, plus aggregate drop-reason counters in /api/stats. The token bucket tracks at most 4096 sources, expires idle entries and rejects new sources at capacity.
Initial packet limiter only: default 1000 packets/second with the same burst, configurable with -max-udp-rate and 0 for off; players behind one NAT share it. Per-player relay byte/datagram budgets, payload and TCP stream limits, failed-token blocking and UDP fuzz coverage remain planned. A larger token needs a protocol version bump.
partial0.4.0
Connection checks net-checks
A check for players and hosts that says where a connection fails (server reachable, HELLO accepted, UDP probe answered, NAT behaviour, peer reachable, relay available) and shows round trip, loss and whether each peer is up.
Not designed yet.
plannednot released
UPnP net-upnp
Open a player's P2P port on their router automatically.
Not started.
laternot released
Force the relay (testing) net-relay-force
-relay-force (RE_PSN_RELAY_FORCE, off by default, needs -relay) sends every stream through the relay, even between players who could connect directly, so the relay can be tested from one network. Datagrams are not forced.
Not in v0.4.0. For testing only: it uses the host's bandwidth.
available0.5.0
Per-title peer traffic off net-no-peer-titles
-no-peer-titles lists communication IDs of titles that use no peer traffic: their players get no UDP probe reply, no relay for datagrams or streams, no relay route and no relay flag in HELLO_ACK, and still use rooms and scores. Other titles on the server are unaffected.
Not in v0.5.0. A list of IDs, not a per-title profile file. Hole punching itself is client-side: it stops because the server never learns the endpoint. Tested on loopback with a client written for the tests, not with a real title.
available0.6.0
Capability report net-caps
GET_CAPS (0x04) after HELLO returns CAPS (0x94): a bit set of what this server offers this player (rooms, scores, room codes, peer keys, the UDP probe, the relay, LAN discovery) and the server version. The probe and relay bits are per title, so a title in -no-peer-titles sees them clear. The C client has the constants and psnr_caps_parse.
Not in v0.5.0. A server before this answers the request with ERROR 1. Tested on loopback with a test client and the C client; no real title asks for it yet.
available0.6.0
Peer keys (server side) net-peer-keys
A client may ask with PEER_AUTH; each pair of members who both asked then gets one new random 16-byte key in a PEER_KEY push, for tagging their direct packets. Opt-in; clients that do not ask are unaffected.
Not in v0.4.0. No client uses the keys yet, and nothing is tagged on the wire. The keys travel over plain TCP, so this gives no confidentiality and no protection from someone who can read a player's network traffic.
partial0.5.0
Public address for LAN players net-lan-public-addr
-lan-public-addr IP (RE_PSN_LAN_PUBLIC_ADDR, IPv4, off by default) gives players on public addresses the host's address and the member's P2P port for a player who connected from a private, CGNAT or link-local address. The host forwards each such P2P port. Players on private addresses are unaffected.
Not in v0.4.0. Off by default. Several LAN players behind one forward need distinct P2P ports; the relay covers a failed connection.
available0.5.0
LAN discovery (server side) net-lan-discover
-discover (RE_PSN_DISCOVER, off by default) answers a 64-byte DISCOVER sent to the game UDP port with a 52-byte reply: TCP and UDP ports, relay flag, player count, name (-name) and version. Only private, carrier-grade NAT, link-local and loopback senders are answered, under the UDP rate limit.
Not in v0.4.0. Off by default. Server side only: no client helper yet. A broadcast does not cross subnets or Wi-Fi client isolation.
available0.5.0

Medius profiles

FeatureStatusSince
Profile-specific transport and discovery medius-transport
An opt-in, disabled-by-default Medius-compatible frontend for a named title/build profile, starting with synthetic local transport and discovery. Unknown or unsupported profiles, versions, application IDs and messages fail explicitly.
Not implemented. Candidate Killzone 3 BCUS98234 update 1.14, app ID 23044: compatibility not tested; not supported.
plannednot released
Profile-specific sessions and lobby medius-lobby
Logical sessions across service sockets and profile-specific lobby operations, with only an explicitly unauthenticated synthetic local TicketLogin envelope.
Not implemented; no PSN authentication or real tickets.
plannednot released
Profile-specific data path medius-data
A data path considered only after the selected profile's transport, membership and authorization requirements are evidenced.
Not implemented. The data plane, NAT and DME requirements are unknown; no psnr reuse is promised.
plannednot released
Further title/build profiles medius-multititle
Consider additional explicitly selected title/build profiles, each with its own evidence and failure rules.
Not started; no general Medius or multi-title compatibility is claimed.
laternot released

Client

FeatureStatusSince
C client client-c
One file with no dependencies and no threads: psnr_send returns a request id, and psnr_poll hands out replies and pushes; sends and automatic heartbeat writes can wait for socket writability. It also builds the UDP probe and relay packets.
Upstream work. The integration guide documents ownership and timeout limits. TestCClient also compiles/runs the minimal example; it skips without a C compiler, and Linux CI requires execution.
available0.1.0
Go client package app-client
app/internal/client: a Go client for the TCP protocol, for the desktop app. HELLO, heartbeat, create, search, join and leave rooms, record and read scores, and the member and route pushes, with typed messages and errors. re-psn-app check HOST:PORT uses it.
Room data and messages, kicks, invite-only rooms, relay streams and UDP are not in it yet.
partial0.3.0
Desktop app, terminal UI app-tui
re-psn-app with no arguments: a first-run choice of Play, Host or Both with the online ID and the server, a menu and content pane (one at a time under 70 columns), a Status screen with Rooms and Leaderboards tabs over the game protocol or an explicitly configured HTTP status source, a Settings screen that saves re-psn.ini, and a dark or light theme in the website colours. Built on Bubble Tea and Lip Gloss.
Partial: no Host screen, room joining, launch profiles or theme presets yet. Linux desktop starts without a TTY can open a supported terminal; Windows uses its console. Synthetic startup tests ran; physical desktops/emulators remain unverified. The optional HTTP source adds uptime, connection counts, public-room members/age and board leader summaries with bounded authenticated fetches and game-protocol fallback. The game protocol still lacks those extra fields; neither source reports server version. Driven in tmux on Linux; not tried on Windows or macOS terminals.
partial0.3.0
Desktop client, terminal UI client-desktop
A terminal program for players and hosts: launch profiles per game, a host mode, and a check of the connection.
Not started. One executable per platform, with nothing else to install.
plannednot released
Desktop client, graphical UI client-gui
A local web UI served by the same program, and a graphical app on Windows, with the same screens as the terminal UI.
Not started. After client-desktop.
plannednot released
Colour themes app-themes
Colour presets in the desktop apps: the project palette by default, then gruvbox, catppuccin, kanagawa and melange. A preset is a data file shared by the apps and the README art.
Not started. A preset ships only after its project's licence is read and credited in NOTICE.md. The terminal UI falls back to the terminal's own 16 colours and respects NO_COLOR.
plannednot released
Appearance mode app-appearance
Dark, Light or System (the default): presets carry a dark and a light variant where their project has both, and System follows the OS or the terminal background and switches while the app runs.
Desktop presets and system-following remain planned. The server status page supports browser System/Light/Dark and local appearance controls; see status-page.
plannednot released
Custom colours app-colours
Edit named colour slots (background, surface, text, muted, accent, good, warn, bad) as hex values, with a contrast warning below 4.5:1, import and export, saved per user and per mode.
Not started. A bad value falls back to the default preset.
plannednot released
LAN discovery in the C client client-lan-discover
psnr_discover broadcasts one UDP request from its own temporary socket and returns the LAN servers that answer, with their ports, relay flag, player count, name and version.
Not in v0.4.0. Finds only servers started with -discover. Tested against a fake responder and, by hand, against a real server on loopback; not a real broadcast across a network. Does not cross subnets or client-isolated Wi-Fi. The Windows build broadcasts to 255.255.255.255 only; CI compiles the client with MSVC but does not run it on Windows.
available0.5.0
Host connect policy in the C client client-connect-policy
psnr_set_connect_policy registers one callback that the client asks before it opens a TCP connection: the connection psnr_connect and psnr_connect_timeout make (once per resolved address, with the host as typed) and each relay leg (psnr_stream_connect, psnr_stream_accept). A refusal skips that address, or returns -1 for a relay leg; refusing every address gives NULL and psnr_connect_error() is PSNR_E_POLICY. No callback means today's behaviour.
Not in v0.5.0. Process-wide, set before the first connect; not covered: psnr_discover and the title's own P2P socket. IPv4 only, like the client. Tested against a real server on loopback; each check fails with its hook removed.
available0.6.0

Desktop app

FeatureStatusSince
Find servers on the LAN app-lan-discover
Find on LAN in the desktop app (f in Settings, Ctrl+F while typing the server): the Go client broadcasts one 64-byte UDP request from a temporary socket, collects the 52-byte replies for about 1.5 s, drops anything with the wrong length, type or nonce, and lists name, address:port, players, relay and version; the chosen address fills the server field. Typing host:port still works.
Not in v0.4.0. Finds only servers started with -discover. Tested against a fake responder and, by hand, against a real server on loopback; not a real broadcast across a network. IPv4 and the local subnet only. server = auto in re-psn.ini is not supported.
available0.5.0
Server view in Status app-server-view
With a status URL set, the Status screen has a read-only Server tab (Tab cycles Rooms, Leaderboards, Server): version, uptime, relay on or off, the limits in force (0 shown as none), TCP refusals and UDP drop counters, and the payload carried by the relay with a bytes-per-second rate computed between two refreshes. A counter that goes down is shown as server restarted and the rate baseline starts again. Fields an older server omits are shown as not reported by this server, never as 0 or off. Nothing is changed on the server and the token is never shown.
Not in v0.4.0. Needs a server that reports the fields (newer than v0.4.0); older servers show not reported. Relay bytes and the rate are payload carried by the relay, not network bandwidth. Checked by hand against a real server on loopback.
available0.5.0

Testing and CI

FeatureStatusSince
Fuzz tests test-fuzz
Go fuzz targets for the frame parser, the request handlers and the PROXY header parser. Their seed inputs run with every go test.
CI runs the seeds only; longer fuzzing runs are by hand. The UDP packets have no fuzz target yet.
available0.2.0
CI checks ci-checks
gofmt, go vet, go test -race on Linux (with the C client) and go test on Windows, govulncheck, gitleaks over the full history, the NAT lab in Docker, and CodeQL for Go and the workflows. Actions are pinned and permissions are read-only by default.
Dependabot proposes version updates. A focused local loopback runner requires native C-client and Go app-client conformance checks to execute; synthetic loopback only.
available0.2.0
More CI checks ci-more
Candidates: the C client outside Linux, the race detector on Windows, and a scan of the container image.
The list is not settled. The header of ci.yml says what CI does not check today.
plannednot released

Release

FeatureStatusSince
Release archives and checksums release-archives
scripts/release-build.sh builds static binaries for windows/amd64, linux/amd64 and linux/arm64 into re-psn-vX.Y.Z-<os>-<arch> archives and one SHA256SUMS listing them. A pushed version tag makes a draft pre-release; a second workflow checks a published one.
Published as the v0.2.0 pre-release (2026-11-06): the release workflow and the published-release check both passed. The Windows and arm64 binaries are cross-built and not run.
partial0.2.0
Linux AppImage release-appimage
scripts/build-appimage.sh packs the linux/amd64 server binary and its examples as re-psn-vX.Y.Z-x86_64.AppImage, with appimagetool 1.9.1 and the type-2 runtime 20251108, both downloaded by the release workflow and checked against pinned SHA-256 hashes. A smoke test compares its -h and version with the binary's, and it is listed in SHA256SUMS.
Published in the v0.2.0 pre-release (2026-11-06); the release workflow built it on a runner and passed its smoke test. It was also built and run on one x86_64 Linux machine, with FUSE and with --appimage-extract-and-run. x86_64 only, unsigned.
partial0.2.0
Install scripts install-script
scripts/install.sh (Linux amd64 and arm64, POSIX sh) and scripts/install.ps1 (Windows amd64; written for PowerShell 5.1 and 7, tested on 7 only) install the server for the current user from a GitHub release, newest or pinned, after checking the download against SHA256SUMS. No sudo or administrator rights, no profile edits; --dry-run, --dir, --appimage, -AddToPath.
Partial: release assets (install.sh, install.ps1, listed in SHA256SUMS) since v0.4.0; v0.3.0 does not carry them. The Linux tests run under dash, bash and BusyBox ash with a fake release, and the published v0.4.0 install.sh installed that release on Linux amd64 into a temporary directory; install.ps1 has not installed from a published release. The PowerShell fake-release tests have passed under PowerShell 7 on Linux and Windows CI; PowerShell 5.1 remains untested. No macOS builds, so no macOS support.
partial0.4.0
Release process release-process
Written steps for cutting a release (tag, notes from CHANGELOG.md, checking the draft, publishing), a check that the version in the code and in CHANGELOG.md agree, and a dry run of the release workflow before the first tag.
VERSION, the version check (TestVersionAgrees), scripts/bump-version.sh and scripts/release-notes.sh are built and docs/releasing.md has the steps; the release workflow builds its notes with the notes script and checks that a tag is v + VERSION, and the first tag, v0.2.0 (2026-11-06), ran the whole path: the dry run, the tag step and the draft job passed, and the draft was published by hand after its assets were read back. No fixed release day: while things are worked out, pre-releases (v0.x, marked pre-release) come whenever there is something worth trying.
available0.2.0
Signed releases release-signing
Sign re-psn.exe so Windows does not warn about an unsigned program, and sign the checksums.
The exe and SHA256SUMS are unsigned today.
laternot released
Desktop app archives app-archives
The release build makes re-psn-app-vX.Y.Z-linux-amd64.tar.gz, -linux-arm64.tar.gz and -windows-amd64.zip from app/ (static, no cgo, version set from the tag), each with LICENSE, NOTICE.md and the app README, listed in SHA256SUMS. A release has ten assets.
Not in v0.4.0. install.sh --app and install.ps1 -App install it (tested against a fake release only). The release workflow has not built them on a tag; the Windows and arm64 binaries are cross-built and not run.
available0.5.0

Documentation

FeatureStatusSince
Features list docs-features
docs/features.tsv holds one row per feature with its status and evidence, and tools/features.py generates this page. CI fails when the page is stale, a row is malformed, or evidence is missing.
available0.2.0
Mermaid diagrams docs-diagrams
The architecture, reverse proxy, relay and NAT lab diagrams are Mermaid, which GitHub renders.
No generated images to keep in step.
available0.2.0
Docs in Markdown docs-markdown
Every doc in Markdown with one layout, including the deploy and Docker READMEs with diagrams.
The offline repo-check guard validates tracked local Markdown links and anchors in CI.
available0.2.0
Ways to connect docs-connecting
A page on how players reach a server: same LAN, port forwarding, a public server with -relay, a VPN overlay (Tailscale, ZeroTier) or a reverse proxy, with what each needs, its limits, diagrams and links to the overlays' official documentation.
Not tested: re-psn has not been run over Tailscale, ZeroTier or any other overlay. Overlay addressing was imitated with network namespaces only.
available0.2.0
README rewrite docs-readme
A README that says what re-psn is, with a quick start, the available features linking to this page, installation, configuration and credit to upstream.
Example output comes from real local runs; the settings table was checked against the server flags. The banner comes from the art generator (docs-art).
available0.2.0
Art generator docs-art
tools/art/make_art.py draws the README banner, a social preview image and terminal images in the project palette (docs/art/palette.json), with JetBrains Mono fetched from its tagged release and pinned by SHA-256. Terminal images are drawn only from transcripts of real runs (docs/art/transcripts/, recorded by tools/art/capture.sh). CI renders again and fails when a byte differs.
The README shows the banner; the social image is uploaded by hand in the repository settings; its example stays text, and the terminal images are kept for the site. Images are dark only; the light colours are in the palette for later. Fonts are not committed.
available0.2.0
Project website docs-website
A static page built from the features list, the version, the settings table and the changelog by tools/site.py: overview, features with status tags, roadmap, settings, install, connect, docs and credits. With site/terminal.js (no dependencies) the page becomes a terminal-style interface: menu, content and a command line with the commands features, roadmap, settings, install, connect, changelog, docs and more, suggestions while typing (a dimmed completion and a list of up to eight matches, taken with Tab, Right arrow, Enter or a tap), Tab completion, history, a dark and a light theme chosen with a System / Light / Dark switch in the header or the theme command (the same choice, remembered in the browser), colour from the full gruvbox-material palette with every text colour checked at 4.5:1 or more, and scrolling tabs and tappable chips on a phone (a phone starts in the plain view); the same page works without script, and a plain view link switches the interface off. No third-party loads, a content security policy that allows only this site, and the colours of the README art. pages.yml deploys site/ to GitHub Pages. Command output has a file header, line-number gutter, frame and syntax-style colours.
Partial: the page is published at https://undecuple.github.io/re-psn/ and deploys from main while PAGES_ENABLED is true. Unit tests and optional headless-browser checks cover command typing, autocomplete/history, selection without gutter numbers, dark/light and system-theme changes, narrow layouts and static fallbacks. A real keyboard, screen reader and physical phone have not been tried. The font remains the visitor's monospace; self-hosting is not added.
partialmain
Repository layout check repo-check
An offline CI check of tracked root filenames and top-level directories, local Markdown links and heading/explicit anchors, and static repository input paths in workflows and Dependabot.
Python standard library and Git only. Does not fetch external links, evaluate arbitrary shell/YAML expressions or validate generated artifact paths.
available0.4.0
Recomp developer integration docs-integration
A source-pinned guide for ps3recomp ports and direct C/C++ adapters: build/startup, callback ownership, message lifetime, failure handling and peer/relay boundaries. A synthetic minimal C example exercises HELLO and room search in the required native-client test.
Runtime integration was inspected at the linked upstream revision; no actual title was built or run for the guide. Native example/conformance checks are synthetic loopback evidence, not gameplay or NAT certification.
available0.4.0

Roadmap

The same list grouped by status. No dates are promised; ROADMAP.md has the order and the scope.

Done (55)

Next (15)

Later (9)

Not planned (1)

Changes

Unreleased

Changed

0.6.0 (2026-11-12)

21 under Added, 18 under Fixed, 10 under Changed.

The full history is in CHANGELOG.md.

Settings

Each of the 35 flags can also be set as a RE_PSN_<FLAG> variable. A flag on the command line wins, then the variable; a value that does not parse stops the server at start. It runs with no settings at all. docs/running.md has the full text.

FlagVariableDefaultWhat it does
-addrRE_PSN_ADDR:36100TCP and UDP address for game clients
-tcp-addrRE_PSN_TCP_ADDR-addrTCP address for game clients, if different
-udp-addrRE_PSN_UDP_ADDR-addrUDP address for game clients, if different
-httpRE_PSN_HTTPnone (off)Status page and /api/stats on this address, for example 127.0.0.1:36101
-http-tokenRE_PSN_HTTP_TOKENnoneToken the status page requires (Authorization: Bearer, or ?token=)
-adminRE_PSN_ADMINnone (off)**Experimental.** A read-only admin API on its own address; needs -admin-read-token-file, and an address that is not loopback also needs -admin-tls-cert and -admin-tls-key (admin-api.md); not in v0.5.0
-admin-read-token-fileRE_PSN_ADMIN_READ_TOKEN_FILEnoneFile holding the admin read token (at least 32 characters, mode 0600), read again on SIGHUP; not in v0.5.0
-admin-tls-certRE_PSN_ADMIN_TLS_CERTnoneCertificate file for the admin listener (TLS 1.2 or newer); not in v0.5.0
-admin-tls-keyRE_PSN_ADMIN_TLS_KEYnonePrivate key file for the admin listener; not in v0.5.0
-max-roomsRE_PSN_MAX_ROOMS1000Rooms across all titles
-max-player-roomsRE_PSN_MAX_PLAYER_ROOMS8Rooms one player may be in at once (0: no limit)
-max-connsRE_PSN_MAX_CONNS4096Open TCP connections in total, players and relay legs (0: no limit); not in v0.5.0
-max-conns-per-addrRE_PSN_MAX_CONNS_PER_ADDR32Open connections per remote address; an IPv6 address counts as its /64 (0: no limit)
-max-req-rateRE_PSN_MAX_REQ_RATE200Requests per second per connection before it is closed (0: no limit)
-max-udp-rateRE_PSN_MAX_UDP_RATE1000UDP packets per second per source (IPv4 address or IPv6 /64), shared by probes and relay traffic; same-size burst (0: no limit); since v0.4.0
-vRE_PSN_VoffSame as -log-level debug
-log-levelRE_PSN_LOG_LEVELinfodebug (room events), info, warn or error
-log-formatRE_PSN_LOG_FORMATtexttext or json
-log-fileRE_PSN_LOG_FILEnoneAlso append the log to this file (mode 0600)
-trusted-proxiesRE_PSN_TRUSTED_PROXIESnoneReverse proxies allowed to name the real client (CIDRs or addresses)
-proxy-protocolRE_PSN_PROXY_PROTOCOLoffRequire a PROXY v1/v2 header from a trusted proxy on the game TCP port
-udp-any-sourceRE_PSN_UDP_ANY_SOURCEoffAccept UDP probes and relay packets from another address than the player's TCP one
-relayRE_PSN_RELAYoffCarry traffic for players who cannot reach each other (uses the host's bandwidth)
-relay-idleRE_PSN_RELAY_IDLE600With -relay, close a relay stream after this many seconds with no bytes in either direction (0: never); not in v0.5.0
-relay-forceRE_PSN_RELAY_FORCEoffWith -relay, send every stream through the relay, even between players who could connect directly. For testing the relay from one network; uses the host's bandwidth
-no-peer-titlesRE_PSN_NO_PEER_TITLESnoneComma-separated communication IDs of titles that use no peer traffic. For their players the server answers no UDP probe, relays nothing and routes nothing, and HELLO_ACK says the relay is off; rooms and scores work as usual. Other titles are unaffected; not in v0.5.0
-discoverRE_PSN_DISCOVERoffAnswer LAN discovery broadcasts on the game UDP port, from private-network senders only (connecting.md)
-nameRE_PSN_NAMEnoneServer name shown in LAN discovery: at most 24 printable ASCII bytes; shown only with -discover
-lan-public-addrRE_PSN_LAN_PUBLIC_ADDRnoneIPv4 address given to players outside the LAN as the address of players who connected over a private, carrier-grade NAT or link-local address; the host must forward each such player's P2P port
-mediusRE_PSN_MEDIUSoffMedius compatibility switch. **No Medius protocol is implemented** (scope): it opens the -medius-port listener, which closes every connection; not in v0.5.0
-medius-portRE_PSN_MEDIUS_PORT10071TCP port of the Medius listener; a value other than the default needs -medius; not in v0.5.0
-medius-addrRE_PSN_MEDIUS_ADDR127.0.0.1Address of the Medius listener: a loopback, private or link-local IP address, or 0.0.0.0 / :: in a container; a value other than the default needs -medius; not in v0.5.0
-dbRE_PSN_DBoffScore storage: off or memory
-db-fileRE_PSN_DB_FILEnoneRead the -db setting from this file instead
-replaceRE_PSN_REPLACEoffIf an earlier re-psn of this user holds the game port, stop it (SIGTERM) and start in its place (Linux only)

Install

The latest release is v0.6.0, a pre-release.

Release archives

Archives for Linux (amd64, arm64) and Windows (amd64), and a Linux x86_64 AppImage, all unsigned, with a SHA256SUMS file. Check a download on Linux:

sha256sum -c SHA256SUMS --ignore-missing

On Windows, compare the output of Get-FileHash <archive> with the line in SHA256SUMS.

Container

docker pull ghcr.io/undecuple/re-psn:0.6.0
docker run --rm -p 36100:36100 -p 36100:36100/udp ghcr.io/undecuple/re-psn:0.6.0

Podman works the same way. See docker/README.md.

From source

You need Go 1.22 or newer; a C compiler only for the C client and its tests.

go build -o re-psn ./server
./re-psn

Connect

Players on one LAN or VPN need nothing forwarded. Otherwise the host forwards a port, runs a public server, or uses an overlay network such as Tailscale or ZeroTier; the relay is the fallback when two players cannot reach each other.

A game reaches the server through the C client in client/psnr.c, via ps3recomp's NP modules. That side is upstream's work; docs/connecting.md covers each way to connect.

Docs

Source and issues: https://github.com/undecuple/re-psn.

Credits and licence

MIT, see LICENSE; upstream's copyright line is kept and a line for the fork's changes is added. PlayStation, PS3 and PSN are trademarks of Sony Interactive Entertainment, used only to say what the server stands in for.

Top