wolf: add optional ES-DE/RetroArch AppImage downloads for Steam Input

Both projects ship official standalone Linux AppImages separate from
the esde/retroarch Wolf catalog containers this repo already runs.
Adding either one to Steam as a non-Steam game (steam-add-nonsteam-game,
now reaching the same roms/saves/bios/retro-home/retroarch mounts as
the esde/retroarch containers) lets Steam Input assign a 4th controller
its own identity by device path, past Wolf's 3-concrete-pad-type ceiling.

ES-DE is hosted on GitLab rather than GitHub, so this adds a GitLab
Releases API counterpart to the existing GitHub-based download helper.
RetroArch's own buildbot doesn't publish through either API, so this
uses hizzlekizzle/RetroArch-AppImage, the community nightly-build
project the AppImage catalogs themselves point to (flagged as
third-party, same treatment this file already gives the Dolphin
community build). Both downloads are opt-in (default no) and symlink
to a fixed filename so steam-add-nonsteam-game's case-sensitive
substring match finds them regardless of the vendor's own asset name.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Z4nqULUEipWNgBsPoSuAb
This commit is contained in:
Claude
2026-09-04 21:04:00 +00:00
parent ae939c4085
commit 07394769ca
+157
View File
@@ -285,6 +285,84 @@ print(pick[0]["browser_download_url"] if pick else "")
esac
}
# Same job as _wolf_download_emulator_appimage above, but against GitLab's
# Releases API instead of GitHub's — needed for any project (ES-DE included)
# that's hosted on GitLab rather than GitHub, since GitHub's API obviously
# can't answer for a repo it doesn't host. Mirrors the same arch-matching /
# post-download ELF-header verification logic so both call sites behave
# identically from the caller's point of view.
_wolf_download_emulator_appimage_gitlab() {
local _display_name="$1" _project_path="$2" _existing_glob="$3" _dir="$4"
if ls "$_dir"/$_existing_glob 2>/dev/null | grep -q .; then
log_info "$_display_name already present in $_dir/"
return 0
fi
local _get=""
echo ""
log_info "$_display_name can be auto-downloaded."
prompt_yn "Download $_display_name AppImage now? (y/n):" "y" _get
[[ "$_get" =~ ^[Yy]$ ]] || return 0
log_info "Fetching latest $_display_name release from GitLab..."
local _encoded_path
_encoded_path=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=''))" "$_project_path")
local _url
_url=$(curl -fsSL "https://gitlab.com/api/v4/projects/${_encoded_path}/releases" \
| HOST_ARCH="$(uname -m)" python3 -c '
import sys, json, os
host = os.environ.get("HOST_ARCH", "")
arch_tags = {
"x86_64": ["x86_64", "amd64", "x64"],
"aarch64": ["aarch64", "arm64"],
"arm64": ["aarch64", "arm64"],
}.get(host, [host] if host else [])
all_arch_tags = ["x86_64", "amd64", "x64", "aarch64", "arm64", "armv7", "armhf", "i386", "i686"]
def has(name, tags):
n = name.lower()
return any(t in n for t in tags)
releases = json.load(sys.stdin)
# GitLab does not guarantee list order — sort explicitly instead of
# assuming index 0 is the newest (the mistake that would silently pick a
# stale/older release on some future API response ordering change).
releases = sorted(releases, key=lambda r: r.get("released_at") or "", reverse=True)
assets = []
for r in releases:
for link in r.get("assets", {}).get("links", []):
url = link.get("direct_asset_url") or link.get("url") or ""
if url.endswith(".AppImage"):
assets.append({"name": link.get("name", url), "browser_download_url": url})
if assets:
break
matching = [a for a in assets if arch_tags and has(a["name"], arch_tags)]
untagged = [a for a in assets if not has(a["name"], all_arch_tags)]
pick = matching or untagged or assets
print(pick[0]["browser_download_url"] if pick else "")
' 2>/dev/null)
if [[ -z "$_url" ]]; then
log_warning "Could not resolve download URL — get it manually from https://gitlab.com/${_project_path}/-/releases"
return 1
fi
local _file="$_dir/$(basename "$_url")"
curl -fL --progress-bar -o "$_file" "$_url" \
&& chmod +x "$_file" \
&& chown "$ACTUAL_USER:$ACTUAL_USER" "$_file" \
&& log_success "$_display_name downloaded: $_file" \
|| { log_warning "Download failed — get it manually from https://gitlab.com/${_project_path}/-/releases"; return 1; }
local _got_arch
_got_arch=$(file -b "$_file" 2>/dev/null)
case "$(uname -m)" in
x86_64)
echo "$_got_arch" | grep -qi 'x86-64\|x86_64' || \
log_warning "$_file doesn't look like an x86_64 build ($_got_arch) — it will fail with 'exec format error'. Grab the x86_64 asset by hand from https://gitlab.com/${_project_path}/-/releases"
;;
aarch64|arm64)
echo "$_got_arch" | grep -qi 'aarch64\|arm64' || \
log_warning "$_file doesn't look like an aarch64 build ($_got_arch) — it may fail to run. Grab the aarch64 asset by hand from https://gitlab.com/${_project_path}/-/releases"
;;
esac
}
install_wolf() {
require_docker || return 1
@@ -939,6 +1017,85 @@ UDEV
log_info "not something this installer can supply. Cemu's own First-Time Setup Wizard covers where"
log_info "to put it once you have one."
# ── Optional: ES-DE and RetroArch as standalone AppImages (for Steam) ────
# These are ADDITIONAL to the esde/retroarch Wolf catalog containers
# above, not a replacement — nothing here removes or changes those. The
# only reason to want this: once added as a Steam non-Steam game
# (./manage.sh steam-add-nonsteam-game, which the wolf mount fix above
# already extended to reach the same roms/saves/bios/retro-home/
# retroarch paths the esde/retroarch containers use), Steam Input can
# give each of up to 4 identical-model controllers its own distinct
# identity by device path — the one thing Wolf's own 3-concrete-pad-type
# ceiling can't do for a 4th controller (see manage.sh's own "4
# controllers (Cemu / Wii U games)" help text). Skip both prompts below
# if you're happy running Wii U/retro systems through the esde app
# directly and don't need Steam's per-device controller assignment.
#
# Both download to a FIXED, predictable filename (ES-DE.AppImage /
# RetroArch.AppImage) regardless of the real upstream release asset's
# own name — steam-add-nonsteam-game matches by substring against the
# actual filename on disk, and bash glob matching is case-sensitive, so
# a fixed name (same symlink trick already used for Dolphin above) is
# what makes './manage.sh steam-add-nonsteam-game es-de' reliably find
# it regardless of how the vendor's own release happens to be named.
if [ ! -f "$_EMU_DIR/ES-DE.AppImage" ]; then
echo ""
# ES-DE is hosted on GitLab, not GitHub (confirmed against its own
# project page) — a different Releases API than every other
# standalone emulator above, hence the separate _gitlab helper.
log_info "ES-DE also ships an official standalone Linux AppImage — separate from the esde Wolf"
log_info "app above. Only useful for adding to Steam (see this repo's wolf README); skip this if"
log_info "you'll only ever use the esde app directly."
local _GET_ESDE_APPIMAGE=""
prompt_yn "Download the ES-DE AppImage for use via Steam? (y/n):" "n" _GET_ESDE_APPIMAGE
if [[ "$_GET_ESDE_APPIMAGE" =~ ^[Yy]$ ]]; then
_wolf_download_emulator_appimage_gitlab \
"ES-DE" "es-de/emulationstation-de" "ES-DE.AppImage" "$_EMU_DIR"
local _ESDE_REAL
_ESDE_REAL=$(ls "$_EMU_DIR"/*.AppImage 2>/dev/null \
| grep -iE '/(es-?de|emulationstation)[^/]*\.AppImage$' \
| grep -v '/ES-DE\.AppImage$' | head -1)
if [[ -n "$_ESDE_REAL" ]]; then
ln -sf "$(basename "$_ESDE_REAL")" "$_EMU_DIR/ES-DE.AppImage"
chown -h "$ACTUAL_USER:$ACTUAL_USER" "$_EMU_DIR/ES-DE.AppImage" 2>/dev/null || true
log_success "Linked $_EMU_DIR/ES-DE.AppImage -> $(basename "$_ESDE_REAL")"
log_info "Once you've signed into Steam once in Moonlight, run:"
log_info " cd $WOLF_DIR && ./manage.sh steam-add-nonsteam-game es-de \"EmulationStation (ES-DE)\""
fi
fi
fi
if [ ! -f "$_EMU_DIR/RetroArch.AppImage" ]; then
echo ""
# Libretro's own buildbot doesn't publish through a GitHub/GitLab
# Releases API this installer can automate against — hizzlekizzle/
# RetroArch-AppImage is the well-regarded THIRD-PARTY nightly-build
# project the AppImage community catalogs (appimage.github.io etc.)
# themselves point to, same "flagged, not silently offered as
# official" treatment as the Dolphin community build above.
log_info "RetroArch also has a standalone Linux AppImage, separate from the retroarch Wolf app"
log_info "above — same rationale as ES-DE just above (Steam Input's per-device controller"
log_info "assignment). This comes from hizzlekizzle/RetroArch-AppImage, a well-regarded but"
log_info "THIRD-PARTY nightly-build project, not an official libretro.org release — grab"
log_info "RetroArch's own build by hand instead if you'd rather not run that."
local _GET_RA_APPIMAGE=""
prompt_yn "Download the RetroArch AppImage for use via Steam? (y/n):" "n" _GET_RA_APPIMAGE
if [[ "$_GET_RA_APPIMAGE" =~ ^[Yy]$ ]]; then
_wolf_download_emulator_appimage \
"RetroArch" "hizzlekizzle/RetroArch-AppImage" "RetroArch.AppImage" "$_EMU_DIR"
local _RA_REAL
_RA_REAL=$(ls "$_EMU_DIR"/*[Rr]etro[Aa]rch*.AppImage 2>/dev/null \
| grep -v '/RetroArch\.AppImage$' | head -1)
if [[ -n "$_RA_REAL" ]]; then
ln -sf "$(basename "$_RA_REAL")" "$_EMU_DIR/RetroArch.AppImage"
chown -h "$ACTUAL_USER:$ACTUAL_USER" "$_EMU_DIR/RetroArch.AppImage" 2>/dev/null || true
log_success "Linked $_EMU_DIR/RetroArch.AppImage -> $(basename "$_RA_REAL")"
log_info "Once you've signed into Steam once in Moonlight, run:"
log_info " cd $WOLF_DIR && ./manage.sh steam-add-nonsteam-game retroarch \"RetroArch\""
fi
fi
fi
# ── Optional: TI-99/4A as its own ES-DE system ────────────────────────────
# TI-99/4A has no libretro core and isn't one of ES-DE's built-in systems,
# so getting it real ES-DE treatment (artwork scraping, gameplay-time