Confirmed live on a real install: after the previous commit, ES-DE found
zero games on every system. Root-caused by walking GOW's own
apps/es-de/build/scripts/startup.sh line by line: GOW's image bakes a
FULL es_settings.xml template (100+ settings, including
ROMDirectory=/ROMs, confirmed against the real template file in the
games-on-whales/gow repo) and its own startup script copies that
template into place exactly once, the very first time the container
starts, gated on the settings file not already existing.
The previous fix pre-created a minimal stub file (containing only
RunInBackground) before the container had ever started - GOW's startup
script saw that file "already there", skipped writing its own template
entirely, and ES-DE fell back to vanilla upstream defaults for
everything else in that file, including ROMDirectory, which is not
/ROMs by default. Every system's games vanished as a result.
Also corrects an earlier wrong claim in both the code comment and
README: ES-DE's own *compiled* default for RunInBackground is false, but
GOW's own template deliberately sets it to true - that's the real,
confirmed source of the original background-launch bug, not an
accidental toggle as previously assumed.
Fix: only ever patch an es_settings.xml that already exists with real
content (`[ -s "$_ESDE_SETTINGS" ]`) - never create one. On a genuinely
fresh install, before EmulationStation has ever launched once, the
installer now says so explicitly and defers rather than guessing, with
clear instructions to connect once and re-run. Verified against GOW's
actual real template: the patch flips just RunInBackground (line count
unchanged, ROMDirectory and everything else untouched), is idempotent on
re-run, and the skip-when-absent gate was verified directly against both
the missing-file and populated-file cases.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
Live user feedback: being asked to pick between 6 numbered entries that
were all the exact same device (client_id repeated 6x from re-pairing)
was genuinely confusing, especially right when the user was already
trying to get to the controller-type poll further down the flow.
The picker now dedupes to distinct client_ids only, first-occurrence
order, tagging a collapsed entry "(paired Nx)" - matches what Wolf's own
get_client_by_id() actually resolves to anyway (first match for a given
id), so nothing is lost by not offering the later duplicates as separate
choices. Verified against the user's real 8-entry (6 duplicate + 2
unique) client list, both with and without an active session, confirming
the controller log-poll still runs correctly right after selection in
both cases.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
Fixes a real, confirmed-live, cross-system bug: with ES-DE's "Run in
background (while game is launched)" enabled, ES-DE keeps listening to
every controller even after a game/emulator takes focus - a second
controller's input can reach ES-DE's own menu and launch a completely
different game underneath whatever's already running. ES-DE's own
USERGUIDE.md names this exact failure mode.
ES-DE's compiled default for this setting is already off (confirmed
against its real Settings.cpp), but nothing in this repo asserted that
durably, and ~/ES-DE (settings, gamelists, scraped media, logs) had no
mount onto the game drive at all - confirmed against ES-DE's own source
(getAppDataDirectory() is a plain $HOME/ES-DE) and GOW's own es-de
startup script. Wolf normally reuses the same app container across
sessions rather than recreating it each time (confirmed against Wolf's
docker.cpp - it only removes the container on exit if
WOLF_STOP_CONTAINER_ON_EXIT=TRUE, which this repo never sets), so this
mostly didn't bite day to day, but it meant the setting - and everything
else under ~/ES-DE - wasn't safe across an actual reinstall the way
roms/saves/BIOS already are.
Mounts esde-settings/ onto ~/ES-DE/settings specifically (additive,
doesn't touch the existing custom_systems mount) and writes a surgical,
idempotent RunInBackground=false into es_settings.xml on every install -
never a full rewrite, since that file is the user's own complete
settings state. Verified the write logic against fresh-file, existing-
file-with-other-settings, and flip-an-existing-true-to-false cases, plus
idempotent re-runs.
Gamelists/scraped-media durability under ~/ES-DE is a related, noted gap
- not fixed here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
Two follow-ups from live testing that just confirmed the whole
controllers_override chain works end to end:
1. manage.sh controllers now polls `docker compose logs wolf` for
"Creating <TYPE> joypad for controller <N>" lines (most recent per
slot wins) and shows what it last saw before asking anything, using
it as the suggested default at each prompt. Every prompt is now
labeled "1st controller"/"2nd controller"/etc - always meaning
Wolf's own 0-indexed "controller 0"/"controller 1" - instead of
asking for a raw slot number, closing the exact client-vs-controller
0-vs-1 confusion surfaced live earlier. Documents plainly that this
can show what TYPE a slot last used but can't show or control WHICH
physical controller becomes which slot - that's decided by Moonlight
client-side, upstream of Wolf's API entirely.
2. Documents a full recovery procedure in the Wii U section for when
Cemu's Input Settings Save button is cut off-screen (confirmed live:
happens even maximized, at higher resolutions, and closing the
window doesn't save) - an Alt-drag/Alt+F7 window-manager fix first,
then editing Cemu's own controllerProfiles/controllerN.xml directly
on the host (plain XML, not inside the container) as a full GUI
bypass. Documents the key insight that made this actually work live:
<mapping>/<button> pairs are universal SDL_CONTROLLER_BUTTON_*
semantics (confirmed earlier against real captured profiles from
different controller brands), so a working mapping can be copied
verbatim into a different device's empty <mappings/> block without
redoing the live button-capture in Cemu's GUI at all.
Verified the redesigned manage.sh controllers flow end to end against a
mocked docker compose logs matching the user's real log output (both
the log-seen and no-logs-seen paths), plus the ordinal-suffix logic in
isolation.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
Live user feedback: "Exactly one client is actively streaming right
now" sitting directly above "How many controller slots" read as "only
one controller is connected" - an easy misread since "client" (a paired
Moonlight session/device) and "controller" (a physical gamepad slot
within that session) are conflated by ordinary English but mean
completely different things here. The tool has no way to detect
controller count at all; it only ever picks which Moonlight
session/device to configure.
Reworded every message in this flow to say "Moonlight session/device"
instead of bare "client", and added an explicit line before the
controller-count prompt clarifying it's a separate question the user
has to answer themselves. Verified the full flow still runs correctly
end to end against the same mocked duplicate-heavy client data used
earlier.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
Confirmed live: the paired-clients list is a wall of meaningless
cert-derived numbers with nothing human-readable attached (verified
against Wolf's own PairedClient API schema - no name, no IP field
exists there at all), so there was no way to tell which entry was the
user's actual device. Made worse by Wolf not deduping repeated pairings
of the same device (the user's real box showed the same client_id 6
times).
Fix: cross-reference against GET /api/v1/sessions, which DOES carry
client_ip for every currently-streaming session, and tag each paired
entry as "ACTIVE - streaming from <ip>" when it matches one. When
exactly one client is actively streaming, it's now auto-selected with
no prompt - the practical case for a single-user setup. Falls back to
the manual picker when nothing's active or multiple clients are (now
showing which IP is which, so the choice is no longer a guess). Verified
against the user's real duplicate-heavy client list plus mocked active-
session data covering: single active session (auto-select), no active
sessions (manual fallback), and two distinct active sessions (manual
fallback with both IPs shown).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
Confirmed live: a plain-user curl against /var/run/wolf/wolf.sock failed
with "Immediate connect fail ... Permission denied". The socket is
srwxr-xr-x root:root - Docker auto-creates the host-side /var/run/wolf
bind-mount source as root, and Wolf itself runs as root in its
container, so group/other never had write permission on the socket file
(required to connect() to a unix socket, not just read/traverse it -
`ls -la` on the containing dir working fine was a red herring).
Fix: sudo on both curl calls, matching how every other root-owned Wolf
state file ($WOLF_CFG) is already touched elsewhere in this script via
`sudo python3 -`. Documents the sudo prompt in the README too.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
Spells out what the three controllers_override values actually create
(Xbox One, PS5 DualSense, Switch Pro Controller - confirmed against
inputtino's own source, not just the enum names) instead of leaving the
reader to guess from bare AUTO/XBOX/PS/NINTENDO tokens, plus a note that
forcing a non-matching type only affects on-screen button-prompt
artwork, not functionality.
Adds a 4-controller section: Cemu's own Input Settings goes up to 8
slots and Wolf's wire protocol has no hardcoded 4-controller cap
(confirmed against control/input_handler.cpp), so the ceiling in
practice is the Moonlight client's own limit. With only 3 concrete
forced types available, a 4th controller can't get a guaranteed-unique
GUID - documents the one-at-a-time Cemu binding workaround for that
case, flagged as reasoned but not confirmed live.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
Root cause (confirmed against inputtino's own uinput device-creation
source, and against Wolf's real virtual-pad architecture): every
virtual gamepad Wolf creates of the same type gets an identical SDL
GUID, since a GUID identifies a controller model, not a physical
instance - the same behavior two real identical controllers would have.
This isn't a Cemu bug specifically; it's why any app whose own
controller picker keys off GUID (Cemu confirmed live) can't reliably
tell two same-type Wolf pads apart, driving every player from
controller 1.
Wolf's own per-client `controllers_override` setting sidesteps this at
the root by forcing each controller slot to a genuinely different pad
type (e.g. slot 1 = Xbox, slot 2 = PlayStation), so their vendor/product
IDs - and SDL GUIDs - actually differ. `./manage.sh controllers` drives
this via Wolf's real REST API (confirmed against its OpenAPI schema,
not its docs page's own stale example command) rather than hand-editing
config.toml: lists paired clients, prompts for a pad type per slot, and
POSTs the update. Needs Wolf's API socket exposed to the host
(WOLF_SOCKET_PATH + a /var/run/wolf bind mount, matching Wolf's own
documented pattern), added to the wolf service's docker-compose.yml.
Full flow verified with a standalone test harness (mocked curl against
real API response shapes, a real AF_UNIX socket file) covering: single-
vs multi-client selection, invalid selection, invalid slot count,
lowercase/unrecognized type input, and the missing-socket path.
Also documents live user feedback that AntiMicroX (added last session)
is confirmed not working for TI-99/4A - flagged in the README rather
than left silently unverified, with this fix noted as the actively
maintained path for the multi-controller problem instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
The PCEE2 libretro core (a separate, actively-developed PCSX2 port,
WizzardSK/pcee2-libretro) is only in ES-DE's own bundled es_systems.xml
as of 3.5.0, which hasn't been released yet - confirmed against ES-DE's
own CHANGELOG.md and by diffing the v3.4.1 tag's real ps2 system block
against git master's (identical except for this one missing <command>
line). ghcr.io/games-on-whales/es-de:edge always installs the latest
*released* AppImage (confirmed against gow's own Dockerfile, which
queries GitLab's releases API), so the container this repo runs
genuinely has no PCEE2 entry at all yet - not a missing core download,
a missing menu entry.
Backfills it via the same esde-custom-systems/es_systems.xml mechanism
already used for the TI-99/4A and Wii U customizations, replicating the
real v3.4.1 ps2 block verbatim plus the one new command (as the default,
matching upstream's own placement). The core file itself needs no extra
handling - confirmed live against the libretro buildbot that
pcee2_libretro.so.zip is already covered by the normal RetroArch cores
pre-download.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
AntiMicroX maps gamepad buttons to synthetic keyboard/mouse events -
useful here because ti99sim-sdl's own joystick handling only ever emits
digit keys 1-9 for a raw button (confirmed against its source), with no
path to 0/Enter/Q/Esc, and because it's a plausible angle on Cemu not
reliably telling apart two Wolf virtual pads that share an SDL GUID.
Scoped to just these two ES-DE systems via a second, separately-labeled
"(AntiMicroX)" <command> alongside each one's existing default - ES-DE's
own multi-command "alternative emulators" mechanism - rather than
touching every system. Each launches through a small wrapper script
that starts AntiMicroX hidden (--no-tray --hidden --eventgen uinput,
flags confirmed against AntiMicroX's own commandlineutility.cpp source)
against a profile the user builds themselves via AntiMicroX's own GUI in
the Desktop/XFCE app, execs the real emulator, and kills AntiMicroX on
exit.
AntiMicroX needs /dev/uinput to inject events under ES-DE's Sway/Wayland
session (its XTest backend needs Xwayland, not present here), so the
esde catalog entry now requests it via both GOW_REQUIRED_DEVICES and a
real device grant - and make_app_block/the reinstall-update path both
learn to read/refresh a per-app 'devices' field instead of always
emitting an empty array, so this actually reaches an existing install on
a rerun, not just a fresh one (verified live against a synthetic
pre-uinput-era config.toml).
Also fixes a latent whitespace-eating bug in every custom_systems
strip-and-reappend regex in this file (trailing \s*\n? reached into the
next sibling block's leading indentation once two custom systems
coexist in the same file) - cosmetic only, but it would have made every
rerun churn indentation between the ti994a and wiiu blocks. Caught and
verified via a standalone test harness exercising all four
strip-and-reappend blocks together across repeated passes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
Joystick buttons beyond the first only reach digit keys 1-9 in
ti99sim-sdl's own source (src/sdl/ti994a-sdl.cpp) - there's no path to
0, Enter, Q, or Esc from a gamepad. Documents the real Esc-to-exit
behavior and full FCTN key row (Alt+1 through Alt+9, Alt+=), both
confirmed against the emulator's own bundled doc/README.html.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
ti99sim-sdl supports --fullscreen directly (confirmed via its own
--help output) — add it to the launch command so TI-99/4A games open
fullscreen the same way every other standalone emulator here does,
instead of a small windowed default.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
The README's "Return to launcher: Ctrl+Alt+Shift+W or START+UP+RB" was
wrong on both counts — confirmed live the controller combo did nothing.
The real, standard Moonlight client shortcuts (confirmed against
Moonlight's own documented shortcuts, not Wolf-specific) are
Ctrl+Alt+Shift+Q on a keyboard and Back/Select+Start+LB+RB pressed
together on a controller. Fixed in all three places this text appeared.
Also documented the actual working procedure for configuring a Cemu
controller and TV audio device, worked out live this session: both need
to be done from the Desktop (XFCE) app rather than ES-DE, since Cemu's
own Settings dialogs don't render correctly in ES-DE's Sway kiosk
session but do in a real XFCE window manager. Desktop shares the same
~/.config/Cemu as ES-DE, so the config carries over. Also notes the
real fix for Cemu's TV-audio-device hang (a full Wolf restart, not just
reconnecting) that worked in testing, and that /ROMs is now mounted in
Desktop too (previous commit) so Cemu can load games directly from
there.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
The 'desktop' catalog entry only ever mounted emulators/ and the
persistent home — /ROMs didn't exist in that container at all.
Confirmed live: with the controller fix requiring a real XFCE session
to reach Cemu's own working dialogs, there was no way to actually load
a ROM once there (Cemu's own File/Load menu had nothing to browse to).
Add the same /ROMs and saves mounts es-de/retroarch already use, so a
standalone emulator run from Desktop can load games directly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
install_wolf()'s own copy of the app-injection step used $APP_KEYS instead
of its actual variable $_APP_KEYS (the underscore-prefixed name is
install_wolf()'s own; APP_KEYS is manage.sh's separate copy's variable).
Confirmed live: under 'set -u' without 'set -e' this was a non-fatal
"APP_KEYS: unbound variable" error printed mid-install right after "Wolf
restarted with updated config" — the script kept going, but the
Steam-storage-location success message never printed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
ghcr.io/games-on-whales/xfce has no libfuse2/libfuse3 at all (checked
against its own Dockerfile) and, unlike es-de's Dockerfile (which sets
this exact env var), no fallback either. Confirmed live: launching Cemu's
AppImage from a terminal inside the Desktop session failed outright with
the standard "AppImages require FUSE to run" error, even though the same
AppImage runs fine when ES-DE launches it — es-de's container already
carries this env var, xfce's doesn't. Adding it makes every AppImage
self-extract into a temp dir and run from there instead of trying to
FUSE-mount itself, matching how ES-DE already handles them.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
update_field() only ever got called for 'mounts' and 'env' when an app was
already present in config.toml, so the games-on-whales/desktop -> xfce
image-name fix from the previous commit would NOT have reached anyone who
already has a (broken) Desktop entry — re-running the installer or
./manage.sh apps would keep refreshing mounts/env but silently leave the
old, 404ing image reference in place forever.
update_field's own array-reformatting logic (scan forward for a closing
']') isn't safe to reuse for a single-line scalar field like image or
icon_png_path — there's no ']' on that line, so the scan would run into
an unrelated array further down the same block (e.g. 'ports = []') and
corrupt it. Added a separate update_scalar_field() that only ever
replaces the exact matched line, and wired it in for both 'image' and
'icon_png_path' in both copies of this app-injector script (install-time
and manage.sh's own 'apps' command) — verified locally against a
synthetic config.toml block that it replaces only the targeted app's own
fields and leaves a neighboring app's identically-named fields untouched.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
ghcr.io/games-on-whales/desktop never existed. Confirmed live: Wolf logged
"[DOCKER] error 404 - No such image: ghcr.io/games-on-whales/desktop:edge"
and silently returned to the Moonlight app list with no other visible
error, making the Desktop tile look like it just didn't launch. The
games-on-whales/gow repo's apps/ directory names this app "xfce", and
ghcr.io/games-on-whales/xfce:edge is the real, currently published image
(confirmed against GHCR's own tag list for that package) — the icon path
uses the same "xfce" naming.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
The 'desktop' Wolf app (ghcr.io/games-on-whales/desktop:edge, full XFCE
session) had mounts=[] — no access to emulators/ or the persistent
.config/.local/share home ES-DE/RetroArch use. That meant there was no way
to run a standalone emulator like Cemu outside ES-DE's single-app Sway
kiosk session, which matters because a second top-level window from an
app (e.g. Cemu's own Settings/Input dialogs) can fail to ever get mapped
or focused there — confirmed live: clicking Cemu's General Settings
highlighted the menu entry but no dialog ever appeared, with no way to
recover control short of killing ES-DE.
Give Desktop the same emulators -> ~/Applications and retro-home(-data)
-> .config/.local/share mounts esde/retroarch already use, so Cemu (or
any other standalone emulator) can be launched directly from a real
multi-window XFCE session instead, using the exact same settings.xml the
next ES-DE-launched session will read.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
ES-DE's findEmulator() decides found-vs-not-found from the <command>
string's emulator token, and every real %INJECT%=...esprefix example in
ES-DE's own shipped es_systems.xml (Dolphin/PrimeHack/Triforce/Supermodel)
pairs it with an %EMULATOR_X%/%CORE_X% placeholder, never a literal path.
The previous ti994a <command> used a literal "/bin/bash -c ..." after
%INJECT%=%BASENAME%.esprefix, which ES-DE reported as "emulator not found"
even though /bin/bash obviously exists on the container.
Fix: give ti99sim-sdl a real custom_systems/es_find_rules.xml entry
(TI99SIM, staticpath ~/Applications/ti99sim-sdl) and reference it via
%EMULATOR_TI99SIM%, matching the pattern every built-in standalone
emulator uses. %STARTDIR%=%EMUDIR% replaces the old shell "cd && ..."
prefix to keep ti99sim-sdl's working directory at its own install dir,
where it looks up the console ROM via a plain relative path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
Confirmed live: the command-line fix from an earlier commit (cd into
emulators/ before launching ti99sim-sdl) never reached the user's actual
es_systems.xml, because the write step's guard was "skip entirely if a
ti994a entry already exists" — which it did, with the old pre-fix
command line still in it. The check only asked whether an entry existed,
never whether its content matched the current template.
Now always strips any existing ti994a <system> block and re-adds the
current one fresh on every run, via a small python3 rewrite (regex block
removal + re-append) instead of a blind append-once guard. Tested against
a stand-in file matching the real stale content, and for idempotency
(second run doesn't duplicate the block), before pushing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
Confirmed live: RetroArch's system_directory on this Wolf setup is
"~/bioses" (GoW's own shipped default, confirmed by reading a real
retroarch.cfg directly — wolf.sh never sets this itself), not RetroArch's
usual default of ~/.config/retroarch/system. The Dolphin Sys-folder
automation added earlier this session assumed the usual default and wrote
to retroarch/system/dolphin-emu/Sys on the host — a path RetroArch's own
config never actually reads, so it would have sat there doing nothing.
Now reads the real configured system_directory value out of
retroarch.cfg instead of assuming, and self-heals by moving a Sys folder
that's already sitting in the old wrong location (from before this fix)
into the correct one. Also corrects the same wrong assumption in the
MAME samples-directory documentation, which pointed at the same
retroarch/system/ default.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
The build succeeded, but the follow-up message pointed at "ti99sim-sdl's
own README" for where the console ROM goes — useless, since that source
tree lived inside the throwaway build container and was already gone by
the time anyone could read it.
Got the real answer from RetroPie's own configure_ti99sim(): it symlinks
the console ROM into the emulator's own install directory, then cd's
there before launching (pushd "$md_inst" && ./ti99sim-sdl "$@") — so
ti99sim-sdl finds it via a plain relative lookup, not any search path.
Caught a real bug this exposed: our own ES-DE <command> line launched
ti99sim-sdl by full path with no cd, so even a correctly-placed file
would never have been found. Fixed to cd into emulators/ first, matching
RetroPie's own pattern exactly.
Also, instead of just printing the required path (emulators/TI-994A.ctg)
and leaving it at that, now prompts for it directly with a tab-completing
path picker (read -e -i, defaulting into the game storage dir) — if you
already have the file somewhere, it copies it into place immediately
instead of making you do it by hand afterward.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
You were right to push back on this earlier — the original ask was SDL2,
same as RetroPie, and I substituted a different thing (SDL1-compat) without
checking what RetroPie actually does first.
Checked RetroPie's own ti99sim.sh scriptmodule directly: it doesn't build
from billzajac/ti99sim (the GitHub fork the last two attempts used) at
all. It fetches upstream v0.16.0 straight from the original author's own
site (mrousseau.org), applies exactly one trivial patch (a missing
#include <cstring> for modern g++), and builds against libsdl2-dev +
libssl-dev directly — no SDL1 compatibility layer, because that source
genuinely supports SDL2 natively. The GitHub fork used previously was
just a stale, pre-SDL2-port copy under a different account.
Verified the download URL actually serves the real tarball (fetched real
binary content, confirmed the 7zXZ header) and the patch's exact content
before using either — same real-source-first standard as the wolf.sh
fixes earlier this session. Tested the sed insertion that replaces
applying the .diff against a stand-in file matching the patch's real
context before trusting it.
Also documents the exact BIOS-equivalent filename RetroPie's own docs
give for this emulator: TI-994A.ctg (case-sensitive) — a real, useful
detail that was missing before.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
Confirmed live: the previous sdl-config shim got past the tooling
problem, but hit a much deeper one — ti99sim's SDL frontend
(src/sdl/main.cpp) is written against the actual SDL 1.2 API and uses
symbols SDL2 genuinely removed (SDL_keysym, SDL_WM_SetCaption,
SDL_EnableUNICODE, SDL_JoystickOpened, the old integer-index
SDL_JoystickName). No shim can paper over missing symbols — this needed
the real thing.
Swapped libsdl2-dev + the hand-rolled sdl-config shim for
libsdl1.2-compat-dev — the SDL project's own official compatibility
package (libsdl-org/sdl12-compat, packaged in Ubuntu's universe repo):
genuine SDL 1.2 headers and its own real sdl-config, implemented on top
of actual SDL2 underneath. The code compiles against the API it was
actually written for, while still running on modern SDL2 at runtime.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
Confirmed live: the containerized ti99sim-sdl build (previous commit)
failed with "SDL.h: No such file or directory" — not a real
compatibility problem, ti99sim's own Makefile.linux calls the old SDL 1.x
sdl-config tool, which doesn't exist in modern libsdl2-dev packaging at
all (only pkg-config .pc files). Every sdl-config invocation was silently
returning nothing, so the compile got no -I/-L flags whatsoever.
Adds a small shim mapping the handful of flags this build actually uses
(--cflags/--libs/--version, tested standalone against a fake pkg-config
before pushing) onto `pkg-config sdl2`, rather than patching the
project's own Makefiles.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
Fixes a cop-out from the previous commit — rather than just warning about
a possible glibc/SDL2 mismatch and handing the user a manual build guide,
build ti99sim-sdl inside a throwaway container running the exact same
image ES-DE itself runs (ghcr.io/games-on-whales/es-de:edge), which
actually eliminates the mismatch risk instead of just noting it. Runs
during install_wolf() when the TI-99/4A prompt is accepted, skips itself
if the binary's already present, and drops the result straight into
emulators/ti99sim-sdl ready to use.
Caught a real bug before pushing, not after: `make` alone leaves the
binary under ti99sim's own src/ directory, not the repo root (confirmed
against its README), so a naive `cp ti99sim-sdl` would have failed
outright. Uses `make install` instead — the project's own reliable way to
collect the binary (copies to /opt/ti99sim/bin, symlinks into
/usr/local/bin) — rather than guessing the exact build subpath.
The one piece that's still genuinely a manual step, and stays that way:
supplying your own TI-99/4A console ROM + GROM dump, real copyrighted
console firmware no installer can legally source for you.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
Cemu: no libretro core exists (a third-party attempt was never merged and
there are no plans to per RetroArch's own issue tracker) — added as a
fourth standalone AppImage download, same pattern as Azahar/PCSX2/Dolphin.
Its official release asset is already named Cemu-<version>-x86_64.AppImage,
matching ES-DE's own find-rule directly, so no Dolphin-style rename/symlink
is needed. Flags a real supply-chain compromise of Cemu's own Linux release
assets around v2.6 (2026-05, since restored, per Datadog Security Labs)
and that most retail games need a user-supplied Wii U common key.
TI-99/4A: added as a genuine ES-DE system (not this repo's existing
js99er service, which is browser-based and can't be launched as an ES-DE
system) via a custom_systems/es_systems.xml, per ES-DE's own documented
mechanism for extending its built-in system list. Needed a new mount
(esde-custom-systems -> ~/ES-DE/custom_systems) since that path wasn't
covered by any existing .config/.local/share mount. The emulator itself
(ti99sim-sdl) is deliberately NOT auto-built — it ships no AppImage, and
compiling it on the host risks a glibc/SDL2 mismatch against the ES-DE
container's own runtime that isn't safe to guess at blind — so this
writes the system definition (cheap, correct, always useful) and gives
manual build+placement instructions instead of a build automation I can't
verify actually runs.
Also expands the BIOS callout to mention PS2 (configured inside PCSX2's
own settings, not auto-detected from bios/) and TI-99/4A (needs a real
console ROM+GROM dump, same legal situation as any other BIOS), and
documents the MAME sample-pack-vs-ROM confusion (identical .zip naming
convention makes them indistinguishable by filename alone; samples belong
in retroarch/system/<mame-core>/samples/, not roms/mame/).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
Confirmed live: RetroArch's file browser and directory-scan importer both
failed to recognize SNES ROMs (.sfc) even though the ROM files and the
snes9x core .so were both genuinely present and correctly mounted — the
core's own .info file (which declares supported_extensions) was never
actually installed, because the "already present, skip" check for the
info.zip pack was "is $CORES_DIR non-empty", and $CORES_DIR is always
non-empty by the time that check runs (the core .so files fill it first,
earlier in the same `cores all` run). So the info-pack fetch silently
no-op'd on every single install, fresh or not — not specific to this
laptop.
Give "info" its own real check: does $CORES_DIR actually contain any
*.info files, not just anything at all.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
Confirmed live on a laptop with only its internal drive (no second/
unmounted disk at all): the game-storage-directory picker's `local -a
_UNMT_DEV _UNMT_LABEL _UNMT_UUID` declares the arrays but, when the lsblk
scan finds zero qualifying unmounted block devices, never actually assigns
an element to any of them. Under setup.sh's `set -u`, that's enough for a
later read (`${#_UNMT_DEV[@]}`) to throw "unbound variable" even though
the arrays were properly `local -a` declared — a known bash nounset quirk
this repo has already hit and documented once before (see
vendor/ai-stack/configure-searxng-safesearch.sh). Explicit `=()`
initializers side-step it. The sibling `_CAND_*` arrays a few lines above
don't need the same fix — they always get at least one element (the home
directory option is unconditional), so they can't hit this path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
Extends `./manage.sh cores all` (and install_wolf()'s own cores prompt,
which calls it) to also pull the rest of what RetroArch's own Online
Updater offers — Slang shaders, overlays/bezels, cheat files, the RDB
game database, and controller autoconfig profiles — directly from the
same libretro buildbot the cores themselves come from
(buildbot.libretro.com/assets/frontend/). Confirmed live against the
real directory listing and each zip's actual internal structure (flat,
no wrapping folder) before writing the extraction paths, rather than
guessing: ~290 MB total, landing in retroarch/{shaders,overlays,cheats,
database,autoconfig}/ using RetroArch's own default paths for the three
config keys (cheat_database_path, content_database_path,
joypad_autoconfig_dir) this install leaves unset.
Thumbnails are deliberately excluded — they're hosted separately, are
per-system, and can run into many GB, so blindly grabbing "all systems"
would be a bad default; documented as a manual per-system pull via
RetroArch's own Thumbnails Updater instead.
Idempotent like the existing cores loop: skips a pack whose destination
directory already has content, unless `force` is passed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
Confirmed live: the Dolphin libretro core needs its own 'Sys' folder
(compatibility DB + IPL data) to boot Wii titles, previously only
obtainable through a manual trip through RetroArch's Online Updater ->
Core System Files Downloader. ./manage.sh cores now fetches it
automatically (a sparse/shallow git checkout of dolphin-emu/dolphin's
Data/Sys) right after the core itself downloads, so a fresh install needs
no GUI step to get GameCube/Wii working.
Also documents the one Dolphin quirk that isn't installer-fixable: a
"This data is corrupted, delete and create a new one?" dialog with a
greyed-out OK button on a specific title (confirmed live with Mario Kart
Wii's own rksys.dat) is that game's own save data getting stuck, not a
setup defect — the fix is deleting that one file, which the README now
walks through directly instead of leaving it as a mystery.
Removes the AntiMicroX controller-combo plumbing entirely: RetroArch's own
universal hotkey binds already cover this for any core-based system
(GameCube/Wii and PS2 both now go through RetroArch's own cores rather
than standalone AppImages by default), and AntiMicroX grabbing the
controller device exclusively caused real problems (a stuck "controller
connected" screen) without enough benefit over RetroArch's native binds to
justify keeping it.
Reframes the GC/Wii/PS2 docs: RetroArch's cores are now the recommended
path (point ES-DE's Alternative Emulators at RetroArch instead of "Dolphin
(Standalone)"), with the standalone AppImages kept as a fallback and as
the only option for 3DS (Azahar has no libretro core at all).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLX1yYKJExGSXmgUhxKQG6
_wolf_download_emulator_appimage picked whatever release asset happened to
be first in the GitHub API's asset list among files ending in .AppImage,
with no architecture filtering. Confirmed live: pkgforge-dev's Dolphin
AppImage release lists an aarch64 build ahead of the x86_64 one, so the
downloaded file silently failed at launch with a bare "exec format error"
and no indication why — the earlier "emulator not found" symlink fix
(previous commit) got ES-DE to find the file at all, which is what
surfaced this as the next failure.
Now prefers whichever release asset's filename actually tags the host's
own architecture, falls back to an untagged asset (Azahar/PCSX2's releases
carry no arch tag and are unaffected either way), and only then falls back
to "take the first one". Also verifies the downloaded file's real ELF
architecture against the host post-download and warns loudly if it still
doesn't match, since filename tagging isn't something every release can be
trusted to get right.
ES-DE's es_find_rules.xml DOLPHIN entry only auto-detects a file literally
named Dolphin_Emulator*.AppImage under ~/Applications. pkgforge-dev's own
community-build release asset isn't named that, so the AppImage downloaded
by install_wolf() sat in emulators/ unnoticed and ES-DE reported "Couldn't
launch game, emulator not found" (%EMULATOR_DOLPHIN% unresolved) even
though the file was right there — confirmed live on a real box.
Drop a same-directory Dolphin_Emulator.AppImage symlink pointing at
whatever the real download is named, without renaming/losing the vendor
filename. Runs unconditionally (not just after a fresh download) so it
also repairs installs that grabbed the file before this fix existed.
Confirmed live: Dolphin's "data is corrupted" / "could not write
to/read from Wii system memory" was ~/.local/share/dolphin-emu (the
emulated Wii NAND) not existing at all inside the container —
ls: cannot access '/home/retro/.local/share/dolphin-emu/':
No such file or directory
Only /home/retro/.config had a persistent mount (retro-home, added
earlier this session for the RetroArch permission-denied fix) —
/home/retro/.local/share, the other half of the XDG base directory
split many apps use (config vs. actual data), was never addressed and
started completely empty on every container launch. Any NAND state
Dolphin wrote mid-session vanished the moment that container was torn
down, which is consistent with "corrupted" rather than merely "empty"
if a session ended mid-write.
Same fix as the earlier .config mount: a new retro-home-data host
directory mounted at /home/retro/.local/share in both the esde and
retroarch app profiles (both CATALOG copies), forced to 1000:1000
ownership like the other retro-home dirs since the container's retro
user is hardcoded to that uid regardless of the host account.
1. Glob case mismatch that would have silently no-op'd the whole
feature: the real GitHub release asset is AntiMicroX-x86_64.AppImage
(capitalized) — confirmed against the actual release, not assumed.
Both the helper's own already-downloaded check and this file's own
post-download lookup used a lowercase-only "antimicrox*.AppImage"
glob (copied from Azahar's/PCSX2's pattern, which really are
lowercase). Linux glob matching is case-sensitive, so _AMX_APP would
have come back empty even after a successful download, hitting the
"didn't produce a usable AppImage" branch and silently skipping the
Sway launch hook with the AppImage sitting there unused.
2. Sway match criteria used app_id, which only matches native Wayland
clients. AntiMicroX is a Qt5/X11 app running under XWayland here —
same as ES-DE and Steam already are in this exact stock config,
which is why their own for_window rules use [class="..."], not
app_id. Switched to class (and title as a fallback, in case the
exact WM_CLASS AntiMicroX registers differs), with a (?i) case-
insensitive prefix — confirmed sway uses PCRE2 for criteria matching
and (?i) is the documented syntax for exactly this, not assumed.
Neither bug was fatal to Wolf itself (both fail quietly rather than
crashing anything), but both would have cost real troubleshooting time
for something that looked like it should just work.
Wires up the infrastructure for RetroArch's own hotkeys (global across
every libretro core already) to be matched by an equivalent for
Dolphin/PCSX2/Azahar, none of which support gamepad-bound hotkeys
natively (confirmed open feature requests: PCSX2/pcsx2#1082,
azahar-emu/azahar#722; Dolphin's are hardcoded to keyboard only).
AntiMicroX watches Wolf's virtual gamepad directly — a real uinput
device per Wolf's own "inputtino" docs, not a proprietary channel —
and injects whatever keyboard shortcut the focused emulator actually
expects.
Mechanism: GoW's own stock Sway config (baked into the esde/retroarch
images) has a deliberate first-line extension point,
`include /home/retro/.config/sway/custom-cfg` (confirmed by reading it
directly out of a running container). Since /home/retro/.config is
already the retro-home mount added earlier this session, writing a
file to $GAME_STORAGE_DIR/retro-home/sway/custom-cfg on the host lands
exactly there in both containers — zero changes to GoW's own image or
scripts needed.
Deliberately scoped to plumbing only: download AntiMicroX (reusing the
_wolf_download_emulator_appimage helper added for PCSX2/Dolphin), add
the Sway launch hook (floating window, not hidden, so it's reachable
through the Moonlight stream), and document the actual one-time setup
flow in the generated README. The button mapping itself, including
AntiMicroX's own per-application Auto Profile switching, is built
through its real GUI rather than a hand-authored .gamecontroller.amgp
this repo can't verify blind.
Marked experimental throughout (install prompt defaults to n, DRY-RUN
summary, README) — this is unverified against a live session.
Factored the existing Azahar-only download block into a reusable
_wolf_download_emulator_appimage() helper (same fetch-latest-release-
and-find-.AppImage-asset logic, parameterized), then reused it for:
- PCSX2 (PS2) — officially publishes Linux AppImages via GitHub
Releases (PCSX2/pcsx2), same pattern as Azahar.
- Dolphin (GameCube/Wii) — dolphin-emu.org's own Linux distribution
is Flatpak-only, no official AppImage at all. Uses the well-regarded
community AppImage build (pkgforge-dev/Dolphin-emu-AppImage)
instead, with explicit warnings before the prompt and in the
generated README that this is a third-party build, not an official
Dolphin release — so the user can decide knowingly rather than this
silently substituting an unofficial build for what looks like an
official option.
Motivated by a live "could not write to/read from Wii system memory"
error — GameCube/Wii isn't handled by a RetroArch libretro core in any
stable way (Dolphin's libretro core is unstable for Wii specifically),
so ES-DE needs a real standalone Dolphin binary to hand those systems
off to, the same way it already does for 3DS via Azahar.
Also updated the DRY-RUN summary and the generated README's emulators
section to describe all three instead of just Azahar.
Only .config/retroarch was mounted, so Docker still had to auto-create
.config itself (one level up) as an intermediate directory for that
bind mount to attach under — same root:root mode-755 stub problem as
before, just one level higher. Confirmed live: RetroArch got past its
own config.toml write this time, but then crashed on:
mkdir: cannot create directory '/home/retro/.config/waybar': Permission denied
(Waybar/Sway — the on-screen overlay GoW's app-runner wrapper spawns
for every app, not RetroArch-specific.)
Fix: add a second mount at /home/retro/.config itself, backed by a new
retro-home/ host directory, alongside the existing more-specific
.config/retroarch mount (Docker resolves nested/overlapping bind mounts
correctly regardless of list order). Now nothing under .config is a
Docker-auto-created stub — it's real host-backed content throughout,
so anything an app writes directly under .config (Waybar, Sway state,
RetroArch's own non-cores/shaders/overlays files) just works.
Also force retro-home/ and the existing retroarch/ dir to 1000:1000
explicitly after the general ACTUAL_USER chown — the GoW app
containers' 'retro' user is hardcoded to uid 1000 regardless of what
uid the host account installing this actually has, so relying on the
general chown alone silently breaks this fix on any box where those
don't match.
update_field() searched a blind +/-25-line window around an app's
name = '...' line to find and rewrite its mounts/env field. Once app
blocks got shorter (e.g. after collapsing a 3-line mounts array into
one line), two adjacent apps' blocks could end up close enough that
the window reached into a neighboring app's block instead — splicing
that block's own field or, worse, eating into its
[profiles.apps.runner] table header.
Confirmed live: this corrupted config.toml into invalid TOML and
crash-looped Wolf outright:
ERROR | Unhandled exception: Error while parsing table header:
cannot redefine existing table 'profiles.apps.runner'
Fix: bound the search to the enclosing [[profiles.apps]] block only —
walk backward from the name line to the nearest [[profiles.apps]]
header, forward to the next [[profiles.apps]] or [[profiles]] header,
and only look for the field within that range. Never crosses into a
neighboring block regardless of how short either one is.
INSTALLED=$(sudo grep "^ name = 'Wolf" ...) required exactly 4 leading
spaces before name = 'Wolf...' in Wolf's own generated config.toml.
Confirmed live: Wolf's TOML writer doesn't reliably indent that way, so
this came back empty even with apps clearly installed and running,
printing "No Wolf apps installed yet". That's silently wrong in two
ways: the "Already installed: ..." message under-reports, and pressing
Enter at the apps prompt ("update mounts only") falls through to the
hardcoded `steam esde` default instead of actually refreshing whatever
was already there — so a mount fix for an already-installed app (e.g.
RetroArch) never gets applied by the Enter/no-picks path at all.
Relaxed the anchor to tolerate any amount of leading whitespace,
matching the Python injector's own already-installed check a few lines
later, which never anchored on indentation to begin with.
The esde and retroarch app profiles (both CATALOG dict copies) mounted
three subdirectories individually:
{games}/retroarch/cores:/home/retro/.config/retroarch/cores:rw
{games}/retroarch/shaders:/home/retro/.config/retroarch/shaders:rw
{games}/retroarch/overlays:/home/retro/.config/retroarch/overlays:rw
but never mounted /home/retro/.config/retroarch itself. Docker
auto-creates that missing parent directory inside the container as
root:root mode 755 (standard behavior for a bind-mount target that
doesn't already exist in the image) — the retro user (uid 1000) can
read/traverse it but not write into it. RetroArch's own entrypoint
then fails outright trying to write its default config there:
cp: cannot create regular file '/home/retro/.config/retroarch/retroarch.cfg': Permission denied
which happens on every single launch, for both apps — confirmed live
against a real box: the container starts, RetroArch dies on that cp
within ~1s, and Wolf tears the session down (the "black screen, back
to app grid" symptom, with nothing RetroArch-specific about it).
Fix: mount the parent {games}/retroarch directory itself onto
/home/retro/.config/retroarch instead of three separate subdirectory
mounts. cores/shaders/overlays already lived as the only subdirectories
under {games}/retroarch/ on the host, so this preserves the exact same
container-side paths — but now the parent is a real bind mount with no
auto-created stub in the way, and RetroArch's other generated config
(button remaps, core options, playlists, cheats, etc.) persists across
sessions too, which the old three-mount setup silently discarded.
Confirmed live: install_frigate()'s fresh-install path overwrote a
working, hand-crafted docker-compose.yml (Frigate + mosquitto +
frigate-notify) with zero backup, because that file's shape didn't match
what frigate.sh's own "existing install" detection knew how to recognize.
Every service's own detection is a judgment call about what counts as
"already installed" and can miss a real setup built outside this repo's
conventions.
lib/common.sh gains backup_if_exists(FILE) — copies FILE to
FILE.bak.<timestamp> if it exists, no-ops otherwise (including DRY_RUN).
Applied before every service's own `cat > docker-compose.yml`/`cat > .env`
write across all 60 services that do one (115 call sites), plus a matching
standalone-mode stub added to every service's own bootstrap block, same
convention already used for port_in_use/find_free_port. This doesn't
replace a service's own update/fresh-reinstall detection — it's the safety
net underneath it, so a wrong detection costs a .bak file to restore from
instead of the original silently disappearing.
Also fixes the actual gap that surfaced this: services/frigate.sh's
Authelia offer only checked for Authelia installed locally on Frigate's
own box, which is never true for a dedicated NVR box with no local Caddy
either (the common shape — Caddy lives elsewhere, snippet-generation mode
already handles that). Now offers Authelia protection unconditionally and,
when Authelia isn't local, asks whether it lives on the same machine as
Caddy (still "import authelia", since that's local to wherever Caddy ends
up) or on a genuinely separate third machine (the explicit
header-pinned forward_auth form, per CLAUDE.md's "forward_auth to a remote
Authelia" note, needed because a bare authelia:9091 shortcut only works
one hop).
Mount retroarch/shaders and retroarch/overlays from the game drive into
~/.config/retroarch/{shaders,overlays} for both ES-DE and standalone
RetroArch (sibling to the existing cores mount). Anything pulled from the
Online Updater or set in the Quick Menu now persists across sessions and
survives a Wolf/OS reinstall, so a CRT shader or bezel set is configured
once. Dirs are pre-created on the game drive; storage summary and README
updated, including a fallback note to repoint Settings -> Directory if
RetroArch ever writes elsewhere.
gaming-backup: add opt-in snapshots for emulator BIOS (bios/) and the
RetroArch shaders/overlays dirs (cores excluded — re-downloadable). New
BACKUP_BIOS and BACKUP_RA_SHADERS flags wired through the prompts,
backup.conf, summary, and the worker script.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XV8mwKLFwUKt94dAKh3k79
The GoW es-de/retroarch images ship the RetroArch frontend but NO libretro
cores, and wolf.sh never mounted a BIOS directory. As a result every
libretro-based game (SNES/NES/Genesis/N64/PSX/GBA/...) failed to launch on a
fresh install, and BIOS-dependent systems had nowhere to read firmware from.
Changes (services/wolf.sh):
- Pre-download RetroArch cores at install into <game>/retroarch/cores and
bind-mount them into the ES-DE and RetroArch apps at
~/.config/retroarch/cores (where both ES-DE's bundled config and GoW's
rom_launcher.sh look). Defaults to the full libretro buildbot set.
- Add a <game>/bios dir mounted at ~/bioses (matches retroarch.cfg's
system_directory) so PSX/Saturn/Dreamcast/Neo-Geo/etc. find their BIOS.
- New './manage.sh cores [all|common] [force]' to download/refresh cores
later; wired into help text and bash tab-completion. Install reuses it.
- Also expose emulators/ at ~/Applications so ES-DE's app finder detects the
Azahar 3DS AppImage.
- Pre-create the new dirs; extend the install summary and README to document
BIOS placement, the cores command, that controllers auto-configure via SDL
(no manual input setup), and how to exit a game back to ES-DE.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XV8mwKLFwUKt94dAKh3k79
With multiple WolfSteam containers, the previous head -1 pick was
arbitrary. Now matches each Steam state directory against running
container IDs so ge-proton, fix-ea-game, and other commands all
target the active session.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
With multiple WolfSteam containers running, _steam_home() picks the first
Steam directory found which may not contain the target game's prefix.
_apply_ea_fix now scans all Wolf Steam homes and selects the one that has
the AppID's Proton prefix, falling back to the original behavior if none
match.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Automates the complete working solution for SWBF2 (AppID 1237950) on Wolf:
1. Extracts EA Desktop from ea_app.msi using msiextract on the host,
bypassing JunoConfigureRegistry Wine incompatibility that causes MSI rollback
2. Copies EA Desktop files (Link2EA.exe, EALocalHostSvc.exe, etc.) into the
Wine prefix at the versioned path with symlink
3. Writes link2ea_fix.reg and ea_services.reg into drive_c so they persist
across Wine prefix operations
4. Installs a launch wrapper (/home/retro/ea_install.sh) that runs regedit
via Steam's sniper+GE-Proton launch chain — required because direct edits
to system.reg are overwritten when wineserver flushes on shutdown
5. Sets LaunchOptions for AppID 1237950 in localconfig.vdf and locks the
config directory (chmod 555) to prevent Steam overwriting it via
atomic rename on shutdown
Run after: Steam open in Moonlight, GE-Proton set, SWBF2 launched once
(triggers Wine prefix + ea_app.msi creation), EA account linked at ea.com.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
wine cmd.exe /c "timeout /t 900 > nul" failed silently (docker exec -d
hides errors), leaving no anchor process and letting wineserver die as
soon as EAappInstaller.exe exited.
Replace with wineserver -f (foreground mode) which keeps the server
alive unconditionally until explicitly killed with wineserver -k.
This is the correct primitive for holding a Wine session open.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs