diff --git a/services/wolf.sh b/services/wolf.sh index e602f14..fcf03a2 100644 --- a/services/wolf.sh +++ b/services/wolf.sh @@ -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