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:
@@ -285,6 +285,84 @@ print(pick[0]["browser_download_url"] if pick else "")
|
|||||||
esac
|
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() {
|
install_wolf() {
|
||||||
require_docker || return 1
|
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 "not something this installer can supply. Cemu's own First-Time Setup Wizard covers where"
|
||||||
log_info "to put it once you have one."
|
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 ────────────────────────────
|
# ── 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,
|
# 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
|
# so getting it real ES-DE treatment (artwork scraping, gameplay-time
|
||||||
|
|||||||
Reference in New Issue
Block a user