feat: find the RetroArch AppImage portable home

This commit is contained in:
Abdessamad Derraz committed 2026-11-06 14:51:44 +01:00
1 parent 2571acc820
commit 92e374328f
6 files changed
+103 -10

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@
# and Invoke-WebRequest throws on a cut transfer before the hash is read:
# the site's build rewrites this line there (scripts/sitebootstrap.py).
$mismatchAdvice = "The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that."
$defaultInstallSha256 = "450cee7625f098d5796452b02ab9c8bffdeaaef89180d2254bbe07ce354bc359"
$defaultInstallSha256 = "5bce9f0f81e4f1c7302ca78fa5242f26acc06523f8718378ee97042f2421ff78"
$maximumInstallerBytes = 2MB
$installerExit = 0
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
+45
View File
@@ -1401,6 +1401,49 @@ def _macos_retroarch_base() -> Path:
return Path.home() / "Documents" / "RetroArch"
def _retroarch_appimage_dirs(home: Path) -> list[Path]:
"""The system folder of each RetroArch AppImage kept with a portable
folder, up to two folders below home.
The buildbot's Linux build unpacks RetroArch-Linux-x86_64.AppImage beside
RetroArch-Linux-x86_64.AppImage.home/.config/retroarch. The AppImage
runtime sets HOME to <AppImage>.home and XDG_CONFIG_HOME to
<AppImage>.config when those folders exist (type2-runtime fedfad51,
src/runtime/runtime.c:1449-1462), so this RetroArch never reads
~/.config/retroarch. The archive holds no retroarch.cfg: until the first
run writes one, the system folder is the base folder's (frontend_unix_get_env
in platform_unix.c).
"""
images: list[Path] = []
for depth in ("", "*/", "*/*/"):
for suffix in (".home", ".config"):
try:
hits = sorted(home.glob(f"{depth}*.AppImage{suffix}"))
except OSError:
continue
for hit in hits:
image = hit.with_name(hit.name[:-len(suffix)])
if image not in images and _is_file(image):
images.append(image)
dirs: list[Path] = []
for image in images:
portable_home = image.with_name(image.name + ".home")
portable_config = image.with_name(image.name + ".config")
if _is_dir(portable_config):
base = portable_config / "retroarch"
elif _is_dir(portable_home) and "XDG_CONFIG_HOME" not in os.environ:
base = portable_home / ".config" / "retroarch"
else:
continue
tilde = portable_home if _is_dir(portable_home) else home
system = _parse_retroarch_system_dir(base / "retroarch.cfg", base, tilde)
if system is None and _is_dir(base):
system = base / "system"
if system is not None and system not in dirs:
dirs.append(system)
return dirs
def _unix_retroarch_base() -> Path:
"""RetroArch's base folder on Linux: $XDG_CONFIG_HOME/retroarch when the
variable is set, else ~/.config/retroarch (fill_pathname_application_data
@@ -2839,6 +2882,8 @@ def detect_platforms(os_type: str) -> list[tuple[str, Path]]:
found.append(("retroarch", ra_dir))
break
found.extend(("retroarch", ra_dir) for ra_dir in _retroarch_appimage_dirs(home))
if os_type == "darwin":
# open_default_config_file reads <Application Support>/RetroArch/config
# (configuration.c). A value left at default is the documents folder,
+1 -1
View File
@@ -23,7 +23,7 @@ DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/ins
# curl or wget stop on a cut transfer before the hash is read: the site's
# build rewrites this line there (scripts/sitebootstrap.py).
MISMATCH_ADVICE="The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that."
DEFAULT_INSTALL_SHA256="450cee7625f098d5796452b02ab9c8bffdeaaef89180d2254bbe07ce354bc359"
DEFAULT_INSTALL_SHA256="5bce9f0f81e4f1c7302ca78fa5242f26acc06523f8718378ee97042f2421ff78"
MAX_INSTALLER_BYTES=2097152
cleanup() {
+8 -8
View File
@@ -27,8 +27,8 @@
{
"dest": "roms/ports/retrobios/install.py",
"repo_path": "install.py",
"size": 1115328,
"sha256": "450cee7625f098d5796452b02ab9c8bffdeaaef89180d2254bbe07ce354bc359",
"size": 1117350,
"sha256": "5bce9f0f81e4f1c7302ca78fa5242f26acc06523f8718378ee97042f2421ff78",
"executable": false
},
{
@@ -71,8 +71,8 @@
"muos": {
"archive": {
"dest": "ARCHIVE/RetroBIOS.muxapp",
"size": 1168955,
"sha256": "1f52731fda2beb3ff9e7aaf7f3d9333853e66b99240b24e4f6b0026368755cb4"
"size": 1170977,
"sha256": "cfb61e90dafef4fceadd8750412fe91a98b5d8cbf9574c819c09bddb76486e78"
},
"members": [
{
@@ -92,8 +92,8 @@
{
"name": "RetroBIOS/install.py",
"repo_path": "install.py",
"size": 1115328,
"sha256": "450cee7625f098d5796452b02ab9c8bffdeaaef89180d2254bbe07ce354bc359",
"size": 1117350,
"sha256": "5bce9f0f81e4f1c7302ca78fa5242f26acc06523f8718378ee97042f2421ff78",
"executable": false
},
{
@@ -166,8 +166,8 @@
{
"dest": ".config/modules/retrobios/install.py",
"repo_path": "install.py",
"size": 1115328,
"sha256": "450cee7625f098d5796452b02ab9c8bffdeaaef89180d2254bbe07ce354bc359",
"size": 1117350,
"sha256": "5bce9f0f81e4f1c7302ca78fa5242f26acc06523f8718378ee97042f2421ff78",
"executable": false
},
{
+47
View File
@@ -4542,6 +4542,53 @@ class WindowsSeenFromWsl(unittest.TestCase):
str(self.root))
class RetroArchAppImageIsPortable(unittest.TestCase):
"""The buildbot's Linux RetroArch is an AppImage beside a .home folder
the AppImage runtime makes its HOME: the run found nothing and offered
~/.config/retroarch/system, a folder this RetroArch never reads."""
def _detect(self, home: Path, env: dict | None = None) -> list:
cleared = {"HOME": str(home), "XDG_CONFIG_HOME": ""}
with unittest.mock.patch.dict(os.environ, {**cleared, **(env or {})}), \
unittest.mock.patch.object(Path, "home", return_value=home):
os.environ.pop("XDG_CONFIG_HOME")
return install.detect_platforms("linux")
def _unpack(self, home: Path) -> Path:
folder = home / "Downloads" / "RetroArch-Linux-x86_64"
base = folder / "RetroArch-Linux-x86_64.AppImage.home" / ".config" / "retroarch"
(base / "system").mkdir(parents=True)
(folder / "RetroArch-Linux-x86_64.AppImage").write_bytes(b"\x7fELF")
return base
def test_the_unpacked_archive_is_found(self):
home = Path(_tempdir(self))
base = self._unpack(home)
self.assertIn(("retroarch", base / "system"), self._detect(home))
def test_its_config_is_read_with_its_own_home(self):
home = Path(_tempdir(self))
base = self._unpack(home)
(base / "retroarch.cfg").write_text('system_directory = "~/bios"\n')
portable = base.parent.parent
self.assertIn(("retroarch", portable / "bios"), self._detect(home))
def test_a_config_folder_beside_it_wins(self):
home = Path(_tempdir(self))
base = self._unpack(home)
config = base.parent.parent.with_name("RetroArch-Linux-x86_64.AppImage.config")
(config / "retroarch").mkdir(parents=True)
found = self._detect(home)
self.assertIn(("retroarch", config / "retroarch" / "system"), found)
self.assertNotIn(("retroarch", base / "system"), found)
def test_a_folder_without_its_appimage_is_left(self):
home = Path(_tempdir(self))
base = self._unpack(home)
(base.parent.parent.with_name("RetroArch-Linux-x86_64.AppImage")).unlink()
self.assertNotIn(("retroarch", base / "system"), self._detect(home))
class SteamRetroArchOnMacIsPortable(unittest.TestCase):
"""platform_darwin.m makes the Steam build portable (HAVE_STEAM) with no
portable.txt: a default system_directory is Application Support's, and
+1
View File
@@ -1177,6 +1177,7 @@ usual folders of a setup that has one on each side: RetroArch's in this Linux
| `~/.var/app/org.libretro.RetroArch/config/retroarch/retroarch.cfg` | RetroArch (Flatpak) | `system_directory` |
| `~/snap/retroarch/current/.config/retroarch/retroarch.cfg` | RetroArch (Snap) | `system_directory` |
| `~/.config/retroarch/retroarch.cfg` | RetroArch (native) | `system_directory` |
| `<name>.AppImage.home/.config/retroarch/retroarch.cfg` (or `<name>.AppImage.config/retroarch/`) beside an AppImage in `~`, or one or two folders below it | RetroArch (AppImage from the buildbot, whose portable folder becomes its home) | `system_directory`. Without a config, `system` in that `retroarch` folder |
| `~/Library/Application Support/RetroArch/config/retroarch.cfg` | RetroArch (macOS) | `system_directory`. At default, `~/Documents/RetroArch/system` (portable, Steam build included: `Application Support/RetroArch/system`) |
| `retroarch.cfg` beside `retroarch.exe` (`C:\RetroArch-Win64`, `C:\RetroArch`, `Program Files\RetroArch`) | RetroArch (Windows) | `system_directory`. At default or without a config, `system` beside the exe |
| `%APPDATA%\RetroArch\retroarch.cfg` | RetroArch (Windows) | `system_directory`, read when the exe folder has no config |