fix: say what minui and file-less cores get

--list-platforms said the MinUI layout was not written yet while
--from-pack and --dest wrote it. On the device the card root is now
found and its version.txt names the file list, and a card without one
gets the --platform and --dest to use. A core with no file of its own
says its files are the platform's, always kept, instead of 0 files.
This commit is contained in:
Abdessamad Derraz committed 2026-10-23 14:08:28 +02:00
1 parent e041fac351
commit ebfa0cbb31
6 files changed
+106 -21

No files matched your search

+1 -1
View File
@@ -11,7 +11,7 @@
$ErrorActionPreference = "Stop"
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
$defaultInstallSha256 = "50afca134ab44a140421e3da43ab3a74fbbb35ba7c524adf688b8af5909dcc38"
$defaultInstallSha256 = "b5070145ae7920b90e8d904552a223261fae35a394552c7ce3a2b87cbcf613e8"
$maximumInstallerBytes = 2MB
$installerExit = 0
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
+43 -15
View File
@@ -1254,8 +1254,9 @@ _EMBEDDED_RULES: tuple[_OsRule, ...] = (
_OsRule("", "TrimUI", "file_exists", ("/usr/trimui",), None),
# MinUI, MyMinUI and NextUI keep their system in .system at the card root
# (MinUI dbf89435, workspace/all/common/defines.h:14-16) and give each core
# Bios/<tag>/ (workspace/all/minarch/minarch.c:2942), a layout the
# installer does not write.
# Bios/<tag>/ (workspace/all/minarch/minarch.c:2942). Their file lists
# write that layout below the card root, and the card's version.txt
# says which of the three it is (card_platform).
_OsRule("", "MinUI", "file_exists", ("/mnt/SDCARD/.system/res",), None, base="TrimUI"),
_OsRule("", "MinUI", "file_exists", ("/mnt/sdcard/.system/res",), None, base="TrimUI"),
_OsRule("retroarch", "Anbernic stock firmware", "file_exists",
@@ -1265,9 +1266,13 @@ _EMBEDDED_RULES: tuple[_OsRule, ...] = (
# Why a system found here gets no folder: said once per run.
_NO_FOLDER = {
"TrimUI": "its firmware publishes no source naming the folder its RetroArch reads",
"MinUI": "MinUI, MyMinUI and NextUI give each core its own Bios/<tag>/ folder, "
"a layout the installer does not write yet",
"MinUI": f"its card names no release in {CARD_MARKER}, which tells MinUI, NextUI "
"and MyMinUI apart. Name the card's root and the one it runs: "
"--platform minui (or nextui, myminui) --dest /mnt/SDCARD",
}
# A system found by its card, with the end of the path that proves it: the
# card root is what comes before.
_CARD_SYSTEMS = {"MinUI": "/.system/res"}
# (platform, BIOS folder) -> the name the system found there gives itself.
_DETECTED_OS: dict[tuple[str, Path], str] = {}
@@ -1337,15 +1342,23 @@ def _detect_embedded() -> list[tuple[str, Path]]:
if name in bases:
continue
rules = [rule for rule in matched if rule.name == name]
folder = _os_system_dir(rules) if rules[0].manifest else None
plat = rules[0].manifest
folder = _os_system_dir(rules) if plat else None
if name in _CARD_SYSTEMS:
marker = next(value for rule in rules for value in rule.values
if value.endswith(_CARD_SYSTEMS[name]))
root = _on_device(marker[:-len(_CARD_SYSTEMS[name])])
plat = card_platform(root) or ""
if plat:
folder, name = root, display_name(plat)
if folder is None:
reason = _NO_FOLDER.get(
name, "nothing on it names the folder its RetroArch reads")
print(f"{name} found here, but {reason}. Name the folder with --dest.",
file=sys.stderr)
closing = "" if name in _CARD_SYSTEMS else " Name the folder with --dest."
print(f"{name} found here, but {reason}.{closing}", file=sys.stderr)
continue
_DETECTED_OS[(rules[0].manifest, folder)] = name
found.append((rules[0].manifest, folder))
_DETECTED_OS[(plat, folder)] = name
found.append((plat, folder))
return found
@@ -1365,7 +1378,10 @@ def _system_lines() -> list[str]:
lines = []
for name in dict.fromkeys(rule.name for rule in _EMBEDDED_RULES):
plat = next(rule.manifest for rule in _EMBEDDED_RULES if rule.name == name)
if plat:
if name in _CARD_SYSTEMS:
lines.append(f"{name}: minui, nextui or myminui, as the card's {CARD_MARKER} "
"names it (from a computer: --dest <card root>)")
elif plat:
lines.append(f"{name}: {plat}")
else:
lines.append(f"{name}: not installed to, {_NO_FOLDER[name]} (use --dest)")
@@ -5611,15 +5627,26 @@ def _sized(files: list[dict]) -> str:
return f"{_plural(len(files), 'file')}, {format_size(sum(f.get('size', 0) for f in files))}"
def _pick(title: str, rows: list[tuple[str, list[dict]]], ordered: bool = False) -> list[str]:
"""Numbered list of names. An empty answer keeps every one."""
# A core whose files are all the platform's own. "pcsx_rearmed 0 files,
# 0 B" read as a core that gets nothing.
NO_CORE_FILES = "no extra file: it reads the platform's own, always kept"
def _pick(
title: str, rows: list[tuple[str, list[dict]]], ordered: bool = False, empty: str = "",
) -> list[str]:
"""Numbered list of names. An empty answer keeps every one.
empty is said instead of "0 files, 0 B" for a name that holds none.
"""
if not rows:
return []
print(f"\n{title}")
width = len(str(len(rows)))
name_width = min(max(len(name) for name, _files in rows), 32)
for index, (name, files) in enumerate(rows, 1):
print(f" {index:>{width}}) {name:<{name_width}} {_sized(files)}")
print(f" {index:>{width}}) {name:<{name_width}} "
f"{_sized(files) if files or not empty else empty}")
hint = "order of preference, e.g. 2,1" if ordered else "e.g. 1,3-5"
while True:
text = _ask(f"Numbers ({hint}), Enter for all, q to quit: ")
@@ -5689,7 +5716,7 @@ def _prompt_custom_selection(
(name, _filter_by_target(
[f for f in kept if f.get("cores") is not None], [name]))
for name in core_names
])
], empty=NO_CORE_FILES)
kept, kept_omitted = narrow(kept, kept_omitted, [], cores, [])
regions: list[str] = []
@@ -5750,7 +5777,8 @@ def _print_choices(
if systems
else _filter_by_target([f for f in files if f.get("cores") is not None], [name])
)
print(f" {labels[name]:<{width}} {_sized(held)}")
shown = NO_CORE_FILES if not systems and not held else _sized(held)
print(f" {labels[name]:<{width}} {shown}")
def emulator_names(manifest: dict, files: list[dict], omitted: list[dict]) -> dict[str, str]:
+1 -1
View File
@@ -18,7 +18,7 @@ esac
TEMP_INSTALLER=""
TEMP_DIRECTORY=""
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
DEFAULT_INSTALL_SHA256="50afca134ab44a140421e3da43ab3a74fbbb35ba7c524adf688b8af5909dcc38"
DEFAULT_INSTALL_SHA256="b5070145ae7920b90e8d904552a223261fae35a394552c7ce3a2b87cbcf613e8"
MAX_INSTALLER_BYTES=2097152
cleanup() {
+20
View File
@@ -4727,8 +4727,28 @@ class HandheldSystemsByTheirOwnFiles(unittest.TestCase):
{"/usr/trimui/": "", "/mnt/SDCARD/.system/res/": ""})
self.assertEqual(found, [])
self.assertIn("MinUI found here", err)
self.assertIn("--platform minui", err)
self.assertIn("--dest", err)
self.assertNotIn("TrimUI", err)
def test_a_minui_card_on_its_device_gets_its_file_list(self):
"""--list-platforms said the MinUI layout was not written yet, while
--from-pack and --dest wrote it: on the device the card root is the
folder, and the card's version.txt names which of the three it is."""
for release, plat, shown in (("MinUI-20251127-0", "minui", "MinUI"),
("NextUI-20250601-1", "nextui", "NextUI"),
("MyMinUI-20260809b-0", "myminui", "MyMinUI")):
with self.subTest(release=release):
root, found, name = self._one({
"/usr/trimui/": "", "/mnt/SDCARD/.system/res/": "",
"/mnt/SDCARD/.system/version.txt": release + "\n"})
self.assertEqual((found, name), ((plat, root / "mnt/SDCARD"), shown))
def test_the_listing_says_what_the_minui_family_gets(self):
line = next(line for line in install._system_lines() if line.startswith("MinUI:"))
self.assertNotIn("does not write", line)
self.assertIn("minui, nextui or myminui", line)
def test_two_unrelated_systems_are_refused(self):
_root, found, names, _err = self._found(
{"/mnt/SDCARD/Koriki/version.txt": "1\n"}, 'OS_NAME="JELOS"\n')
+37
View File
@@ -9,6 +9,7 @@ to one answer.
from __future__ import annotations
import contextlib
import functools
import hashlib
import http.server
@@ -353,6 +354,42 @@ class CommandLineChoices(unittest.TestCase):
install.parse_selection(bad, 5)
class ACoreWithNoFileOfItsOwn(unittest.TestCase):
"""pcsx_rearmed, swanstation and pcsx1 showed `0 files, 0 B` in the
cores step and in --list-cores, which reads as "this core gets nothing":
their files are the platform's own, which are always kept."""
FILES = [
{"dest": "scph5501.bin", "size": 5, "cores": None, "systems": ["sony-playstation"],
"declared_choices": [], "slot_choices": [{"cores": ["pcsx_rearmed"]}]},
{"dest": "ps2.bin", "size": 9, "cores": ["duckstation"],
"systems": ["sony-playstation"]},
]
def _line(self, out: str, core: str) -> str:
return next(line for line in out.splitlines() if line.strip().startswith(core)
or f") {core}" in line)
def test_the_listing_says_its_files_are_the_platforms(self):
out = io.StringIO()
with contextlib.redirect_stdout(out):
install._print_choices("retroarch", self.FILES, [], systems=False)
line = self._line(out.getvalue(), "pcsx_rearmed")
self.assertNotIn("0 files", line)
self.assertIn("always kept", line)
self.assertIn("1 file, 9 B", self._line(out.getvalue(), "duckstation"))
def test_the_cores_step_says_so_too(self):
answers = iter(["c", "", ""])
out = io.StringIO()
with mock.patch("builtins.input", lambda _prompt="": next(answers)), \
contextlib.redirect_stdout(out):
install._prompt_custom_selection("retroarch", self.FILES, [])
line = self._line(out.getvalue(), "pcsx_rearmed")
self.assertNotIn("0 files", line)
self.assertIn("always kept", line)
class TheBoundaryChecksTheNewFields(unittest.TestCase):
def _manifest(self, **extra):
entry = {"dest": "a.bin", "sha1": "a" * 40, "size": 1,
+4 -4
View File
@@ -581,7 +581,7 @@ rewrites both to `/roms2/bios`: a second card is followed through them.
| `/mnt/plumos-user` | plumOS | RetroArch | `/mnt/plumos-user/bios`, which its launcher appends to every RetroArch run |
| `/etc/os-release` `ID=plumos` without the folder above | plumOS | none | its builds choose a folder per launch: named, not installed to |
| `/usr/trimui` | TrimUI | none | no published source names the stock firmware's folder: named, not installed to |
| `/mnt/SDCARD/.system/res` or `/mnt/sdcard/.system/res` | MinUI | none | MinUI, MyMinUI and NextUI give each core its own `Bios/<tag>/`, a layout not written yet: named, not installed to |
| `/mnt/SDCARD/.system/res` or `/mnt/sdcard/.system/res` | MinUI, NextUI or MyMinUI, as the first line of the card's `.system/version.txt` names it | MinUI, NextUI or MyMinUI | the card root, below which the list writes `Bios/<tag>/`. Without `version.txt` the three cannot be told apart: named, not installed to, and the message gives `--platform` and `--dest` to use |
| `/mnt/vendor/bin/dmenu.bin` | Anbernic stock firmware | RetroArch | `/mnt/mmc/bios` |
AmberELEC, EmuELEC and KNULLI install the BIOS list they publish themselves. TheRA is not detected: no
@@ -692,9 +692,9 @@ for EmuDeck (`C:\Emulation\bios` on Windows), `/storage/roms/bios` for ROCKNIX,
directory for RetroArch. BizHawk has no such folder: it reads `Firmware`
beside its executable wherever the archive was extracted, so a forced
`--platform bizhawk` that finds no install stops and asks for `--dest`. MinUI,
NextUI and MyMinUI keep their files on the SD card, which the installer reads
from a computer it is mounted on: a forced `--platform minui` stops and asks
for the card's root as `--dest`, and a `--dest` that is a folder inside such a
NextUI and MyMinUI keep their files on the SD card. Run on the device, the
installer finds the card root. From a computer the card is mounted on, a
forced `--platform minui` stops and asks for the card's root as `--dest`, and a `--dest` that is a folder inside such a
card (its `Bios/`) is refused with the root to name instead. On Android a forced `--platform retroarch` falls back to
`<shared storage>/RetroArch/system` instead, a home directory there belonging
to Termux rather than to the frontend. Pass `--dest` to say where instead of