* Post-processing: Eden's ReShade FX presets in Settings > Video and in a game's own settings
* Launcher texts: the appearance, wallpaper and launcher texts in every language
* Post-processing in the README, the settings guide and the changelog: where presets and effects go
* Slate polish: warning popup, accent saved, setup card over the Library
Warnings no longer draw inline under whatever card is on screen:
say() with a warning puts them in a centered dismissible popup drawn
last. Any button press closes it and the action carries on, so a
still-failing action simply shows it again. "Saved ..." messages stay
inline as before.
The accent choice is carried by preferences() again, so saves no
longer erase "launcher": { "accent": ... }. Switching themes clears
the accent together with the save and restores both if that save
fails. preferences_check roundtrips "ice", keeps an empty accent
absent, and rejects "ICE" or path tricks.
The setup-required card leaves the bottom-left: on Slate (top_nav) it
draws last, centered over the Library's panel on Home and Library, and
not at all on Multiplayer, Settings or About. Canopy keeps its old
place. The panel's geometry moves to layout so the card and the panel
share one source.
Also: Color::rgb is constexpr; THEMES.md documents the theme fields.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Clear the warning card when a screen or dialog changes
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Console memory: a memory layout per game
A mod that keeps a block of memory of its own asks the guest kernel how much
the game has and reserves from the answer, so one that wants more than the
standard 4 GiB stops the game before its first picture. Every session was
pinned to 4 GiB with no way to ask for more; "memory_layout" in the settings
file, for every game or for one, now reaches the emulator before the guest's
memory block is taken.
* Console memory: the size in Settings > Performance
The larger memory was only a settings-file value, so a game with a mod that
wants more than 4 GiB had to be set by hand - and any change on the
Performance page re-saved that block of the file without it, taking the
hand-written value back out again. The page now has a Memory row after the
seven switches: Left and right move over 4, 6 and 8 GiB, and it writes the
same "memory_layout" key the file holds and reads it back, so the menu and
the file are one thing and nothing between the two has to be kept in order.
A game can still have its own in the file, which comes first. The row needs
no new text: "Memory" is the overlay's word for it already, and the sizes
are numbers.
---------
Co-authored-by: BlackBearReloaded <blackbearreloaded@protonmail.com>
* Say the game's own resolution in the overlay
The overlay so far told what was sent to the display; a game's own picture
was only to be found by guessing. Overlay options > Resolution puts the size
the guest renders at next to the backend, before the frame rate, so a game
that scales its picture down in heavy scenes shows it as it happens.
The size is taken where the frame is handed to the display, a cropped layer
counted as the part shown, as frame generation's own GuestExtent counts it.
It is a new bit at the end of the field mask, so a stored mask keeps meaning
what it did; the piece is off on a console that has one until it is switched.
* Validate overlay preferences against supported HUD bits
---------
Co-authored-by: BlackBearReloaded <blackbearreloaded@protonmail.com>
tools/eden-patches/*.diff are applied in name order to the pinned Eden
tree, each once: a patch that reverses cleanly is already in. One that
neither reverses nor applies was changed after an earlier version went
in, and the files it touches come back from the pinned archive first.
- 01 fast file reads: read-only files are served from recently read
1 MiB blocks. Each system call on the console's storage costs about the
same whatever its size, so a game's small reads no longer pay it each.
Writable files keep stdio's buffered view, which has their unflushed
writes.
- 02 cache storage per title: a cache storage request with title id 0
means the current process, as switchbrew documents for save data.
Otherwise every game's cache storage was one shared folder, and two
games of one series writing the same file names read each other's GPU
shader caches.
- 03 bounded shader local memory: an out-of-bounds local memory load reads
zero and a store is dropped, both through word 0. The PS5 faults on
scratch accesses outside the allocation, which killed the process when a
game indexed a local array with -1 after its intro.
- 04 CPU reads without reactive flushing: plain CPU reads of GPU-cached
pages download GPU-written data only when reactive flushing is on, as
with fastmem, where such reads never trap. Without fastmem every read
otherwise took the rasterizer caches' locks, one 4 KiB page at a time.
docs/BUILDING.md says that the fork now patches these files.
Co-authored-by: Claude <noreply@anthropic.com>
* Validate Eden room packets and add offline interoperability checks
* Bound proxy decompression and receive queues
* Add asynchronous room controller and cancellable connections
* Wire room settings and lifecycle into the launcher
* Bound outgoing room queues and test stalled peers
* Own room receive callbacks per proxy socket
* Support proxy polling and release sockets between games
* Retain guest sockets across descriptor changes
* Record offline qualification and freeze fresh candidates
* Record room server and read-only console preflight evidence
* Show room members and reported games in a scrollable list
* Document Tailscale capabilities and verify room rejection recovery
* Record remaining hardware validation prerequisites
* Record successful room transport over Tailscale
* Record verified console deployment for operator testing
* Use a supported keyboard type for masked password entry
* Remember room passwords and report connection failures
* Report ENet handshake errors and packet counts
* Parse numeric room addresses before invoking the resolver
* Record operator-confirmed PS5 room connection
* Use the room virtual interface for guest wireless networking
* Compose room interfaces with the native getifaddrs adaptation
* Exercise LDN discovery and peer connection through a real room
* Record PS5 Mario Kart wireless lobby validation
* Record live LDN traffic, recovery and Tailscale component validation
* Prepare official desktop reference and correct room interface guidance
* Record public room connection and native DNS failure
* Show multiplayer connection status on home and settings
* Make external Tailscale qualification optional
* Invalidate network compiler cache for generated room headers
* Use the pinned Boost version in multiplayer host checks
* Show room server ping in the game overlay
* Record verified ping overlay deployment to PS5
* Respect detailed logging settings for multiplayer diagnostics
* Make the room socket non-blocking on the console so the member loop can send the join request
* Add public room browser beside private multiplayer connection
* Keep multiplayer destination choice selectable while connected
* Record local public browser validation and deferred console test
* Explain multiplayer connection failures in a persistent UI panel
* Define idle-only multiplayer validation on console .40
* Record console .40 public directory and private room validation
* Update public room notes for PR publication
* Keep the saved private room on public joins, save the private form as typed, and harden the public room list
* Translate the multiplayer and public room texts in all 29 languages
* Eden at 67bada77
* Patches Eden now carries itself are removed; sparse tables follow its new vector
* Emitter header anchors for Eden 67bada77
* Memory checks use the JIT callback that takes a size
* Development hooks follow the JIT callbacks that take a size
* GPU lifecycle follows Eden 67bada77
* Frame generation log lines follow Eden 67bada77
* alloca for the effect compiler Eden now builds
* NSO segments, ZBIC ones too, are decoded in place
* The NSO loader check knows the ZBIC branch
* The producer check declares the shutdown the worker file now defines
* A thread's context guard and the slab lock stay spin locks
* The sparse vector's page constants do not depend on initialization order
* The exclusive monitor check uses the Boost that Eden now fetches
* Vulkan driver: the latest PS5_Vulkan tools, with the Mesa and Payload SDK they build
* RADV display adaptation for the newer Mesa: a set of framebuffers per output size
* Eden at 67bada77 (pins only; the port is adapted in the next commits)
* Add nixshell
Simple shell.nix to provide a full build enviorment.
* Update Building docs
Include nix-shell
* Update Nix shell recommendation wording
Removed the word 'recommended' from the Nix shell section.
* Building docs: keep the loading screen tool as it is on main
---------
Co-authored-by: BlackBearReloaded <blackbearreloaded@protonmail.com>
* The loading screen names the step a game's start is at
* The loading screen is a progress bar with the steps of the start
* Loading screen: the lettering is read in one place
* Loading screen: one small table per glyph
* Loading screen: smaller lettering tables
* Pin the audio helper header as it is since its comment changed
* Release workflow: build on the ubuntu-24.04 runner itself, without a container
* Release workflow: Meson where the build scripts look for it
* Download sources: games on the network in the Library, downloaded when wanted
A download source is a server in the network that has Switch games. Its games are
listed in the Library beside the ones on the console, with the sources that have them.
A source says what it knows of a game: its title ID, its ids at metadata providers
(several at once) and whether its name comes from such metadata. The same game on
several sources is one title, and a game already on the console is not offered again:
by title ID, then a provider id both have, then the normalised name, else the file
name. Cross downloads a game and starts it once
it is there, asking first which source when several have it; Square puts it in a
download queue, which goes on in the background and after the app was reopened. Nothing
is streamed: a game runs from its file in roms/, its updates and DLC go to updates/. A
download is written to .remote-downloads/ in the game files folder and, once all of
the game's files are complete, moved into place (a cancel never touches roms/ or
updates/); a stopped one resumes where it was, its last 4 MB fetched again. A game
a source has can be deleted from the console in its settings: the game and all of its
updates and DLC go, its save data and settings stay. Settings > Downloads shows the
sources and the queue.
The sources are set up in config/remote/sources.json. Each entry names its backend's
"type": a backend implements the Source interface (headless/remote/source.h) and is made
in backends.cpp; remote.cpp, the launcher and EdenServices only know that interface.
This commit has no backend yet. remote/http.c is HTTP for the backends that need it,
linked into the update check's libcurl object.
Settings has one category more, so the preview walks one row further to Language.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Download sources: a RomM backend
"type": "romm" in sources.json is a RomM server (https://github.com/rommapp/romm), with
its address and a client API token, or a user name and password. Its Switch games come
from the game list's pages with each ROM's files; RomM's file categories say what is the
game and what are its updates and DLC (manuals, mods and the like stay on the server);
its ids at the metadata providers RomM matched, its title ID when the server has the keys,
and whether RomM identified it tell the game apart. A
file is downloaded by its own id (/api/roms/<file id>/files/content/<name>), the endpoint
RomM's feeds use, and goes on from where it was (Range). A current RomM is assumed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Download sources: tests and preview pictures
tools/check-remote.py builds the download sources (headless/remote) with their RomM
backend on the host and runs headless/remote_check.cpp against tools/romm-mock-server.py,
a stand-in for the parts of RomM's API the backend uses: sign-in, pages of games with
their files' categories, covers, downloads by file id with Range, a server that ignores
Range, a slow download to stop and cancel. It checks which game is which (title IDs,
provider ids, normalised names, file names), one source and two (a game of another
file name on the second one is the same title, the same game queued from one of them
only, a source of an unknown type, a source taken out of sources.json), going on after a stop with a spoilt end, a cancel that leaves the
player's own updates alone, a server that pages fewer games than asked for, two copies
of a game on one source, the cleaning of .remote-downloads/ and the kept queue. make test runs it; it is skipped without
libcurl's headers.
The launcher preview's sample services have two sources, Home and Office, one game on
both, and the preview draws the new screens (80-98): the Library with games on the
sources, the download dialog, choosing a source, a failed download, Settings >
Downloads, and deleting a game from the console.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Download sources: written by the console's FTP server, covers, speed and status
Written by the app itself, a download slowed the drive down as the file grew
(65 MB/s at first, 2 MB/s after 2 GB) and stopped the menu for seconds per
frame. From a payload the same drive keeps its speed, so the app now hands
the bytes to an FTP server running on the console (ftpsrv, etaHEN's, ...)
over loopback: REST to go on at a byte, STOR to write (headless/remote/ftp.h).
Downloads need such a server; without one they fail and say so. sources.json
may name its port ("ftp_port", 2121 by default) and a sign-in. File names
with a line break are refused, so a source cannot send the server commands.
When the server stops taking a file (a full drive), the download fails with
what it said ("No space left on device"); what was written stays in
.remote-downloads/ to go on from once there is room.
- Crash: the free-space check called statfs, which the SDK's libc.a has as
a raw system call that ends a title. The check is left out on the console.
- Library: a game downloaded from a source is that source's game by the
file name it came as, before the title IDs: a server's title ID can be
another than the console reads from the file (Super Mario Galaxy 2 on the
test server), and the game was then listed twice.
- Library: it reads all of the console's games again only when a source's
list changed or a cover came, not after every reading of an unchanged
list (that rewrote every cover and could make the menu flicker).
- Covers: Eden builds stb_image for JPEG only, and RomM's covers are PNG
("the cover of ... cannot be read"). metadata_bridge.cpp has a reader of
its own for both, private to it.
- Speed: measured by the download thread, shown in the download dialog and
in Settings > Downloads; the time left comes from it. Game files are not
asked for compressed, and libcurl takes them in pieces of up to 1 MB.
- Menu: a cancel no longer deletes gigabytes while holding the queue's lock
(the download thread does it, also while a game runs), and the menu asks
for covers without touching the drive.
- Log: download sources write only what went wrong (on this console every
line is a write to the drive the menu may wait for).
- Settings > Downloads: says when the lists asked for are in (a message
gives way to the buttons after 4 s: with one source no row can be moved
to, which takes it away elsewhere), shows the FTP server's port, and
without a source shows an example with a RomM server. German added for
the new texts.
- README: the FTP server is named where download sources are.
- Tests: tools/ftp-mock-server.py writes as ftpsrv does; check-remote runs
every download through it, plus a download without an FTP server, a full
drive and a file name with a line break.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Download sources: backends by what they can do, RomM's server part on its own
Groundwork for more uses of a backend than downloads (the save data of a
profile next): a backend's type now says what it can be used for, and what
all uses of a RomM server share is a part of its own.
- backends.h: Backends() lists each type with what it makes, one column per
use (for now: `source`, a download source; nullptr when its servers have
no games to download). MakeSource moved here from source.h and says when a
type cannot be a download source. A new use is a column and a Make... of
its own.
- romm/romm_client.h: Romm::Client, made from an entry of any config file
(its errors name that file): the address, the sign-in, a JSON GET, a file
into memory, the platform's id and the list of games. NormalUrl and
ParsePage moved with it.
- romm/romm_source.cpp: the download source is what is left: the cover and
the file download, on top of Romm::Client. Nothing changes for sources.json.
- remote_check: the table, and a RomM entry read for another file.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Download sources: review fixes
- A download cancelled and queued again before its transfer stopped goes
on instead of being dropped with its files.
- A cancel that comes once the game is all in place leaves it done: the
Library reads its list again.
- The covers' paths are taken while the lock is held: Start sets the
paths again while a list thread may still fetch covers.
- Settings > Downloads: a row the queue no longer has (a download that
finished meanwhile) is not drawn from past its end; trying a failed
download again moves the rows with the queue.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Download sources: a download's contents checked as they come
RomM does not hash Switch files (they are in its NON_HASHABLE_PLATFORMS),
so a download could only be told whole by its size. Now each file is
checked while its bytes stream to the console's FTP server, as Eden's
integrity check checks a game, with no keys needed and without reading
the file again afterwards.
- remote/stream_check: reads the file's layout from the stream itself
(an NSP's PFS0, an XCI's header, root HFS0 and secure HFS0) and hashes
each NCA with OpenSSL's SHA-256 as its bytes go by, against the half of
the SHA-256 it is named after. The first 1 MB is kept for headers asked
for after their bytes went by. A gap, an unknown layout, a header that
points past any file or a file that is no NSP or XCI leaves it
unchecked, not damaged.
- remote: each file's bytes go to a StreamCheck on their way to FTP. A
download that goes on from where it was reads what its file has so far
from the drive first (the queue shows it as verifying, up to where it
goes on); a server that sends the whole file anyway starts the check
again. A damaged file is deleted and the download fails saying so, so
trying again downloads it again. What the FTP server writes is not
read back.
- Launcher: "Checking the game files 45%" in the download dialog,
"Checking 45%" in the Library and Settings > Downloads, with the
progress bar. German added; the preview's Orbit Postman goes on from
40% and is shot while checking (83b). README.
- remote_check: synthetic NSPs and XCIs fed in pieces, overlapping,
restarted, with gaps, spoilt, with headers of absurd sizes and as no
NSP; check-remote links libcrypto and is skipped without its headers.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Save sync: save-sync.json, and a game's save data in step with RomM
Each player profile can keep its save data on a server, apart from the
download sources: another server, or another user of the same one. This
is the part without the menu: the config file, the store's interface,
RomM's store and the sync itself.
- save-sync.json (save_sync_config.h): the app writes an entry for every
profile and keeps it in step with them (added, renamed, removed, in
their order); the player fills one in over FTP with a server's "type",
"url" and sign-in. What the player wrote stays; a file that is not JSON
is left alone and nothing syncs. eden-save-sync-check.
- remote/save_store.h: a save store, a backend's interface of its own
beside the download sources'; Backends() has a column for it.
- remote/save_archive.h: a game's save folder as one zip with the title's
folder at its top, as Argosy packs Eden's, and RomM's content hash of
it (MD5 of "<name>:<MD5>" lines by name, MD5 from OpenSSL), told from
the folder. A folder that cannot be read whole fails instead of being
taken as less. Unpacking takes JKSV's zips and Mac zips (without
__MACOSX/ and .DS_Store), keeps a zip of one folder of the game's own,
and refuses entries outside the folder. miniz is built into the app.
- remote/save_sync.h: SyncSaveData finds the game on the store and tells
which side changed by the save data, not by the server's times: the
console keeps the content hashes of both sides from when they were last
in step (synced.json), bound to the server, the user and the store's
game. One side changed: it uploads or downloads; both, or no record:
the player chooses, so save data changed offline is never replaced
unasked. Replaced save data goes to a backup folder first, the last
three of a game kept.
- remote/romm/romm_saves.cpp: RomM's save sync, RomM 5.0.0 or newer
(Romm::kMinimumVersion; an older server is refused at each sync). The
console is registered once as a device of the user; a game's save data
is in the slot "autosave" of the emulator "eden", as Argosy keeps it;
the server's state is read from /api/saves (RomM 5.3's negotiate only
for the session, as it pairs other emulators' save data with the
console's). A refused upload (409) is a conflict.
- romm_client: requests with a JSON body, uploads as a form field and
downloads to a file, all stopped when the sync is; a refusal (403)
names both causes, a wrong sign-in or the token's missing scope.
- remote/http: POST and PUT with a JSON body or a file as a form field; a
redirect sends a POST again with its body.
- remote_check: the zips, RomM's times and versions, save stores made
from entries.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Save sync: synced before a game starts and after it ended
- Launcher: a dialog while a game's save data is synced before it
starts; the game starts when it is done. Circle goes back without the
game (the sync stops, nothing more changes). When the server cannot
be reached: Play anyway, Try again or Back.
- After a game, once the menu is back, its save data goes up with a
notice at the top right, and is tried again on later starts until it
worked (pending.json). "auto": false stops those too.
- Changed on both sides: a dialog asks which one stays (Keep this
console's, Take the server's, Change nothing), with when each was
saved and the device of the server's.
- A server older than the save sync takes says so in a dialog to
confirm, with both versions (before a game: play anyway or not; after
one: Understood, or a notice when another game is about to start).
- The sync runs on a thread of its own in EdenServices; a game's title
ID is read there, so the menu never waits for a scan of the games.
- arc() moves to widgets for the sync dialog's spinner.
- Preview A0-A7; German added; README: Save sync.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Save sync: profiles paired by a QR code
Setting a profile's save sync up needs no FTP and no typing.
- remote/pairing.h: a backend's pairing (as RFC 8628): a code and the
address to approve it at, then polled until approved, denied or
expired, stopped when it is cancelled. RomM's: /api/auth/device/init
and /token, approved on the server's /pair/device signed in as the
profile's user; RomM makes a device of the console with a client API
token bound to it, which the save sync then is (no second device).
- save_sync_config.h: SetEntry links or unlinks a profile's entry, the
player's own fields kept.
- Settings > Save sync: each profile with its server and user; Cross
pairs it with one of the download sources' servers that can pair
(asked first when several can; the source's "platform" kept), Square
twice unlinks it. The pairing dialog shows a QR code of the approval
page (Project Nayuki's QR Code generator library,
third_party/qrcodegen, MIT), the code, the address, the steps and how
long the code works.
- Preview B0-B6; German added; README.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Checks: the download sources and the save sync against RomM and ftpsrv in Docker
tools/check-romm.py [version ...] starts, for each RomM version, RomM
and MariaDB on a free port of localhost with a library of fake Switch
games (laid out as real NSPs and XCIs), and ftpsrv (the console's FTP
server, built for Linux from a pinned commit) on the host's network
(its passive mode needs it); makes an admin and a player with client
API tokens, sets a cover in RomM, scans the library over RomM's socket
(the scan task cannot be started over REST), and runs
headless/romm_live_check.cpp against it; then takes it all down. By
default it checks kMinimumVersion (5.0.0), 5.3.1, and 4.9.0, which has
to be refused. make test runs it after check-remote.py; without Docker
it is skipped, ROMM_CHECK=0 skips it too.
romm_live_check:
- Download sources: sign-ins, the list kept, a cover, what is no game, a
game of 400 MB stopped part way and gone on with by Range (seen
reading what it had from the drive first) and compared byte for byte,
a spoilt end, an NSP and an XCI spoilt on the drive found damaged,
updates and DLC, a cancel, leftovers, no FTP server, two sources on
one server, sources removed or moved, the kept queue.
- Save sync: two consoles and a phone (Argosy's API calls): uploads, a
save of 150 MB with 400 files, a device deleted in RomM, the versions
kept, Argosy's and JKSV's zips, another emulator's and slot's save
data, both sides changed in both orders, backups, a slot emptied in
RomM, a token without scopes, a server that does not answer, an older
RomM refused at each sync, and pairing approved, denied and cancelled.
check-remote.py keeps what needs no server and, against the stand-ins,
only what a real RomM and ftpsrv do not show on demand: capped pages, a
server that cannot resume, metadata ids and title IDs, another file
name on a second source, a full drive. romm-mock-server.py serves the
list and files only. docs/BUILDING.md says how to run it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Sample names in the checks and comments: invented ones
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: BlackBearReloaded <blackbearreloaded@protonmail.com>
* PS5 firmware 7.40: no mprotect on the macro path, lld 18 weak imports
On firmware 7.40 an app that is not jailbroken cannot mprotect(RX) memory, so
the GPU macro JIT's first compile ended every game session with xbyak's
"can't protect" (OpenGL then also reports the guest's 2010-0212 abort during
teardown). The CPU JIT is unaffected because it writes through a separate RW
alias. Run the few non-HLE macros through the interpreter on the PS5 build
instead: no executable memory on that path.
lld 18 exports undefined weak symbols from executables (lld 21+ no longer
does), and Mesa's entrypoint tables reference thousands of optional driver
entrypoints weakly, which the native packager then rejects. --no-dynamic-linker
is lld 18's switch for that; it is redundant on newer lld.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCGd2KgMjDTQnfiHzJbrxx
* docs: firmware 7.40 report and ubuntu-24.04 build notes
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCGd2KgMjDTQnfiHzJbrxx
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>