mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-10 21:43:23 -05:00
feat: detect android in the installer
This commit is contained in:
1 parent
96920785ad
commit
f864613f11
5 files changed
+207
-9
No files matched your search
+1
-1
@@ -8,7 +8,7 @@ param(
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
||||
$defaultInstallSha256 = "7dbb5c48a834d31a470b1952a9b8659b4c1f49f8d37c709a4032a56abe495f27"
|
||||
$defaultInstallSha256 = "b5749a3aea8679657a6d48f04ef18879f2fe04cc55d1c1a6510fd6b5d74068e1"
|
||||
$maximumInstallerBytes = 2MB
|
||||
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
|
||||
$temporary = $null
|
||||
|
||||
+79
-5
@@ -104,19 +104,39 @@ DEFAULT_DESTS = {
|
||||
"retropie": Path.home() / "RetroPie" / "BIOS",
|
||||
}
|
||||
|
||||
# The three RetroArch packages published for Android, newest ABI first.
|
||||
ANDROID_RETROARCH_PACKAGES = (
|
||||
"com.retroarch.aarch64",
|
||||
"com.retroarch",
|
||||
"com.retroarch.ra32",
|
||||
)
|
||||
|
||||
|
||||
def _is_android() -> bool:
|
||||
"""Android reports itself as Linux, so the runtime is named instead.
|
||||
|
||||
Termux inherits the two variables the Android runtime exports, and
|
||||
/system/build.prop covers a shell that was started without them.
|
||||
"""
|
||||
if os.environ.get("ANDROID_ROOT") and os.environ.get("ANDROID_DATA"):
|
||||
return True
|
||||
return Path("/system/build.prop").exists()
|
||||
|
||||
|
||||
def detect_os() -> str:
|
||||
"""Return normalized OS identifier.
|
||||
|
||||
RETROBIOS_OS names the platform outright (linux, wsl, windows, darwin),
|
||||
for a run that has to behave like another host: the PowerShell wrapper
|
||||
tests drive a Windows layout on a Linux runner.
|
||||
RETROBIOS_OS names the platform outright (linux, wsl, windows, darwin,
|
||||
android), for a run that has to behave like another host: the PowerShell
|
||||
wrapper tests drive a Windows layout on a Linux runner.
|
||||
"""
|
||||
forced = os.environ.get("RETROBIOS_OS", "").strip().lower()
|
||||
if forced in ("linux", "wsl", "windows", "darwin"):
|
||||
if forced in ("linux", "wsl", "windows", "darwin", "android"):
|
||||
return forced
|
||||
system = platform.system().lower()
|
||||
if system == "linux":
|
||||
if _is_android():
|
||||
return "android"
|
||||
proc_version = Path("/proc/version")
|
||||
if proc_version.exists():
|
||||
try:
|
||||
@@ -326,6 +346,40 @@ def _detect_embedded() -> list[tuple[str, Path]]:
|
||||
return found
|
||||
|
||||
|
||||
def _android_storage_root() -> Path:
|
||||
"""Shared storage root, the parent RetroArch builds its defaults from."""
|
||||
value = os.environ.get("EXTERNAL_STORAGE", "").strip()
|
||||
return Path(value) if value else Path("/storage/emulated/0")
|
||||
|
||||
|
||||
def _detect_android() -> list[tuple[str, Path]]:
|
||||
"""Find the RetroArch system directory on Android.
|
||||
|
||||
The config is read from the app external files directory first, then from
|
||||
the internal one (platform_unix.c:1327-1395). A system_directory left at
|
||||
default resolves against <shared storage>/RetroArch, which is also where
|
||||
the directory sits when no config has been written yet
|
||||
(platform_unix.c:2836-2851).
|
||||
"""
|
||||
found: list[tuple[str, Path]] = []
|
||||
storage = _android_storage_root()
|
||||
default_root = storage / "RetroArch"
|
||||
|
||||
for package in ANDROID_RETROARCH_PACKAGES:
|
||||
for cfg in (
|
||||
storage / "Android" / "data" / package / "files" / "retroarch.cfg",
|
||||
Path("/data/data") / package / "files" / "retroarch.cfg",
|
||||
):
|
||||
system_dir = _parse_retroarch_system_dir(cfg, default_root)
|
||||
if system_dir:
|
||||
found.append(("retroarch", system_dir))
|
||||
return found
|
||||
|
||||
if default_root.exists():
|
||||
found.append(("retroarch", default_root / "system"))
|
||||
return found
|
||||
|
||||
|
||||
def _lnk_target(lnk_path: Path, exe_name: str) -> Path | None:
|
||||
"""Read the target path of a Windows shortcut.
|
||||
|
||||
@@ -530,6 +584,9 @@ def detect_platforms(os_type: str) -> list[tuple[str, Path]]:
|
||||
"""Detect installed emulator platforms and their BIOS directories."""
|
||||
found: list[tuple[str, Path]] = []
|
||||
|
||||
if os_type == "android":
|
||||
found.extend(_detect_android())
|
||||
|
||||
if os_type in ("linux", "wsl"):
|
||||
found.extend(_detect_embedded())
|
||||
|
||||
@@ -1227,6 +1284,17 @@ def _prompt_manual_platform(os_type: str) -> list[tuple[str, Path]]:
|
||||
return [(plat, dest)]
|
||||
|
||||
|
||||
def _default_dest(os_type: str, platform_name: str) -> Path:
|
||||
"""Fall back path for a platform named on the command line but not found.
|
||||
|
||||
A home directory is meaningless for RetroArch on Android, where Termux
|
||||
owns the home and the frontend reads from shared storage.
|
||||
"""
|
||||
if os_type == "android" and platform_name == "retroarch":
|
||||
return _android_storage_root() / "RetroArch" / "system"
|
||||
return DEFAULT_DESTS.get(platform_name, Path.home() / "bios")
|
||||
|
||||
|
||||
def _manual_usage_hint(os_type: str) -> list[str]:
|
||||
"""How to name the platform and directory when nothing is detected."""
|
||||
if os_type == "windows":
|
||||
@@ -1236,6 +1304,12 @@ def _manual_usage_hint(os_type: str) -> list[str]:
|
||||
"https://raw.githubusercontent.com/Abdess/retrobios/main/install.ps1))) "
|
||||
"--platform retroarch --dest 'C:\\RetroArch\\system'",
|
||||
]
|
||||
if os_type == "android":
|
||||
return [
|
||||
" Use --platform and --dest to specify manually, for example:",
|
||||
" python3 install.py --platform retroarch "
|
||||
"--dest /storage/emulated/0/RetroArch/system",
|
||||
]
|
||||
return [
|
||||
" Use --platform and --dest to specify manually, for example:",
|
||||
" python3 install.py --platform retroarch --dest ~/.config/retroarch/system",
|
||||
@@ -1352,7 +1426,7 @@ def main() -> None:
|
||||
if matched:
|
||||
platforms = matched
|
||||
else:
|
||||
default_dest = DEFAULT_DESTS.get(args.platform, Path.home() / "bios")
|
||||
default_dest = _default_dest(os_type, args.platform)
|
||||
print(
|
||||
f" Platform '{args.platform}' not detected, "
|
||||
f"using default path: {default_dest}"
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ esac
|
||||
TEMP_INSTALLER=""
|
||||
TEMP_DIRECTORY=""
|
||||
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
||||
DEFAULT_INSTALL_SHA256="7dbb5c48a834d31a470b1952a9b8659b4c1f49f8d37c709a4032a56abe495f27"
|
||||
DEFAULT_INSTALL_SHA256="b5749a3aea8679657a6d48f04ef18879f2fe04cc55d1c1a6510fd6b5d74068e1"
|
||||
MAX_INSTALLER_BYTES=2097152
|
||||
|
||||
cleanup() {
|
||||
|
||||
@@ -214,6 +214,109 @@ class TestEmbeddedDetection(unittest.TestCase):
|
||||
self.assertEqual(self._detect_with(set()), [])
|
||||
|
||||
|
||||
class TestAndroidDetection(unittest.TestCase):
|
||||
"""Android reports itself as Linux, and RetroArch is the frontend there."""
|
||||
|
||||
def setUp(self):
|
||||
self.saved = {
|
||||
key: os.environ.get(key)
|
||||
for key in ("ANDROID_ROOT", "ANDROID_DATA", "EXTERNAL_STORAGE", "RETROBIOS_OS")
|
||||
}
|
||||
for key in self.saved:
|
||||
os.environ.pop(key, None)
|
||||
|
||||
def tearDown(self):
|
||||
for key, value in self.saved.items():
|
||||
if value is None:
|
||||
os.environ.pop(key, None)
|
||||
else:
|
||||
os.environ[key] = value
|
||||
|
||||
def test_runtime_variables_name_android(self):
|
||||
os.environ["ANDROID_ROOT"] = "/system"
|
||||
os.environ["ANDROID_DATA"] = "/data"
|
||||
self.assertTrue(install._is_android())
|
||||
|
||||
def test_plain_linux_is_not_android(self):
|
||||
self.assertFalse(
|
||||
install._is_android() and not Path("/system/build.prop").exists()
|
||||
)
|
||||
|
||||
def test_forced_os_accepts_android(self):
|
||||
os.environ["RETROBIOS_OS"] = "android"
|
||||
self.assertEqual(install.detect_os(), "android")
|
||||
|
||||
def test_default_directory_found_without_a_config(self):
|
||||
storage = Path(tempfile.mkdtemp())
|
||||
(storage / "RetroArch" / "system").mkdir(parents=True)
|
||||
os.environ["EXTERNAL_STORAGE"] = str(storage)
|
||||
self.assertEqual(
|
||||
install._detect_android(),
|
||||
[("retroarch", storage / "RetroArch" / "system")],
|
||||
)
|
||||
shutil.rmtree(storage)
|
||||
|
||||
def test_config_default_resolves_against_the_retroarch_root(self):
|
||||
storage = Path(tempfile.mkdtemp())
|
||||
files = storage / "Android" / "data" / "com.retroarch.aarch64" / "files"
|
||||
files.mkdir(parents=True)
|
||||
(files / "retroarch.cfg").write_text('system_directory = "default"\n')
|
||||
os.environ["EXTERNAL_STORAGE"] = str(storage)
|
||||
self.assertEqual(
|
||||
install._detect_android(),
|
||||
[("retroarch", storage / "RetroArch" / "system")],
|
||||
)
|
||||
shutil.rmtree(storage)
|
||||
|
||||
def test_custom_system_directory_wins(self):
|
||||
storage = Path(tempfile.mkdtemp())
|
||||
files = storage / "Android" / "data" / "com.retroarch" / "files"
|
||||
files.mkdir(parents=True)
|
||||
custom = storage / "Emulation" / "bios"
|
||||
(files / "retroarch.cfg").write_text(f'system_directory = "{custom}"\n')
|
||||
os.environ["EXTERNAL_STORAGE"] = str(storage)
|
||||
self.assertEqual(install._detect_android(), [("retroarch", custom)])
|
||||
shutil.rmtree(storage)
|
||||
|
||||
def test_nothing_found_on_a_bare_device(self):
|
||||
storage = Path(tempfile.mkdtemp())
|
||||
os.environ["EXTERNAL_STORAGE"] = str(storage)
|
||||
self.assertEqual(install._detect_android(), [])
|
||||
shutil.rmtree(storage)
|
||||
|
||||
def test_detect_platforms_routes_android(self):
|
||||
storage = Path(tempfile.mkdtemp())
|
||||
(storage / "RetroArch" / "system").mkdir(parents=True)
|
||||
os.environ["EXTERNAL_STORAGE"] = str(storage)
|
||||
self.assertEqual(
|
||||
install.detect_platforms("android"),
|
||||
[("retroarch", storage / "RetroArch" / "system")],
|
||||
)
|
||||
shutil.rmtree(storage)
|
||||
|
||||
def test_manual_hint_names_the_android_path(self):
|
||||
hint = "\n".join(install._manual_usage_hint("android"))
|
||||
self.assertIn("/storage/emulated/0/RetroArch/system", hint)
|
||||
|
||||
def test_named_platform_falls_back_to_shared_storage(self):
|
||||
storage = Path(tempfile.mkdtemp())
|
||||
os.environ["EXTERNAL_STORAGE"] = str(storage)
|
||||
self.assertEqual(
|
||||
install._default_dest("android", "retroarch"),
|
||||
storage / "RetroArch" / "system",
|
||||
)
|
||||
self.assertEqual(
|
||||
install._default_dest("linux", "batocera"), Path("/userdata/bios")
|
||||
)
|
||||
shutil.rmtree(storage)
|
||||
|
||||
def test_packages_match_the_published_flavors(self):
|
||||
self.assertEqual(
|
||||
install.ANDROID_RETROARCH_PACKAGES,
|
||||
("com.retroarch.aarch64", "com.retroarch", "com.retroarch.ra32"),
|
||||
)
|
||||
|
||||
|
||||
class TestLaunchboxDetection(unittest.TestCase):
|
||||
"""LaunchBox references its emulators in Data/Emulators.xml."""
|
||||
|
||||
|
||||
+23
-2
@@ -217,8 +217,29 @@ redirect the write somewhere else again.
|
||||
|
||||
## Android
|
||||
|
||||
There is no Android detection. Termux reports itself as Linux, so nothing is
|
||||
found and the platform has to be named along with where it writes:
|
||||
Android reports itself as Linux, so the runtime is named from its own
|
||||
variables instead: `ANDROID_ROOT` and `ANDROID_DATA`, which Termux inherits,
|
||||
with `/system/build.prop` covering a shell started without them.
|
||||
|
||||
Detection then looks for RetroArch, the only Android frontend keeping a shared
|
||||
BIOS directory. `retroarch.cfg` is read from the app external files directory
|
||||
and then from the internal one, the order the frontend itself follows
|
||||
(`platform_unix.c:1327-1395`), and a `system_directory` left at `default`
|
||||
resolves against `<shared storage>/RetroArch`, where the directory also sits
|
||||
when no config has been written yet (`platform_unix.c:2836-2851`).
|
||||
|
||||
Under scoped storage those two config paths are unreadable from Termux on
|
||||
Android 11 and later, so on current devices the shared directory is what
|
||||
answers. Running `termux-setup-storage` once is what makes it reachable:
|
||||
|
||||
```bash
|
||||
pkg install python
|
||||
termux-setup-storage
|
||||
curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh
|
||||
```
|
||||
|
||||
`EXTERNAL_STORAGE` overrides the storage root for a device that mounts it
|
||||
elsewhere. A custom BIOS directory is still named directly:
|
||||
|
||||
```bash
|
||||
python install.py --platform retroarch --dest /storage/emulated/0/RetroArch/system
|
||||
|
||||
Reference in new issue
Block a user