mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-01 00:27:20 -05:00
chore: merge the remaining minor fixes
This commit is contained in:
commit
8f5b715c75
32 files changed
+1001
-218
No files matched your search
+1
-1
@@ -11,7 +11,7 @@
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
||||
$defaultInstallSha256 = "cec348d9aa6c9f940a2c9dc9bc6414a923acea8137e4a3624d4710c9d88c38ad"
|
||||
$defaultInstallSha256 = "c63a84da36e10657838dc359f4258db75e098d4902ee27312296ccd22a6994f4"
|
||||
$maximumInstallerBytes = 2MB
|
||||
$installerExit = 0
|
||||
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
|
||||
|
||||
+288
-67
@@ -521,7 +521,8 @@ EVENT_KEYS: dict[str, tuple[str, ...]] = {
|
||||
# stage: manifest (reason: network, not_found, invalid, clock for a
|
||||
# refused certificate under a clock set in the past, or no_ca for no
|
||||
# trusted certificates on the machine) or detect (reason: conflict, two
|
||||
# systems found on one machine) or from (a folder named with --from,
|
||||
# systems found on one machine, or not_found, the platform named with
|
||||
# --platform found nowhere and no folder to assume) or from (a folder named with --from,
|
||||
# reason: not_found, not_a_folder, unreadable, or system for the root
|
||||
# of the system and /proc, /sys, /dev).
|
||||
"error": ("stage", "reason", "platform", "message"),
|
||||
@@ -1975,7 +1976,8 @@ def _note_refused(path: Path, exc: OSError) -> None:
|
||||
retroarch.cfg was readable, and the run fell back to a folder it does
|
||||
not read.
|
||||
"""
|
||||
if not getattr(_SETUP_SEARCH, "active", False) or not isinstance(exc, PermissionError):
|
||||
if (not getattr(_SETUP_SEARCH, "active", False) or getattr(_SETUP_SEARCH, "quiet", False)
|
||||
or not isinstance(exc, PermissionError)):
|
||||
return
|
||||
refused = closed_folder(path) or (path, exc.strerror or str(exc))
|
||||
if refused in _DETECTION_REFUSED:
|
||||
@@ -3451,6 +3453,9 @@ def _fetch_json(
|
||||
_network_allowed(label)
|
||||
timeout = LIST_TIMEOUT if timeout is None else timeout
|
||||
for attempt in range(1, attempts + 1):
|
||||
# The tries this failure gets: "(2/3)" then "after 2 attempts" for
|
||||
# a server gone silent, asked once more only.
|
||||
tries = attempts
|
||||
try:
|
||||
with urllib.request.urlopen(url, timeout=timeout) as resp:
|
||||
return _read_limited_json(resp, limit, label)
|
||||
@@ -3470,9 +3475,11 @@ def _fetch_json(
|
||||
exc.attempts = attempt # type: ignore[union-attr]
|
||||
raise
|
||||
delay = _retry_delay(attempt, None)
|
||||
if _stalled(exc):
|
||||
tries = min(attempts, STALLED_ATTEMPTS)
|
||||
# Said, or three 30-second attempts read as a frozen program.
|
||||
print(f" The {label} did not arrive ({problem}), trying again "
|
||||
f"({attempt + 1}/{attempts}) in {delay:.0f} s.", file=sys.stderr)
|
||||
f"({attempt + 1}/{tries}) in {delay:.0f} s.", file=sys.stderr)
|
||||
time.sleep(delay)
|
||||
raise AssertionError("unreachable")
|
||||
|
||||
@@ -4088,8 +4095,15 @@ def _retroarch_config_for(bios_path: Path) -> tuple[Path, Path] | None:
|
||||
if found is not None or _CONFIGS_READ:
|
||||
return found
|
||||
_CONFIGS_READ.append(True)
|
||||
with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):
|
||||
detect_platforms(_INVOCATION.os_type or detect_os())
|
||||
# Silent, the unsearched event too: this run searched for no setup, and
|
||||
# the event came after detect, the line it stands for never shown.
|
||||
_SETUP_SEARCH.quiet = True
|
||||
try:
|
||||
with contextlib.redirect_stdout(io.StringIO()), \
|
||||
contextlib.redirect_stderr(io.StringIO()):
|
||||
detect_platforms(_INVOCATION.os_type or detect_os())
|
||||
finally:
|
||||
_SETUP_SEARCH.quiet = False
|
||||
return _retroarch_config_at(bios_path)
|
||||
|
||||
|
||||
@@ -4311,7 +4325,17 @@ def closed_folder(path: Path) -> tuple[Path, str] | None:
|
||||
except OSError as exc:
|
||||
if exc.errno in (errno.ENOENT, errno.ENOTDIR):
|
||||
return None
|
||||
return folder.parent, exc.strerror or str(exc)
|
||||
why = exc.strerror or str(exc)
|
||||
if exc.errno == errno.ELOOP:
|
||||
# The link that loops, not the open folder holding it.
|
||||
return folder, why
|
||||
real = Path(os.path.realpath(folder))
|
||||
if real != folder:
|
||||
# ~/Emulation linked into a closed card: the closed folder
|
||||
# is under the link's target, and naming the link's
|
||||
# parent told the user they cannot open their own HOME.
|
||||
return closed_folder(real) or (real.parent, why)
|
||||
return folder.parent, why
|
||||
return probe, failure.strerror or str(failure)
|
||||
|
||||
|
||||
@@ -5590,7 +5614,13 @@ def _print_plan(
|
||||
print(line)
|
||||
else:
|
||||
largest = sorted(by_system, key=lambda s: (-_bytes(by_system[s]), s))
|
||||
print(f" {_plural(len(largest), 'system')}, the largest first:")
|
||||
# A file several systems share is a group of its own: "88 systems"
|
||||
# were 66 systems, as the full screen counts them, and 22 groups.
|
||||
systems = {name for entry in to_download for name in entry_systems(entry)}
|
||||
groups = (f"{_plural(len(systems), 'system')} in {len(largest)} groups"
|
||||
if systems and len(systems) != len(largest)
|
||||
else _plural(len(largest), 'system'))
|
||||
print(f" {groups}, the largest first:")
|
||||
for system in largest[:PLAN_LARGEST_SYSTEMS]:
|
||||
print(f" {term.bold(system)} {_sized(by_system[system])}")
|
||||
if len(largest) > PLAN_LARGEST_SYSTEMS:
|
||||
@@ -5998,6 +6028,11 @@ def _install_standalone_folders(
|
||||
False when this machine has none, the run then going on to the platform
|
||||
question or hint.
|
||||
"""
|
||||
if offline_requested():
|
||||
# Their folders are named by file lists this run cannot fetch: the
|
||||
# first refusal ended an --offline run that asked nothing of EmuDeck
|
||||
# on "No file list for emudeck".
|
||||
return False
|
||||
manifests = []
|
||||
for plat in STANDALONE_SOURCES:
|
||||
manifests.append(fetch_manifest(plat))
|
||||
@@ -6243,6 +6278,8 @@ def _prompt_manual_platform(os_type: str) -> list[tuple[str, Path]]:
|
||||
while not raw:
|
||||
raw = _ask(
|
||||
f"BIOS directory [{default_dest}]: " if default_dest
|
||||
else "BIOS directory (no default, a folder above could not be opened): "
|
||||
if _default_set_aside(os_type, plat)
|
||||
else "BIOS directory (no default on this system): "
|
||||
)
|
||||
if default_dest is not None:
|
||||
@@ -6284,14 +6321,7 @@ def _default_dest(os_type: str, platform_name: str) -> Path | None:
|
||||
path on Windows resolved to the root of the current drive: 8.7 GB under
|
||||
C:\\userdata\\bios, reported done.
|
||||
"""
|
||||
if os_type == "android" and platform_name == "retroarch":
|
||||
default = _android_storage_root() / "RetroArch" / "system"
|
||||
elif platform_name in PORTABLE_ONLY or (os_type == "windows" and platform_name
|
||||
not in _os_default_dests(os_type)):
|
||||
return None
|
||||
else:
|
||||
default = (_os_default_dests(os_type).get(platform_name)
|
||||
or (None if platform_name == "retrobat" else DEFAULT_DESTS.get(platform_name)))
|
||||
default = _built_in_dest(os_type, platform_name)
|
||||
if default is not None and _refused_near(default):
|
||||
# The Flatpak's own folder at 000 hid the retroarch.cfg naming
|
||||
# ~/Games/bios, and 6.6 GB were offered to a folder it does not read.
|
||||
@@ -6299,6 +6329,24 @@ def _default_dest(os_type: str, platform_name: str) -> Path | None:
|
||||
return default
|
||||
|
||||
|
||||
def _built_in_dest(os_type: str, platform_name: str) -> Path | None:
|
||||
"""The platform's usual folder on this OS, whatever detection met."""
|
||||
if os_type == "android" and platform_name == "retroarch":
|
||||
return _android_storage_root() / "RetroArch" / "system"
|
||||
if platform_name in PORTABLE_ONLY or (os_type == "windows" and platform_name
|
||||
not in _os_default_dests(os_type)):
|
||||
return None
|
||||
return (_os_default_dests(os_type).get(platform_name)
|
||||
or (None if platform_name == "retrobat" else DEFAULT_DESTS.get(platform_name)))
|
||||
|
||||
|
||||
def _default_set_aside(os_type: str, platform_name: str) -> bool:
|
||||
"""Whether the platform's usual folder was set aside because detection
|
||||
met a closed folder, as against having none on this OS."""
|
||||
return (_default_dest(os_type, platform_name) is None
|
||||
and _built_in_dest(os_type, platform_name) is not None)
|
||||
|
||||
|
||||
def _refused_near(default: Path) -> bool:
|
||||
"""Whether detection met a closed folder under HOME, where configs that
|
||||
could name another folder live, or above the default itself."""
|
||||
@@ -6450,7 +6498,7 @@ INSTALLED_ONLY = "installed"
|
||||
def _prompt_custom_selection(
|
||||
plat: str, files: list[dict], omitted: list[dict],
|
||||
installed: tuple[list[dict], list[dict]] | None = None,
|
||||
name: str = "",
|
||||
name: str = "", dry_run: bool = False,
|
||||
) -> tuple[list[str], list[str], list[str]] | str | None:
|
||||
"""Offer to narrow the install by system, core and region.
|
||||
|
||||
@@ -6460,7 +6508,9 @@ def _prompt_custom_selection(
|
||||
everything on its axis.
|
||||
"""
|
||||
print(f"\n{name or display_name(plat)}: {_sized(files)}.")
|
||||
print(" Enter) install everything")
|
||||
# A dry run installs nothing: "install everything" was asked before it.
|
||||
print(" Enter) list everything, nothing is written" if dry_run
|
||||
else " Enter) install everything")
|
||||
if installed is not None:
|
||||
print(f" i) only what the installed cores read: {_sized(installed[0])}")
|
||||
print(" c) choose systems, cores and regions")
|
||||
@@ -9283,8 +9333,9 @@ def _open_from_folders(folders: list[Path]) -> None:
|
||||
other_drives=[str(drive) for drive in index.other_drives])
|
||||
|
||||
|
||||
def _refuse_closed(dest: Path, named: str) -> None:
|
||||
"""End the run, with its name, on a folder above dest this user cannot open."""
|
||||
def _refuse_closed(dest: Path, named: str, then: str = "") -> None:
|
||||
"""End the run, with its name, on a folder above dest this user cannot
|
||||
open. then is a line said after the error, which it continues."""
|
||||
closed = closed_folder(dest)
|
||||
if closed is None:
|
||||
return
|
||||
@@ -9293,6 +9344,8 @@ def _refuse_closed(dest: Path, named: str) -> None:
|
||||
print(f"Error: {screen_text(message)}. Give this user access to it, run as "
|
||||
f"its owner, or choose another folder than the one {named}.",
|
||||
file=sys.stderr)
|
||||
if then:
|
||||
print(f" {then}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@@ -9628,6 +9681,9 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
|
||||
elif args.platform in CARD_PLATFORMS:
|
||||
# Its files go on its SD card, which is never this machine's
|
||||
# own folder: the card's mount point is the user's to name.
|
||||
EVENTS.emit("error", stage="detect", reason="not_found", platform=args.platform,
|
||||
message=f"{display_name(args.platform)} keeps its BIOS files on "
|
||||
"its SD card, and no card was named with --dest")
|
||||
print(
|
||||
f" {display_name(args.platform)} keeps its BIOS files on its SD card. "
|
||||
f"Mount the card and name its root with --dest, for example: "
|
||||
@@ -9639,6 +9695,9 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
|
||||
elif args.platform in PORTABLE_ONLY:
|
||||
# A portable emulator reads the folder beside its executable:
|
||||
# any default would be a folder it never opens.
|
||||
EVENTS.emit("error", stage="detect", reason="not_found", platform=args.platform,
|
||||
message=f"Platform '{args.platform}' not found. "
|
||||
f"{PORTABLE_ONLY[args.platform]}")
|
||||
print(
|
||||
f" Platform '{args.platform}' not found. {PORTABLE_ONLY[args.platform]} "
|
||||
f"Name it with --dest, for example: --platform {args.platform} "
|
||||
@@ -9650,11 +9709,14 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
|
||||
default_dest = _default_dest(os_type, args.platform)
|
||||
if default_dest is None:
|
||||
where = ("where this user can look, and its default folder may not be "
|
||||
"the one it reads" if _DETECTION_REFUSED
|
||||
"the one it reads" if _default_set_aside(os_type, args.platform)
|
||||
else "and no folder on this system is one it reads")
|
||||
message = f"Platform '{args.platform}' not found {where}"
|
||||
EVENTS.emit("error", stage="detect", reason="not_found",
|
||||
platform=args.platform, message=message)
|
||||
print(
|
||||
f" Platform '{args.platform}' not found {where}. Name it with "
|
||||
f"--dest, for example: --platform {args.platform} --dest <its BIOS folder>",
|
||||
f" {message}. Name it with --dest, for example: "
|
||||
f"--platform {args.platform} --dest <its BIOS folder>",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
@@ -9737,13 +9799,13 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
|
||||
# A system_directory under /root read from retroarch.cfg, run without
|
||||
# sudo: the case probe raised PermissionError before any line.
|
||||
for name, path in platforms:
|
||||
if closed_folder(path) is not None and len(platforms) > 1:
|
||||
print(" Nothing was installed. Name one platform with --platform "
|
||||
"to install it alone.", file=sys.stderr)
|
||||
_refuse_closed(path, (
|
||||
f"taken as {display_name(name)}'s default, {display_name(name)} "
|
||||
"not being found here" if path in assumed_dests
|
||||
else "detected here") + " (name another with --dest)")
|
||||
found_in = _retroarch_config_at(path)
|
||||
named = (f"used by default, since {display_name(name)} was not found"
|
||||
if path in assumed_dests
|
||||
else f"{found_in[0]} names" if found_in else "detected here")
|
||||
_refuse_closed(path, f"{named} (name another with --dest)",
|
||||
"Nothing was installed. Name one platform with --platform "
|
||||
"to install it alone." if len(platforms) > 1 else "")
|
||||
|
||||
total_downloaded = 0
|
||||
total_up_to_date = 0
|
||||
@@ -9968,7 +10030,7 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
|
||||
)
|
||||
selection = _prompt_custom_selection(
|
||||
plat_name, files, omitted_files, installed,
|
||||
display_name(plat_name, manifest),
|
||||
display_name(plat_name, manifest), args.dry_run,
|
||||
)
|
||||
if selection is None:
|
||||
smaller = (" Nothing downloaded. To install less, run the same command "
|
||||
@@ -10673,6 +10735,11 @@ class TuiState:
|
||||
# A --dest given without --platform: the list asks whose files go there.
|
||||
self.dest_given: Path | None = None
|
||||
self.folder_default: Path | None = None
|
||||
# What folder_default is, for the words above it: "usual" (the
|
||||
# platform's built-in folder), "found" (the one detected here),
|
||||
# "chosen" (the run's folder, from Change folder) or "closed" (the
|
||||
# one chosen could not be opened, none is offered).
|
||||
self.folder_kind: str = "usual"
|
||||
self.path_text: str = ""
|
||||
self.browse_dir: Path | None = None
|
||||
self.browse_rows: list = []
|
||||
@@ -11190,12 +11257,13 @@ def _system_rows(state: TuiState) -> list[TuiRow]:
|
||||
right = f"{_plural(need, 'file')}, {format_size(row['bytes_needed'])}"
|
||||
narrow = (format_size(row["bytes_needed"]),)
|
||||
elif not own:
|
||||
# Two spellings of shared, never cut: "+ 28 sh." and ", shared"
|
||||
# sat beside "+ 1689 shared" on one 40-column screen.
|
||||
right = f"{_plural(need, 'file')}, {format_size(size)}, all shared"
|
||||
narrow = (f"{format_size(size)}, shared", format_size(size))
|
||||
narrow = (f"{format_size(size)}, all shared", format_size(size))
|
||||
else:
|
||||
right = f"{_plural(own, 'file')}, {format_size(own_size)} + {count} shared"
|
||||
narrow = (f"{format_size(own_size)} + {count} shared",
|
||||
f"{format_size(own_size)} + {count} sh.", format_size(own_size))
|
||||
narrow = (f"{format_size(own_size)} + {count} shared", format_size(own_size))
|
||||
rows.append(TuiRow("system", f"{box} {label}", right, row["system"],
|
||||
"" if need else "dim", narrow))
|
||||
return rows
|
||||
@@ -11248,6 +11316,10 @@ def _option_rows(state: TuiState) -> list[TuiRow]:
|
||||
chosen = f"{len(state.cores)} chosen" if state.cores else "all"
|
||||
rows.append(TuiRow("cores", f"Cores: {chosen}", "--core"))
|
||||
copied = ", ".join(str(folder) for folder in state.from_folders) or "none"
|
||||
if manifest.get("save_directory"):
|
||||
# The interface said "Name it with --save-dir" and had no row for it.
|
||||
rows.append(TuiRow("savedir", f"Save folder: {_save_folder_words(state)}",
|
||||
"--save-dir", style="path"))
|
||||
rows += [
|
||||
TuiRow("jobs", f"Parallel downloads: {state.jobs}", "--jobs"),
|
||||
TuiRow("folder", f"BIOS folder: {_short_path(state.dest, state.home)}", "--dest",
|
||||
@@ -11261,6 +11333,21 @@ def _option_rows(state: TuiState) -> list[TuiRow]:
|
||||
return rows
|
||||
|
||||
|
||||
def _save_dir_given(state: TuiState) -> Path | None:
|
||||
return next((Path(value) for flag, value in state.passthrough
|
||||
if flag == "--save-dir" and value), None)
|
||||
|
||||
|
||||
def _save_folder_words(state: TuiState) -> str:
|
||||
"""The frontend's save directory as the Options row shows it."""
|
||||
given = _save_dir_given(state)
|
||||
if given is not None:
|
||||
return _short_path(given, state.home)
|
||||
if state.saves and state.saves.get("folder"):
|
||||
return _short_path(Path(state.saves["folder"]), state.home)
|
||||
return "not known"
|
||||
|
||||
|
||||
def _push(state: TuiState, screen: str, focus: int = 0) -> list[tuple]:
|
||||
state.stack.append(screen)
|
||||
state.focus[screen] = focus
|
||||
@@ -11371,7 +11458,7 @@ def _choose(state: TuiState, plat: str, dest: Path) -> list[tuple]:
|
||||
# The folder found here is the one the frontend reads: the built-in
|
||||
# default beside it is one it does not, offered as "reads its BIOS
|
||||
# files from" and filled with Enter.
|
||||
state.folder_default = None
|
||||
state.folder_default, state.folder_kind = None, "closed"
|
||||
return _push(state, "folder")
|
||||
card = card_system(dest)
|
||||
if card and card[0] == plat and card[1] != dest:
|
||||
@@ -11396,9 +11483,14 @@ def _choose(state: TuiState, plat: str, dest: Path) -> list[tuple]:
|
||||
return _start_check(state)
|
||||
|
||||
|
||||
def _open_folder_screen(state: TuiState, default: Path | None) -> list[tuple]:
|
||||
state.folder_default = default
|
||||
return _push(state, "folder")
|
||||
def _open_folder_screen(state: TuiState, default: Path | None,
|
||||
kind: str = "usual") -> list[tuple]:
|
||||
state.folder_default, state.folder_kind = default, kind
|
||||
refused = kind == "chosen" and (state.facts.get("writable") is False
|
||||
or _short_of_space(state, state.check or {}))
|
||||
# A folder just refused as full or read-only: Browse, not Use this
|
||||
# folder, is under A, or A, A came back to the same warning.
|
||||
return _push(state, "folder", 1 if refused and default is not None else 0)
|
||||
|
||||
|
||||
def _mount_name(mount: Path) -> str:
|
||||
@@ -11463,6 +11555,33 @@ def _browse_rows(folder: Path | None, zips: bool = False) -> tuple[list[TuiRow],
|
||||
return rows, ""
|
||||
|
||||
|
||||
# The folders of a system root that hold no one's files: A went into
|
||||
# /bin from /, the first name in order.
|
||||
_POSIX_ROOT_SYSTEM = frozenset((
|
||||
"bin", "boot", "dev", "efi", "etc", "lib", "lib32", "lib64", "libx32", "lost+found",
|
||||
"proc", "root", "run", "sbin", "snap", "srv", "sys", "tmp", "usr", "var",
|
||||
))
|
||||
# FILE_ATTRIBUTE_HIDDEN and FILE_ATTRIBUTE_SYSTEM: Explorer shows neither.
|
||||
_WINDOWS_HIDDEN = 0x2 | 0x4
|
||||
|
||||
|
||||
def _os_folder(path: Path, folder: Path) -> bool:
|
||||
"""A folder of the system, not one a person keeps files in: the
|
||||
recycle bin and restore points of a Windows drive ($RECYCLE.BIN, System
|
||||
Volume Information, hidden by Explorer), and /bin, /etc, /usr and the
|
||||
like at the root of a Unix system."""
|
||||
name = path.name
|
||||
if name.startswith(("$", ".")) or name.casefold() == "system volume information":
|
||||
return True
|
||||
try:
|
||||
attributes = getattr(os.stat(path, follow_symlinks=False), "st_file_attributes", 0)
|
||||
except OSError:
|
||||
attributes = 0
|
||||
if attributes & _WINDOWS_HIDDEN:
|
||||
return True
|
||||
return folder == Path("/") and name in _POSIX_ROOT_SYSTEM
|
||||
|
||||
|
||||
def _browse_to(state: TuiState, folder: Path | None, came_from: Path | None = None) -> None:
|
||||
state.browse_dir = folder
|
||||
state.browse_rows, state.status = _browse_rows(folder, state.browse_for == "from")
|
||||
@@ -11477,7 +11596,7 @@ def _browse_to(state: TuiState, folder: Path | None, came_from: Path | None = No
|
||||
focus = next((index for index, row in enumerate(state.browse_rows)
|
||||
if row.action == "dir" and isinstance(row.value, Path)
|
||||
and row.label == f"{row.value.name}/"
|
||||
and not row.value.name.startswith(".")), 0)
|
||||
and not _os_folder(row.value, folder)), 0)
|
||||
if not focus and not state.status:
|
||||
state.status = ("This is your home folder: no setup reads its BIOS files "
|
||||
"here. Go back to type a path." if folder == state.home
|
||||
@@ -11532,8 +11651,15 @@ def _activate(state: TuiState, row: TuiRow) -> list[tuple]:
|
||||
if state.dest_given is not None:
|
||||
return _choose(state, row.value, state.dest_given)
|
||||
state.platform = row.value
|
||||
default = None if row.value in CARD_PLATFORMS else _default_dest(state.os_type, row.value)
|
||||
return _open_folder_screen(state, default)
|
||||
if row.value in CARD_PLATFORMS:
|
||||
return _open_folder_screen(state, None)
|
||||
# The folder detected here before the built-in one: the built-in
|
||||
# default was offered as where RetroArch reads, when the
|
||||
# retroarch.cfg found here names another.
|
||||
found = next((path for plat, path, _label in state.setups if plat == row.value), None)
|
||||
if found is not None:
|
||||
return _open_folder_screen(state, found, "found")
|
||||
return _open_folder_screen(state, _default_dest(state.os_type, row.value))
|
||||
if screen == "folder":
|
||||
if action == "default":
|
||||
return _choose(state, state.platform, state.folder_default)
|
||||
@@ -11546,6 +11672,12 @@ def _activate(state: TuiState, row: TuiRow) -> list[tuple]:
|
||||
state.path_selected = bool(state.path_text)
|
||||
return _push(state, "path")
|
||||
if screen == "browse":
|
||||
if action == "use" and state.browse_for == "save":
|
||||
state.passthrough = [flag for flag in state.passthrough
|
||||
if flag[0] != "--save-dir"]
|
||||
state.passthrough.append(("--save-dir", str(state.browse_dir)))
|
||||
state.browse_for = "dest"
|
||||
return _start_check(state)
|
||||
if action in ("use", "zip") and state.browse_for == "from":
|
||||
chosen = state.browse_dir if action == "use" else row.value
|
||||
if chosen not in state.from_folders:
|
||||
@@ -11582,7 +11714,7 @@ def _activate(state: TuiState, row: TuiRow) -> list[tuple]:
|
||||
if action == "quit":
|
||||
return tui_quit(state)
|
||||
if action == "folder":
|
||||
return _open_folder_screen(state, state.dest)
|
||||
return _open_folder_screen(state, state.dest, "chosen")
|
||||
if action == "fromcard":
|
||||
return _open_from_browser(state)
|
||||
if action in ("misplaced", "unreadable"):
|
||||
@@ -11660,7 +11792,7 @@ def _option(state: TuiState, action: str, step: int) -> list[tuple]:
|
||||
elif action in ("regions", "command"):
|
||||
return _push(state, action)
|
||||
elif action == "folder":
|
||||
return _open_folder_screen(state, state.dest)
|
||||
return _open_folder_screen(state, state.dest, "chosen")
|
||||
elif action == "from" and step < 0:
|
||||
# Said, and the screen stays: Back to the plan counts again.
|
||||
if state.from_folders:
|
||||
@@ -11668,6 +11800,10 @@ def _option(state: TuiState, action: str, step: int) -> list[tuple]:
|
||||
state.status = f"No longer copying from {_last_name(str(removed))}."
|
||||
elif action == "from":
|
||||
return _open_from_browser(state)
|
||||
elif action == "savedir":
|
||||
state.browse_for = "save"
|
||||
_push(state, "browse")
|
||||
_browse_to(state, _nearest_folder(_save_dir_given(state) or state.home))
|
||||
elif action == "preview":
|
||||
_push(state, "preview")
|
||||
return _start_run(state, "preview")
|
||||
@@ -11679,10 +11815,23 @@ def _open_from_browser(state: TuiState) -> list[tuple]:
|
||||
state.browse_for = "from"
|
||||
_push(state, "browse")
|
||||
start = state.from_folders[-1].parent if state.from_folders else state.home
|
||||
downloads = state.home / "Downloads"
|
||||
if not state.from_folders and _is_dir(downloads) and any(
|
||||
name.lower().endswith(".zip") for name in _listed(downloads)):
|
||||
# A pack or a backup ZIP is most often there: 18 presses from the
|
||||
# first screen reached it through HOME, past every mounted drive.
|
||||
start = downloads
|
||||
_browse_to(state, _nearest_folder(start))
|
||||
return []
|
||||
|
||||
|
||||
def _listed(folder: Path) -> list[str]:
|
||||
try:
|
||||
return os.listdir(folder)
|
||||
except OSError:
|
||||
return []
|
||||
|
||||
|
||||
def _toggle_region(state: TuiState, name: str) -> None:
|
||||
if name in state.regions:
|
||||
state.regions.remove(name)
|
||||
@@ -12328,7 +12477,10 @@ def _scan_lines(state: TuiState) -> list[str]:
|
||||
for drive in scan["other_drives"]]
|
||||
if state.unreadable:
|
||||
count = len(state.unreadable)
|
||||
lines.append(f"{_plural(count, 'file')} on your old card could not be read.")
|
||||
# Named as it was given: "your old card" was said of a folder.
|
||||
where = ", ".join(_last_name(folder) for folder in
|
||||
dict.fromkeys(event["folder"] for event in state.unreadable))
|
||||
lines.append(f"{_plural(count, 'file')} in {where} could not be read.")
|
||||
return lines
|
||||
|
||||
|
||||
@@ -12387,8 +12539,14 @@ def _plan_lines(check: dict, dest: Path | None, state: TuiState) -> list:
|
||||
]
|
||||
if state.facts.get("writable") is False:
|
||||
lines.append("You cannot write to this folder: choose another one.")
|
||||
lines.append(f"{check['present']} in place, {check['missing']} missing, "
|
||||
f"{wrong} wrong {'copy' if wrong == 1 else 'copies'}")
|
||||
counts = (f"{check['present']} in place, {check['missing']} missing, "
|
||||
f"{wrong} wrong {'copy' if wrong == 1 else 'copies'}")
|
||||
if copied and fetch:
|
||||
# 47 to download above 49 missing and 1 wrong copy: the 3 found on
|
||||
# this machine are the difference, said where the sum is read.
|
||||
counts += (f": {len(copied)} of them {'is' if len(copied) == 1 else 'are'} "
|
||||
f"copied from this machine, {fetch} downloaded")
|
||||
lines.append(counts)
|
||||
saves = (state.saves if check is state.check
|
||||
else state.dry_saves if check is state.dry else None)
|
||||
lines += [_styled(line, "path") for line in _saves_lines(state, saves)]
|
||||
@@ -12402,7 +12560,10 @@ def _plan_lines(check: dict, dest: Path | None, state: TuiState) -> list:
|
||||
lines.append(f"Not enough free space: {format_size(check['bytes_needed'])} "
|
||||
"needed. Free some, or choose another folder.")
|
||||
if wrong:
|
||||
lines.append("They are replaced, and the old copies are kept in a backup folder.")
|
||||
# With its subject: "They are replaced" sat lines under the count.
|
||||
lines.append(f"The {wrong} wrong {'copy' if wrong == 1 else 'copies'} "
|
||||
f"{'is' if wrong == 1 else 'are'} replaced, the old "
|
||||
f"{'one' if wrong == 1 else 'ones'} kept in a backup folder.")
|
||||
misplaced = _tui_misplaced(state) if check is state.check else []
|
||||
if misplaced:
|
||||
lines.insert(3, f"{_plural(len(misplaced), 'file')} "
|
||||
@@ -12425,7 +12586,8 @@ def _plan_lines(check: dict, dest: Path | None, state: TuiState) -> list:
|
||||
if check.get("not_installable"):
|
||||
lines += _unavailable_lines(state)
|
||||
if check is state.check:
|
||||
lines += _unplaced_lines(state.check_summary, state)
|
||||
lines += _unplaced_lines(state.check_summary, state,
|
||||
"Choose it in Options, Save folder.")
|
||||
return lines[:2] + offline_lines + lines[2:]
|
||||
|
||||
|
||||
@@ -12442,7 +12604,8 @@ def _saves_lines(state: TuiState, saves: dict | None) -> list[str]:
|
||||
f"{_short_path(Path(saves['folder']), state.home)}"]
|
||||
|
||||
|
||||
def _unplaced_lines(summary: dict | None, state: TuiState) -> list[str]:
|
||||
def _unplaced_lines(summary: dict | None, state: TuiState,
|
||||
remedy: str = "Name it with --save-dir.") -> list[str]:
|
||||
"""The files read from the frontend's save directory that the run left
|
||||
out, that directory being unknown: said, never silent."""
|
||||
count = sum(group.get("files", 0) for group in (summary or {}).get("skipped") or []
|
||||
@@ -12451,7 +12614,7 @@ def _unplaced_lines(summary: dict | None, state: TuiState) -> list[str]:
|
||||
return []
|
||||
name = display_name(state.platform) if state.platform else "the frontend"
|
||||
return [f"{_plural(count, 'file')} read from {name}'s save directory left out: "
|
||||
"its place is not known. Name it with --save-dir."]
|
||||
f"its place is not known. {remedy}"]
|
||||
|
||||
|
||||
def _view_setup(state: TuiState, width: int, height: int) -> tuple:
|
||||
@@ -12475,10 +12638,17 @@ def _view_platforms(state: TuiState, width: int, height: int) -> tuple:
|
||||
else:
|
||||
title = "Which setup?"
|
||||
before = ["Choose the setup that will use the BIOS files."]
|
||||
pinned = list(state.refused)
|
||||
if state.dest_given is None and not state.setups and len(state.stack) == 1:
|
||||
# The first screen when nothing was found: at 40x15 its 18 rows
|
||||
# left no line to say so, or what to do.
|
||||
pinned.append("Nothing was found here: choose the setup that will use the "
|
||||
"BIOS files.")
|
||||
before = []
|
||||
needle = state.filters.get("platforms", "")
|
||||
after = [f"No setup matches '{needle}'."] if len(rows) == 1 and needle else []
|
||||
after += [state.status] if state.status else []
|
||||
return (title, _compose(state, width, height, before, rows, after, pinned=state.refused),
|
||||
return (title, _compose(state, width, height, before, rows, after, pinned=pinned),
|
||||
("A/Enter Choose", "B/Esc Back", "/ Filter", "? Help"))
|
||||
|
||||
|
||||
@@ -12490,7 +12660,13 @@ def _view_folder(state: TuiState, width: int, height: int) -> tuple:
|
||||
"root folder, the one holding .system."]
|
||||
elif state.folder_default is not None:
|
||||
title = "Which folder?"
|
||||
before = [f"{name} usually reads its BIOS files from:", str(state.folder_default)]
|
||||
lead = {"found": f"{name} reads its BIOS files from:",
|
||||
"chosen": f"The BIOS folder chosen for {name}:"}.get(
|
||||
state.folder_kind, f"{name} usually reads its BIOS files from:")
|
||||
before = [lead, str(state.folder_default)]
|
||||
elif state.folder_kind == "closed":
|
||||
title = "Which folder?"
|
||||
before = [f"Choose another folder for {name}: browse for it or type it."]
|
||||
else:
|
||||
title = "Which folder?"
|
||||
before = [f"No usual {name} folder here: browse for it or type it."]
|
||||
@@ -12506,7 +12682,11 @@ def _view_browse(state: TuiState, width: int, height: int) -> tuple:
|
||||
if state.browse_for == "from":
|
||||
after.insert(0, "Choose the old card, a backup folder or a ZIP: its BIOS files "
|
||||
"are copied, each one checked, nothing on it is changed.")
|
||||
return ("Copy from" if state.browse_for == "from" else "Browse for a folder",
|
||||
elif state.browse_for == "save":
|
||||
after.insert(0, f"Choose {display_name(state.platform)}'s save directory: some "
|
||||
"cores read files there.")
|
||||
return ("Copy from" if state.browse_for == "from"
|
||||
else "Save folder" if state.browse_for == "save" else "Browse for a folder",
|
||||
_compose(state, width, height, [where], tui_rows(state), after),
|
||||
("A/Enter Open", "B/Esc Back", "? Help"))
|
||||
|
||||
@@ -12548,8 +12728,9 @@ def _view_checking(state: TuiState, width: int, height: int) -> tuple:
|
||||
if state.manifest is None:
|
||||
lines.append(f"Getting the file list... {elapsed}")
|
||||
else:
|
||||
files = len(state.manifest.get("files", []))
|
||||
lines += [f"File list: {_plural(files, 'file')}.", _reading_line(state, elapsed)]
|
||||
# Not its count: the check counts the files it checks, which the
|
||||
# plan then gives (8179 after "File list: 8169 files").
|
||||
lines += ["File list received.", _reading_line(state, elapsed)]
|
||||
return ("Checking", _compose(state, width, height, lines, []),
|
||||
("Ctrl-C Quit", "B/Esc Back"))
|
||||
|
||||
@@ -12604,6 +12785,7 @@ def _ticked_files(state: TuiState) -> list[dict]:
|
||||
|
||||
def _view_systems(state: TuiState, width: int, height: int) -> tuple:
|
||||
rows = tui_rows(state)
|
||||
whole_rows = rows
|
||||
if width < 60:
|
||||
# The name first: the size alone stays beside it, the count is in
|
||||
# the total below.
|
||||
@@ -12621,6 +12803,14 @@ def _view_systems(state: TuiState, width: int, height: int) -> tuple:
|
||||
f"{format_size(sum(e['size'] for e in chosen))}"
|
||||
+ (" before your options" if _counts_predate_options(state) else "")]
|
||||
focused = rows[min(state.focus.get("systems", 0), len(rows) - 1)]
|
||||
drawn = str(_row_line(focused, True, width))
|
||||
if focused.action == "system" and not (
|
||||
f"> {focused.label}" in drawn
|
||||
and any(form and drawn.endswith(form) for form in (focused.right, *focused.narrow))):
|
||||
# A long name kept whole leaves no room for its size, and a name
|
||||
# longer than the screen is cut: both are spelled out below.
|
||||
whole = whole_rows[min(state.focus.get("systems", 0), len(whole_rows) - 1)]
|
||||
footer.insert(0, f"{whole.label[4:]}: {whole.right}")
|
||||
if focused.value == OTHER_SYSTEMS:
|
||||
footer.insert(0, OTHER_HELP)
|
||||
elif "shared" in focused.right:
|
||||
@@ -12732,7 +12922,11 @@ def _view_confirm(state: TuiState, width: int, height: int) -> tuple:
|
||||
if state.running or state.dry is None:
|
||||
lines: list = ["Counting the files for these choices..."]
|
||||
else:
|
||||
lines = _plan_lines(state.dry, state.dest, state)
|
||||
# Why this screen: the numbers above are counted again for the
|
||||
# systems and options chosen, the last look before anything is
|
||||
# written.
|
||||
lines = ["Counted again for your choices. Nothing is written before Install.",
|
||||
*_plan_lines(state.dry, state.dest, state)]
|
||||
return ("Confirm", _compose(state, width, height, lines, tui_rows(state)),
|
||||
("A/Enter Install", "B/Esc Back", "? Help"))
|
||||
|
||||
@@ -12860,14 +13054,30 @@ def _unavailable_lines(state: TuiState) -> list[str]:
|
||||
rows = {row["system"]: row for row in state.systems}
|
||||
|
||||
def system(name: str) -> str:
|
||||
return _tui_system_label(rows.get(name) or {"system": name}, titles)
|
||||
label = _tui_system_label(rows.get(name) or {"system": name}, titles)
|
||||
# An id no title names (jazz-jackrabbit-2) read as a raw key.
|
||||
return label.replace("-", " ").title() if label == name else label
|
||||
|
||||
names = [f"{entry['dest']} ({system(entry['system'])})" if entry.get("system")
|
||||
else entry["dest"] for entry in needed[:3]]
|
||||
more = f" and {len(needed) - 3} more" if len(needed) > 3 else ""
|
||||
return [f"Not available here, and needed to start "
|
||||
f"{'its emulator' if len(needed) == 1 else 'their emulators'}: "
|
||||
f"{', '.join(names)}{more}. Your own copy goes in the BIOS folder."]
|
||||
def named(entries: list[dict]) -> str:
|
||||
names = [f"{entry['dest']} ({system(entry['system'])})" if entry.get("system")
|
||||
else entry["dest"] for entry in entries[:3]]
|
||||
more = f" and {len(entries) - 3} more" if len(entries) > 3 else ""
|
||||
return f"{', '.join(names)}{more}"
|
||||
|
||||
# A game's own data is not firmware: on every RetroArch plan the one
|
||||
# alarm named Jazz Jackrabbit 2's Anims.j2a, read as a broken install.
|
||||
games = [entry for entry in needed if entry.get("category") == GAME_DATA]
|
||||
firmware = [entry for entry in needed if entry.get("category") != GAME_DATA]
|
||||
lines = []
|
||||
if firmware:
|
||||
lines.append(f"Not available here, and needed to start "
|
||||
f"{'its emulator' if len(firmware) == 1 else 'their emulators'}: "
|
||||
f"{named(firmware)}. Your own copy goes in the BIOS folder.")
|
||||
if games:
|
||||
lines.append(f"Game data not available here, read by "
|
||||
f"{'its game' if len(games) == 1 else 'their games'}: "
|
||||
f"{named(games)}. Copy it from your own copy of the game.")
|
||||
return lines
|
||||
|
||||
|
||||
def tui_outcome(state: TuiState) -> tuple[str, list[str]]:
|
||||
@@ -13064,7 +13274,7 @@ def _help_lines(state: TuiState) -> list[tuple[str, str]]:
|
||||
if screen == "installing":
|
||||
return [(names["stop"], "Stop: what is done stays done"),
|
||||
*([(names["kill"], "Stop")] if names["kill"] else []),
|
||||
*_route_extras(state), ("?, F1", "This help")]
|
||||
*_route_extras(state), *_help_key(state)]
|
||||
if screen == "path":
|
||||
return [(names["enter"], "Use this folder"), ("Backspace", "Delete a character"),
|
||||
("Ctrl-U", "Clear"), (names["esc"], "Back"), ("Ctrl-C", "Quit"),
|
||||
@@ -13092,10 +13302,16 @@ def _help_lines(state: TuiState) -> list[tuple[str, str]]:
|
||||
keys.append((names["options"], "Options"))
|
||||
back = "Quit" if len(state.stack) == 1 else "Back"
|
||||
keys += [(names["back"], back), ("q, Ctrl-C", "Quit"), *kill, *_route_extras(state),
|
||||
("?, F1", "This help")]
|
||||
*_help_key(state)]
|
||||
return keys
|
||||
|
||||
|
||||
def _help_key(state: TuiState) -> list[tuple[str, str]]:
|
||||
"""? and F1, named only where a keyboard may send them: no pad route
|
||||
maps a button to either."""
|
||||
return [("?, F1", "This help")] if state.pad in ("both", "keys") else []
|
||||
|
||||
|
||||
def _primary(state: TuiState) -> str | None:
|
||||
"""What A does on the row in focus, for the first entry of the bar."""
|
||||
rows = tui_rows(state)
|
||||
@@ -13109,7 +13325,10 @@ def _primary(state: TuiState) -> str | None:
|
||||
def _view_help(state: TuiState, width: int, height: int) -> tuple:
|
||||
keys = _help_lines(state)
|
||||
column = min(22, max(cell_width(key) for key, _what in keys) + 2)
|
||||
lines = [f"{_pad(key, column)}{what}" for key, what in keys]
|
||||
# Two spaces at least: a key as wide as the column touched its text
|
||||
# ("Select Keyboard on or off" on muxterm).
|
||||
lines = [f"{_pad(key, column)}{what}" if cell_width(key) <= column - 2
|
||||
else f"{key} {what}" for key, what in keys]
|
||||
if state.screen in ("setup", "plan", "browse", "folder") and state.dest is not None:
|
||||
lines += ["", f"BIOS folder: {state.dest}"]
|
||||
return ("Keys", _compose(state, width, height, lines, []), ("Any key closes this help",))
|
||||
@@ -13615,12 +13834,14 @@ def tui_farewell(state: TuiState) -> list[str]:
|
||||
|
||||
def tui_idle_code(state: TuiState) -> int:
|
||||
"""The exit code of an idle close: an error's or a result's on those
|
||||
screens, so a launcher tells them from a close with nothing to say."""
|
||||
screens, so a launcher tells them from a close with nothing to say.
|
||||
Elsewhere nothing was installed, and 0 said it all was: 130, as a quit
|
||||
before the install."""
|
||||
if state.screen == "error":
|
||||
return state.error_code
|
||||
if state.screen == "result" and state.code is not None:
|
||||
return state.code
|
||||
return 0
|
||||
return 130
|
||||
|
||||
|
||||
def _farewell_lines(state: TuiState) -> list[str]:
|
||||
|
||||
+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="cec348d9aa6c9f940a2c9dc9bc6414a923acea8137e4a3624d4710c9d88c38ad"
|
||||
DEFAULT_INSTALL_SHA256="c63a84da36e10657838dc359f4258db75e098d4902ee27312296ccd22a6994f4"
|
||||
MAX_INSTALLER_BYTES=2097152
|
||||
|
||||
cleanup() {
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
{
|
||||
"dest": "roms/ports/retrobios/install.py",
|
||||
"repo_path": "install.py",
|
||||
"size": 611006,
|
||||
"sha256": "cec348d9aa6c9f940a2c9dc9bc6414a923acea8137e4a3624d4710c9d88c38ad",
|
||||
"size": 622744,
|
||||
"sha256": "c63a84da36e10657838dc359f4258db75e098d4902ee27312296ccd22a6994f4",
|
||||
"executable": false
|
||||
},
|
||||
{
|
||||
@@ -62,8 +62,8 @@
|
||||
{
|
||||
"dest": "roms/ports/retrobios/tui.txt",
|
||||
"repo_path": "ports/shared/tui.txt",
|
||||
"size": 34998,
|
||||
"sha256": "6e22292fc3f0688e0509bebd55f242fb3eecf9e149d5dc365ea75447e921bf78",
|
||||
"size": 35356,
|
||||
"sha256": "d2d56e2c67f0a5d0d11fc75e28f337516006cc4414e18662e100359caa642a97",
|
||||
"executable": false
|
||||
}
|
||||
]
|
||||
@@ -71,8 +71,8 @@
|
||||
"muos": {
|
||||
"archive": {
|
||||
"dest": "ARCHIVE/RetroBIOS.muxapp",
|
||||
"size": 661660,
|
||||
"sha256": "681ae3d8eb8572640b57dd4ca8699cb2cb8f011d1071ba5ef286c1b2eeb1877d"
|
||||
"size": 675244,
|
||||
"sha256": "447a2eb9863dd1dad2108d22132bbb8d553adca5ebf1b2bddfbce698df42cf3f"
|
||||
},
|
||||
"members": [
|
||||
{
|
||||
@@ -82,11 +82,18 @@
|
||||
"sha256": "c0cfafad5fae8e6764b263b14a06195b267f66c4fe042333941ae6bcc6dab265",
|
||||
"executable": false
|
||||
},
|
||||
{
|
||||
"name": "RetroBIOS/glyph/retrobios.png",
|
||||
"repo_path": "ports/muos/glyph/retrobios.png",
|
||||
"size": 1354,
|
||||
"sha256": "93d0d1248a3bb78d1222b113276137de971a33468da72b91f79248100f81cc72",
|
||||
"executable": false
|
||||
},
|
||||
{
|
||||
"name": "RetroBIOS/install.py",
|
||||
"repo_path": "install.py",
|
||||
"size": 611006,
|
||||
"sha256": "cec348d9aa6c9f940a2c9dc9bc6414a923acea8137e4a3624d4710c9d88c38ad",
|
||||
"size": 622744,
|
||||
"sha256": "c63a84da36e10657838dc359f4258db75e098d4902ee27312296ccd22a6994f4",
|
||||
"executable": false
|
||||
},
|
||||
{
|
||||
@@ -134,8 +141,8 @@
|
||||
{
|
||||
"name": "RetroBIOS/tui.txt",
|
||||
"repo_path": "ports/shared/tui.txt",
|
||||
"size": 34998,
|
||||
"sha256": "6e22292fc3f0688e0509bebd55f242fb3eecf9e149d5dc365ea75447e921bf78",
|
||||
"size": 35356,
|
||||
"sha256": "d2d56e2c67f0a5d0d11fc75e28f337516006cc4414e18662e100359caa642a97",
|
||||
"executable": false
|
||||
}
|
||||
]
|
||||
@@ -145,8 +152,8 @@
|
||||
{
|
||||
"dest": ".config/modules/RetroBIOS.sh",
|
||||
"repo_path": "ports/tools/RetroBIOS.sh",
|
||||
"size": 4732,
|
||||
"sha256": "556c83159111a2071097a38590e3335aea90f76201473e064ba8365882fb2a6f",
|
||||
"size": 4846,
|
||||
"sha256": "bf5aabfe4f73d39cb35bdc2868b18850fc71154ed53f43aabf582e8870f7b227",
|
||||
"executable": true
|
||||
},
|
||||
{
|
||||
@@ -159,8 +166,8 @@
|
||||
{
|
||||
"dest": ".config/modules/retrobios/install.py",
|
||||
"repo_path": "install.py",
|
||||
"size": 611006,
|
||||
"sha256": "cec348d9aa6c9f940a2c9dc9bc6414a923acea8137e4a3624d4710c9d88c38ad",
|
||||
"size": 622744,
|
||||
"sha256": "c63a84da36e10657838dc359f4258db75e098d4902ee27312296ccd22a6994f4",
|
||||
"executable": false
|
||||
},
|
||||
{
|
||||
@@ -201,8 +208,8 @@
|
||||
{
|
||||
"dest": ".config/modules/retrobios/tui.txt",
|
||||
"repo_path": "ports/shared/tui.txt",
|
||||
"size": 34998,
|
||||
"sha256": "6e22292fc3f0688e0509bebd55f242fb3eecf9e149d5dc365ea75447e921bf78",
|
||||
"size": 35356,
|
||||
"sha256": "d2d56e2c67f0a5d0d11fc75e28f337516006cc4414e18662e100359caa642a97",
|
||||
"executable": false
|
||||
}
|
||||
]
|
||||
|
||||
@@ -60,6 +60,8 @@ if [ -e "${RB_ARK_MARKER:-/usr/share/plymouth/themes/text.plymouth}" ]; then
|
||||
RB_BIOS_DEST=$(awk -F'"' '/^system_directory *=/ { print $2; exit }' \
|
||||
"${RB_RETROARCH_CFG:-/home/ark/.config/retroarch/retroarch.cfg}" 2>/dev/null)
|
||||
RB_BIOS_DEST="${RB_BIOS_DEST:-/$directory/bios}"
|
||||
# The font ArkOS's own tools put back on leaving (dArkOS_Tools/Wifi.sh).
|
||||
RB_FONT_DEFAULT="Lat7-Terminus20x10"
|
||||
fi
|
||||
# shellcheck source=../shared/tui.txt
|
||||
source "$GAMEDIR/tui.txt"
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
@@ -30,6 +30,9 @@
|
||||
# RB_BIOS_DEST the one of them the firmware reads, never offered as the
|
||||
# other card
|
||||
# RB_FALLBACK 1 when the launcher has screens of its own to fall back to
|
||||
# RB_FONT_DEFAULT the console font the firmware itself puts back, by
|
||||
# name (ArkOS: Lat7-Terminus20x10), for a setfont that
|
||||
# cannot save the one in place. Unset, the font is kept.
|
||||
# RB_PAD the input route, exported to the interface as
|
||||
# RETROBIOS_PAD: gptokeyb (retrobios.gptk, the default when
|
||||
# RB_MAPPER is set), muxterm (muxterm's own keys), evmapy
|
||||
@@ -493,8 +496,9 @@ rb_route_ready() {
|
||||
# The largest Terminus font that keeps 56 columns and 20 rows, the way
|
||||
# dArkOS's own tools pick one per screen (Lat7-Terminus16 on 480x320,
|
||||
# TerminusBold22x11 on 640x480, 32x16 on 1024x768). Only changed when the
|
||||
# font in place can be put back: saved with setfont -O, or the
|
||||
# Lat7-Terminus20x10 ArkOS tools restore.
|
||||
# font in place can be put back: saved with setfont -O, or RB_FONT_DEFAULT,
|
||||
# the one the firmware's own tools restore. A fixed fallback gave a device
|
||||
# whose default differs another font after the run.
|
||||
rb_font_set() {
|
||||
command -v setfont >/dev/null 2>&1 || return 0
|
||||
local size width height spec fh fw name file="" fallback
|
||||
@@ -524,7 +528,8 @@ rb_font_set() {
|
||||
RB_FONT_BACK="$RB_STATE/font.psf"
|
||||
return 0
|
||||
fi
|
||||
fallback="$RB_FONTS/Lat7-Terminus20x10.psf.gz"
|
||||
[ -n "${RB_FONT_DEFAULT:-}" ] || return 0
|
||||
fallback="$RB_FONTS/$RB_FONT_DEFAULT.psf.gz"
|
||||
if [ -f "$fallback" ] && $ESUDO setfont -C "$RB_TTY" "$file" >> "$RB_LOG" 2>&1; then
|
||||
RB_FONT_BACK="$fallback"
|
||||
fi
|
||||
|
||||
@@ -40,6 +40,8 @@ if [ -d "$ROOT/opt/system" ] && [ -d "$ROOT/opt/inttools" ]; then
|
||||
fi
|
||||
RB_ROUTES="vt"
|
||||
RB_CONSOLE="${RB_CONSOLE:-/dev/tty1}"
|
||||
# The font ArkOS's own tools put back on leaving (dArkOS_Tools/Wifi.sh).
|
||||
RB_FONT_DEFAULT="Lat7-Terminus20x10"
|
||||
RB_PACK_PATH="$RB_DIR:$HERE:/roms:/roms2"
|
||||
# The second card is /roms2, and "Switch to SD2 for Roms" moves RetroArch's
|
||||
# system folder there (dArkOS scripts/Switch to SD2 for Roms.sh:91-92): the
|
||||
|
||||
@@ -109,6 +109,10 @@ def archive_members(name: str, root: Path = REPO_ROOT) -> list[tuple[str, Path |
|
||||
members = [
|
||||
("RetroBIOS/mux_launch.sh", source / "mux_launch.sh"),
|
||||
("RetroBIOS/README.md", source / "README.md"),
|
||||
# muOS draws an application's list glyph from glyph/<ICON>.png
|
||||
# in its own folder (apply_app_glyph, frontend common/ui/glyph.c):
|
||||
# `# ICON: retrobios` resolved to nothing without it.
|
||||
("RetroBIOS/glyph/retrobios.png", source / "glyph" / "retrobios.png"),
|
||||
*_engine("RetroBIOS", root),
|
||||
]
|
||||
elif name == TOOLS_ZIP:
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
RetroArch: ~/.config/retroarch/system
|
||||
|
||||
File list: 5 files.
|
||||
File list received.
|
||||
Reading the files already in the folder... 0:04
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
RetroArch: ~/.config/retroarch/system
|
||||
|
||||
File list: 5 files.
|
||||
File list received.
|
||||
Reading the files already in the
|
||||
folder... 0:04
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
RetroArch: ~/.config/retroarch/system
|
||||
|
||||
File list: 5 files.
|
||||
File list received.
|
||||
Reading the files already in the
|
||||
folder... 0:04
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
RetroArch: ~/.config/retroarch/system
|
||||
|
||||
File list: 5 files.
|
||||
File list received.
|
||||
Reading the files already in the folder... 0:04
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,6 @@
|
||||
RetroBIOS: Confirm
|
||||
|
||||
Counted again for your choices. Nothing is written before Install.
|
||||
1 file to download, 16.0 KB
|
||||
BIOS folder: ~/.config/retroarch/system
|
||||
0 in place, 1 missing, 0 wrong copies
|
||||
@@ -34,7 +35,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
A/Enter Install B/Esc Back ? Help
|
||||
Vendored
+2
-2
@@ -1,5 +1,7 @@
|
||||
RetroBIOS: Confirm
|
||||
|
||||
Counted again for your choices.
|
||||
Nothing is written before Install.
|
||||
1 file to download, 16.0 KB
|
||||
BIOS folder: ~/.co.../retroarch/system
|
||||
0 in place, 1 missing, 0 wrong copies
|
||||
@@ -7,6 +9,4 @@
|
||||
|
||||
> Install
|
||||
Back
|
||||
|
||||
|
||||
A/Enter Install B/Esc Back ? Help
|
||||
Vendored
+2
-2
@@ -1,5 +1,7 @@
|
||||
RetroBIOS: Confirm
|
||||
|
||||
Counted again for your choices.
|
||||
Nothing is written before Install.
|
||||
1 file to download, 16.0 KB
|
||||
BIOS folder: ~/.co.../retroarch/system
|
||||
0 in place, 1 missing, 0 wrong copies
|
||||
@@ -10,6 +12,4 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
A/Enter Install B/Esc Back ? Help
|
||||
Vendored
+1
-1
@@ -1,5 +1,6 @@
|
||||
RetroBIOS: Confirm
|
||||
|
||||
Counted again for your choices. Nothing is written before Install.
|
||||
1 file to download, 16.0 KB
|
||||
BIOS folder: ~/.config/retroarch/system
|
||||
0 in place, 1 missing, 0 wrong copies
|
||||
@@ -20,5 +21,4 @@
|
||||
|
||||
|
||||
|
||||
|
||||
A/Enter Install B/Esc Back ? Help
|
||||
Vendored
+1
-1
@@ -4,7 +4,7 @@
|
||||
BIOS folder: ~/.config/retroarch/system
|
||||
1 in place, 3 missing, 1 wrong copy
|
||||
Free space: 50.0 GB
|
||||
They are replaced, and the old copies are kept in a backup folder.
|
||||
The 1 wrong copy is replaced, the old one kept in a backup folder.
|
||||
|
||||
> Install
|
||||
Choose systems
|
||||
|
||||
Vendored
+2
-2
@@ -4,8 +4,8 @@
|
||||
BIOS folder: ~/.co.../retroarch/system
|
||||
1 in place, 3 missing, 1 wrong copy
|
||||
Free space: 50.0 GB
|
||||
They are replaced, and the old copies
|
||||
are kept in a backup folder.
|
||||
The 1 wrong copy is replaced, the old
|
||||
one kept in a backup folder.
|
||||
|
||||
> Install
|
||||
Choose systems
|
||||
|
||||
Vendored
+1
-1
@@ -4,7 +4,7 @@
|
||||
BIOS folder: ~/.config/retroarch/system
|
||||
1 in place, 3 missing, 1 wrong copy
|
||||
Free space: 50.0 GB
|
||||
They are replaced, and the old copies are kept in a backup folder.
|
||||
The 1 wrong copy is replaced, the old one kept in a backup folder.
|
||||
|
||||
> Install
|
||||
Choose systems
|
||||
|
||||
Vendored
+14
-14
@@ -381,7 +381,7 @@
|
||||
|
||||
RetroArch: ~/.config/retroarch/system
|
||||
|
||||
File list: 5 files.
|
||||
File list received.
|
||||
Reading the files already in the
|
||||
folder... 0:04
|
||||
|
||||
@@ -394,7 +394,7 @@
|
||||
|
||||
RetroArch: ~/.config/retroarch/system
|
||||
|
||||
File list: 5 files.
|
||||
File list received.
|
||||
Reading the files already in the
|
||||
folder... 0:04
|
||||
|
||||
@@ -410,7 +410,7 @@
|
||||
|
||||
RetroArch: ~/.config/retroarch/system
|
||||
|
||||
File list: 5 files.
|
||||
File list received.
|
||||
Reading the files already in the folder... 0:04
|
||||
|
||||
|
||||
@@ -450,8 +450,8 @@
|
||||
BIOS folder: ~/.co.../retroarch/system
|
||||
1 in place, 3 missing, 1 wrong copy
|
||||
Free space: 50.0 GB
|
||||
They are replaced, and the old copies
|
||||
are kept in a backup folder.
|
||||
The 1 wrong copy is replaced, the old
|
||||
one kept in a backup folder.
|
||||
|
||||
> Install
|
||||
Choose systems
|
||||
@@ -466,7 +466,7 @@
|
||||
BIOS folder: ~/.config/retroarch/system
|
||||
1 in place, 3 missing, 1 wrong copy
|
||||
Free space: 50.0 GB
|
||||
They are replaced, and the old copies are kept in a backup folder.
|
||||
The 1 wrong copy is replaced, the old one kept in a backup folder.
|
||||
|
||||
> Install
|
||||
Choose systems
|
||||
@@ -865,6 +865,8 @@
|
||||
=== confirm 40x12
|
||||
RetroBIOS: Confirm
|
||||
|
||||
Counted again for your choices.
|
||||
Nothing is written before Install.
|
||||
1 file to download, 16.0 KB
|
||||
BIOS folder: ~/.co.../retroarch/system
|
||||
0 in place, 1 missing, 0 wrong copies
|
||||
@@ -872,12 +874,12 @@
|
||||
|
||||
> Install
|
||||
Back
|
||||
|
||||
|
||||
A Install B Back
|
||||
=== confirm 40x15
|
||||
RetroBIOS: Confirm
|
||||
|
||||
Counted again for your choices.
|
||||
Nothing is written before Install.
|
||||
1 file to download, 16.0 KB
|
||||
BIOS folder: ~/.co.../retroarch/system
|
||||
0 in place, 1 missing, 0 wrong copies
|
||||
@@ -888,12 +890,11 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
A Install B Back
|
||||
=== confirm 80x24
|
||||
RetroBIOS: Confirm
|
||||
|
||||
Counted again for your choices. Nothing is written before Install.
|
||||
1 file to download, 16.0 KB
|
||||
BIOS folder: ~/.config/retroarch/system
|
||||
0 in place, 1 missing, 0 wrong copies
|
||||
@@ -914,7 +915,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
A Install B Back
|
||||
=== installing 40x12
|
||||
RetroBIOS: Installing
|
||||
@@ -1140,10 +1140,10 @@
|
||||
Y, Tab, o Options
|
||||
B, Select, Esc Back
|
||||
q, Ctrl-C Quit
|
||||
?, F1 This help
|
||||
|
||||
BIOS folder:
|
||||
/home/user/.config/retroarch/system
|
||||
|
||||
Any key closes this help
|
||||
=== help 40x15
|
||||
RetroBIOS: Keys
|
||||
@@ -1153,13 +1153,13 @@
|
||||
Y, Tab, o Options
|
||||
B, Select, Esc Back
|
||||
q, Ctrl-C Quit
|
||||
?, F1 This help
|
||||
|
||||
BIOS folder:
|
||||
/home/user/.config/retroarch/system
|
||||
|
||||
|
||||
|
||||
|
||||
Any key closes this help
|
||||
=== help 80x24
|
||||
RetroBIOS: Keys
|
||||
@@ -1169,7 +1169,6 @@
|
||||
Y, Tab, o Options
|
||||
B, Select, Esc Back
|
||||
q, Ctrl-C Quit
|
||||
?, F1 This help
|
||||
|
||||
BIOS folder: /home/user/.config/retroarch/system
|
||||
|
||||
@@ -1185,4 +1184,5 @@
|
||||
|
||||
|
||||
|
||||
|
||||
Any key closes this help
|
||||
+15
-15
@@ -381,7 +381,7 @@
|
||||
|
||||
RetroArch: ~/.config/retroarch/system
|
||||
|
||||
File list: 5 files.
|
||||
File list received.
|
||||
Reading the files already in the
|
||||
folder... 0:04
|
||||
|
||||
@@ -394,7 +394,7 @@
|
||||
|
||||
RetroArch: ~/.config/retroarch/system
|
||||
|
||||
File list: 5 files.
|
||||
File list received.
|
||||
Reading the files already in the
|
||||
folder... 0:04
|
||||
|
||||
@@ -410,7 +410,7 @@
|
||||
|
||||
RetroArch: ~/.config/retroarch/system
|
||||
|
||||
File list: 5 files.
|
||||
File list received.
|
||||
Reading the files already in the folder... 0:04
|
||||
|
||||
|
||||
@@ -450,8 +450,8 @@
|
||||
BIOS folder: ~/.co.../retroarch/system
|
||||
1 in place, 3 missing, 1 wrong copy
|
||||
Free space: 50.0 GB
|
||||
They are replaced, and the old copies
|
||||
are kept in a backup folder.
|
||||
The 1 wrong copy is replaced, the old
|
||||
one kept in a backup folder.
|
||||
|
||||
> Install
|
||||
Choose systems
|
||||
@@ -466,7 +466,7 @@
|
||||
BIOS folder: ~/.config/retroarch/system
|
||||
1 in place, 3 missing, 1 wrong copy
|
||||
Free space: 50.0 GB
|
||||
They are replaced, and the old copies are kept in a backup folder.
|
||||
The 1 wrong copy is replaced, the old one kept in a backup folder.
|
||||
|
||||
> Install
|
||||
Choose systems
|
||||
@@ -865,6 +865,8 @@
|
||||
=== confirm 40x12
|
||||
RetroBIOS: Confirm
|
||||
|
||||
Counted again for your choices.
|
||||
Nothing is written before Install.
|
||||
1 file to download, 16.0 KB
|
||||
BIOS folder: ~/.co.../retroarch/system
|
||||
0 in place, 1 missing, 0 wrong copies
|
||||
@@ -872,12 +874,12 @@
|
||||
|
||||
> Install
|
||||
Back
|
||||
|
||||
|
||||
A Install B Back
|
||||
=== confirm 40x15
|
||||
RetroBIOS: Confirm
|
||||
|
||||
Counted again for your choices.
|
||||
Nothing is written before Install.
|
||||
1 file to download, 16.0 KB
|
||||
BIOS folder: ~/.co.../retroarch/system
|
||||
0 in place, 1 missing, 0 wrong copies
|
||||
@@ -888,12 +890,11 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
A Install B Back
|
||||
=== confirm 80x24
|
||||
RetroBIOS: Confirm
|
||||
|
||||
Counted again for your choices. Nothing is written before Install.
|
||||
1 file to download, 16.0 KB
|
||||
BIOS folder: ~/.config/retroarch/system
|
||||
0 in place, 1 missing, 0 wrong copies
|
||||
@@ -914,7 +915,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
A Install B Back
|
||||
=== installing 40x12
|
||||
RetroBIOS: Installing
|
||||
@@ -1141,9 +1141,9 @@
|
||||
B, Select, Esc Back
|
||||
q, Ctrl-C Quit
|
||||
Select, then Start Quit at once
|
||||
?, F1 This help
|
||||
|
||||
|
||||
BIOS folder:
|
||||
/home/user/.config/retroarch/system
|
||||
Any key closes this help
|
||||
=== help 40x15
|
||||
RetroBIOS: Keys
|
||||
@@ -1154,12 +1154,12 @@
|
||||
B, Select, Esc Back
|
||||
q, Ctrl-C Quit
|
||||
Select, then Start Quit at once
|
||||
?, F1 This help
|
||||
|
||||
BIOS folder:
|
||||
/home/user/.config/retroarch/system
|
||||
|
||||
|
||||
|
||||
Any key closes this help
|
||||
=== help 80x24
|
||||
RetroBIOS: Keys
|
||||
@@ -1170,7 +1170,6 @@
|
||||
B, Select, Esc Back
|
||||
q, Ctrl-C Quit
|
||||
Select, then Start Quit at once
|
||||
?, F1 This help
|
||||
|
||||
BIOS folder: /home/user/.config/retroarch/system
|
||||
|
||||
@@ -1185,4 +1184,5 @@
|
||||
|
||||
|
||||
|
||||
|
||||
Any key closes this help
|
||||
+15
-15
@@ -381,7 +381,7 @@
|
||||
|
||||
RetroArch: ~/.config/retroarch/system
|
||||
|
||||
File list: 5 files.
|
||||
File list received.
|
||||
Reading the files already in the
|
||||
folder... 0:04
|
||||
|
||||
@@ -394,7 +394,7 @@
|
||||
|
||||
RetroArch: ~/.config/retroarch/system
|
||||
|
||||
File list: 5 files.
|
||||
File list received.
|
||||
Reading the files already in the
|
||||
folder... 0:04
|
||||
|
||||
@@ -410,7 +410,7 @@
|
||||
|
||||
RetroArch: ~/.config/retroarch/system
|
||||
|
||||
File list: 5 files.
|
||||
File list received.
|
||||
Reading the files already in the folder... 0:04
|
||||
|
||||
|
||||
@@ -450,8 +450,8 @@
|
||||
BIOS folder: ~/.co.../retroarch/system
|
||||
1 in place, 3 missing, 1 wrong copy
|
||||
Free space: 50.0 GB
|
||||
They are replaced, and the old copies
|
||||
are kept in a backup folder.
|
||||
The 1 wrong copy is replaced, the old
|
||||
one kept in a backup folder.
|
||||
|
||||
> Install
|
||||
Choose systems
|
||||
@@ -466,7 +466,7 @@
|
||||
BIOS folder: ~/.config/retroarch/system
|
||||
1 in place, 3 missing, 1 wrong copy
|
||||
Free space: 50.0 GB
|
||||
They are replaced, and the old copies are kept in a backup folder.
|
||||
The 1 wrong copy is replaced, the old one kept in a backup folder.
|
||||
|
||||
> Install
|
||||
Choose systems
|
||||
@@ -865,6 +865,8 @@
|
||||
=== confirm 40x12
|
||||
RetroBIOS: Confirm
|
||||
|
||||
Counted again for your choices.
|
||||
Nothing is written before Install.
|
||||
1 file to download, 16.0 KB
|
||||
BIOS folder: ~/.co.../retroarch/system
|
||||
0 in place, 1 missing, 0 wrong copies
|
||||
@@ -872,12 +874,12 @@
|
||||
|
||||
> Install
|
||||
Back
|
||||
|
||||
|
||||
A Install B Back
|
||||
=== confirm 40x15
|
||||
RetroBIOS: Confirm
|
||||
|
||||
Counted again for your choices.
|
||||
Nothing is written before Install.
|
||||
1 file to download, 16.0 KB
|
||||
BIOS folder: ~/.co.../retroarch/system
|
||||
0 in place, 1 missing, 0 wrong copies
|
||||
@@ -888,12 +890,11 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
A Install B Back
|
||||
=== confirm 80x24
|
||||
RetroBIOS: Confirm
|
||||
|
||||
Counted again for your choices. Nothing is written before Install.
|
||||
1 file to download, 16.0 KB
|
||||
BIOS folder: ~/.config/retroarch/system
|
||||
0 in place, 1 missing, 0 wrong copies
|
||||
@@ -914,7 +915,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
A Install B Back
|
||||
=== installing 40x12
|
||||
RetroBIOS: Installing
|
||||
@@ -1141,9 +1141,9 @@
|
||||
B, Select, Esc Back
|
||||
q, Ctrl-C Quit
|
||||
Select, then Start Quit at once
|
||||
?, F1 This help
|
||||
|
||||
|
||||
BIOS folder:
|
||||
/home/user/.config/retroarch/system
|
||||
Any key closes this help
|
||||
=== help 40x15
|
||||
RetroBIOS: Keys
|
||||
@@ -1154,12 +1154,12 @@
|
||||
B, Select, Esc Back
|
||||
q, Ctrl-C Quit
|
||||
Select, then Start Quit at once
|
||||
?, F1 This help
|
||||
|
||||
BIOS folder:
|
||||
/home/user/.config/retroarch/system
|
||||
|
||||
|
||||
|
||||
Any key closes this help
|
||||
=== help 80x24
|
||||
RetroBIOS: Keys
|
||||
@@ -1170,7 +1170,6 @@
|
||||
B, Select, Esc Back
|
||||
q, Ctrl-C Quit
|
||||
Select, then Start Quit at once
|
||||
?, F1 This help
|
||||
|
||||
BIOS folder: /home/user/.config/retroarch/system
|
||||
|
||||
@@ -1185,4 +1184,5 @@
|
||||
|
||||
|
||||
|
||||
|
||||
Any key closes this help
|
||||
Vendored
+11
-11
@@ -327,7 +327,7 @@
|
||||
|
||||
RetroArch: ~/.config/retroarch/system
|
||||
|
||||
File list: 5 files.
|
||||
File list received.
|
||||
Reading the files already in the
|
||||
folder... 0:04
|
||||
|
||||
@@ -340,7 +340,7 @@
|
||||
|
||||
RetroArch: ~/.config/retroarch/system
|
||||
|
||||
File list: 5 files.
|
||||
File list received.
|
||||
Reading the files already in the
|
||||
folder... 0:04
|
||||
|
||||
@@ -356,7 +356,7 @@
|
||||
|
||||
RetroArch: ~/.config/retroarch/system
|
||||
|
||||
File list: 5 files.
|
||||
File list received.
|
||||
Reading the files already in the folder... 0:04
|
||||
|
||||
|
||||
@@ -396,8 +396,8 @@
|
||||
BIOS folder: ~/.co.../retroarch/system
|
||||
1 in place, 3 missing, 1 wrong copy
|
||||
Free space: 50.0 GB
|
||||
They are replaced, and the old copies
|
||||
are kept in a backup folder.
|
||||
The 1 wrong copy is replaced, the old
|
||||
one kept in a backup folder.
|
||||
|
||||
> Install
|
||||
Choose systems
|
||||
@@ -412,7 +412,7 @@
|
||||
BIOS folder: ~/.config/retroarch/system
|
||||
1 in place, 3 missing, 1 wrong copy
|
||||
Free space: 50.0 GB
|
||||
They are replaced, and the old copies are kept in a backup folder.
|
||||
The 1 wrong copy is replaced, the old one kept in a backup folder.
|
||||
|
||||
> Install
|
||||
Choose systems
|
||||
@@ -811,6 +811,8 @@
|
||||
=== confirm 40x12
|
||||
RetroBIOS: Confirm
|
||||
|
||||
Counted again for your choices.
|
||||
Nothing is written before Install.
|
||||
1 file to download, 16.0 KB
|
||||
BIOS folder: ~/.co.../retroarch/system
|
||||
0 in place, 1 missing, 0 wrong copies
|
||||
@@ -818,12 +820,12 @@
|
||||
|
||||
> Install
|
||||
Back
|
||||
|
||||
|
||||
Enter Install Esc Back ? Help
|
||||
=== confirm 40x15
|
||||
RetroBIOS: Confirm
|
||||
|
||||
Counted again for your choices.
|
||||
Nothing is written before Install.
|
||||
1 file to download, 16.0 KB
|
||||
BIOS folder: ~/.co.../retroarch/system
|
||||
0 in place, 1 missing, 0 wrong copies
|
||||
@@ -834,12 +836,11 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Enter Install Esc Back ? Help
|
||||
=== confirm 80x24
|
||||
RetroBIOS: Confirm
|
||||
|
||||
Counted again for your choices. Nothing is written before Install.
|
||||
1 file to download, 16.0 KB
|
||||
BIOS folder: ~/.config/retroarch/system
|
||||
0 in place, 1 missing, 0 wrong copies
|
||||
@@ -860,7 +861,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
Enter Install Esc Back ? Help
|
||||
=== installing 40x12
|
||||
RetroBIOS: Installing
|
||||
|
||||
Vendored
+15
-15
@@ -381,7 +381,7 @@
|
||||
|
||||
RetroArch: ~/.config/retroarch/system
|
||||
|
||||
File list: 5 files.
|
||||
File list received.
|
||||
Reading the files already in the
|
||||
folder... 0:04
|
||||
|
||||
@@ -394,7 +394,7 @@
|
||||
|
||||
RetroArch: ~/.config/retroarch/system
|
||||
|
||||
File list: 5 files.
|
||||
File list received.
|
||||
Reading the files already in the
|
||||
folder... 0:04
|
||||
|
||||
@@ -410,7 +410,7 @@
|
||||
|
||||
RetroArch: ~/.config/retroarch/system
|
||||
|
||||
File list: 5 files.
|
||||
File list received.
|
||||
Reading the files already in the folder... 0:04
|
||||
|
||||
|
||||
@@ -450,8 +450,8 @@
|
||||
BIOS folder: ~/.co.../retroarch/system
|
||||
1 in place, 3 missing, 1 wrong copy
|
||||
Free space: 50.0 GB
|
||||
They are replaced, and the old copies
|
||||
are kept in a backup folder.
|
||||
The 1 wrong copy is replaced, the old
|
||||
one kept in a backup folder.
|
||||
|
||||
> Install
|
||||
Choose systems
|
||||
@@ -466,7 +466,7 @@
|
||||
BIOS folder: ~/.config/retroarch/system
|
||||
1 in place, 3 missing, 1 wrong copy
|
||||
Free space: 50.0 GB
|
||||
They are replaced, and the old copies are kept in a backup folder.
|
||||
The 1 wrong copy is replaced, the old one kept in a backup folder.
|
||||
|
||||
> Install
|
||||
Choose systems
|
||||
@@ -865,6 +865,8 @@
|
||||
=== confirm 40x12
|
||||
RetroBIOS: Confirm
|
||||
|
||||
Counted again for your choices.
|
||||
Nothing is written before Install.
|
||||
1 file to download, 16.0 KB
|
||||
BIOS folder: ~/.co.../retroarch/system
|
||||
0 in place, 1 missing, 0 wrong copies
|
||||
@@ -872,12 +874,12 @@
|
||||
|
||||
> Install
|
||||
Back
|
||||
|
||||
|
||||
A Install R2 Back
|
||||
=== confirm 40x15
|
||||
RetroBIOS: Confirm
|
||||
|
||||
Counted again for your choices.
|
||||
Nothing is written before Install.
|
||||
1 file to download, 16.0 KB
|
||||
BIOS folder: ~/.co.../retroarch/system
|
||||
0 in place, 1 missing, 0 wrong copies
|
||||
@@ -888,12 +890,11 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
A Install R2 Back
|
||||
=== confirm 80x24
|
||||
RetroBIOS: Confirm
|
||||
|
||||
Counted again for your choices. Nothing is written before Install.
|
||||
1 file to download, 16.0 KB
|
||||
BIOS folder: ~/.config/retroarch/system
|
||||
0 in place, 1 missing, 0 wrong copies
|
||||
@@ -914,7 +915,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
A Install R2 Back
|
||||
=== installing 40x12
|
||||
RetroBIOS: Installing
|
||||
@@ -1141,9 +1141,9 @@
|
||||
R2, Esc, Backspace Back
|
||||
q, Ctrl-C Quit
|
||||
B Sends no key here
|
||||
?, F1 This help
|
||||
|
||||
|
||||
BIOS folder:
|
||||
/home/user/.config/retroarch/system
|
||||
Any key closes this help
|
||||
=== help 40x15
|
||||
RetroBIOS: Keys
|
||||
@@ -1154,12 +1154,12 @@
|
||||
R2, Esc, Backspace Back
|
||||
q, Ctrl-C Quit
|
||||
B Sends no key here
|
||||
?, F1 This help
|
||||
|
||||
BIOS folder:
|
||||
/home/user/.config/retroarch/system
|
||||
|
||||
|
||||
|
||||
Any key closes this help
|
||||
=== help 80x24
|
||||
RetroBIOS: Keys
|
||||
@@ -1170,7 +1170,6 @@
|
||||
R2, Esc, Backspace Back
|
||||
q, Ctrl-C Quit
|
||||
B Sends no key here
|
||||
?, F1 This help
|
||||
|
||||
BIOS folder: /home/user/.config/retroarch/system
|
||||
|
||||
@@ -1185,4 +1184,5 @@
|
||||
|
||||
|
||||
|
||||
|
||||
Any key closes this help
|
||||
Vendored
+14
-14
@@ -381,7 +381,7 @@
|
||||
|
||||
RetroArch: ~/.config/retroarch/system
|
||||
|
||||
File list: 5 files.
|
||||
File list received.
|
||||
Reading the files already in the
|
||||
folder... 0:04
|
||||
|
||||
@@ -394,7 +394,7 @@
|
||||
|
||||
RetroArch: ~/.config/retroarch/system
|
||||
|
||||
File list: 5 files.
|
||||
File list received.
|
||||
Reading the files already in the
|
||||
folder... 0:04
|
||||
|
||||
@@ -410,7 +410,7 @@
|
||||
|
||||
RetroArch: ~/.config/retroarch/system
|
||||
|
||||
File list: 5 files.
|
||||
File list received.
|
||||
Reading the files already in the folder... 0:04
|
||||
|
||||
|
||||
@@ -450,8 +450,8 @@
|
||||
BIOS folder: ~/.co.../retroarch/system
|
||||
1 in place, 3 missing, 1 wrong copy
|
||||
Free space: 50.0 GB
|
||||
They are replaced, and the old copies
|
||||
are kept in a backup folder.
|
||||
The 1 wrong copy is replaced, the old
|
||||
one kept in a backup folder.
|
||||
|
||||
> Install
|
||||
Choose systems
|
||||
@@ -466,7 +466,7 @@
|
||||
BIOS folder: ~/.config/retroarch/system
|
||||
1 in place, 3 missing, 1 wrong copy
|
||||
Free space: 50.0 GB
|
||||
They are replaced, and the old copies are kept in a backup folder.
|
||||
The 1 wrong copy is replaced, the old one kept in a backup folder.
|
||||
|
||||
> Install
|
||||
Choose systems
|
||||
@@ -865,6 +865,8 @@
|
||||
=== confirm 40x12
|
||||
RetroBIOS: Confirm
|
||||
|
||||
Counted again for your choices.
|
||||
Nothing is written before Install.
|
||||
1 file to download, 16.0 KB
|
||||
BIOS folder: ~/.co.../retroarch/system
|
||||
0 in place, 1 missing, 0 wrong copies
|
||||
@@ -872,12 +874,12 @@
|
||||
|
||||
> Install
|
||||
Back
|
||||
|
||||
|
||||
Start Install B Back
|
||||
=== confirm 40x15
|
||||
RetroBIOS: Confirm
|
||||
|
||||
Counted again for your choices.
|
||||
Nothing is written before Install.
|
||||
1 file to download, 16.0 KB
|
||||
BIOS folder: ~/.co.../retroarch/system
|
||||
0 in place, 1 missing, 0 wrong copies
|
||||
@@ -888,12 +890,11 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Start Install B Back
|
||||
=== confirm 80x24
|
||||
RetroBIOS: Confirm
|
||||
|
||||
Counted again for your choices. Nothing is written before Install.
|
||||
1 file to download, 16.0 KB
|
||||
BIOS folder: ~/.config/retroarch/system
|
||||
0 in place, 1 missing, 0 wrong copies
|
||||
@@ -914,7 +915,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
Start Install B Back
|
||||
=== installing 40x12
|
||||
RetroBIOS: Installing
|
||||
@@ -1142,7 +1142,7 @@
|
||||
q, Ctrl-C Quit
|
||||
Select Keyboard on or off
|
||||
Menu Close the terminal
|
||||
?, F1 This help
|
||||
|
||||
|
||||
Any key closes this help
|
||||
=== help 40x15
|
||||
@@ -1155,11 +1155,11 @@
|
||||
q, Ctrl-C Quit
|
||||
Select Keyboard on or off
|
||||
Menu Close the terminal
|
||||
?, F1 This help
|
||||
|
||||
BIOS folder:
|
||||
/home/user/.config/retroarch/system
|
||||
|
||||
|
||||
Any key closes this help
|
||||
=== help 80x24
|
||||
RetroBIOS: Keys
|
||||
@@ -1171,7 +1171,6 @@
|
||||
q, Ctrl-C Quit
|
||||
Select Keyboard on or off
|
||||
Menu Close the terminal
|
||||
?, F1 This help
|
||||
|
||||
BIOS folder: /home/user/.config/retroarch/system
|
||||
|
||||
@@ -1185,4 +1184,5 @@
|
||||
|
||||
|
||||
|
||||
|
||||
Any key closes this help
|
||||
@@ -309,6 +309,28 @@ class TheArchives(unittest.TestCase):
|
||||
self.assertEqual(top, [])
|
||||
self.assertIn("RetroBIOS/retrobios.gptk", names)
|
||||
|
||||
def test_the_muos_icon_is_the_glyph_its_header_names(self):
|
||||
"""muOS reads glyph/<ICON>.png in the application's folder, and the
|
||||
muxapp named an icon it did not carry."""
|
||||
header = (REPO_ROOT / "ports" / "muos" / "mux_launch.sh").read_text()
|
||||
icon = re.search(r"^# ICON: (\S+)$", header, re.MULTILINE).group(1)
|
||||
name = build_port.MUOS_APP
|
||||
with zipfile.ZipFile(build_port.build_archive(name, self.out / name)) as zf:
|
||||
data = zf.read(f"RetroBIOS/glyph/{icon}.png")
|
||||
self.assertEqual(data[:8], b"\x89PNG\r\n\x1a\n")
|
||||
|
||||
def test_no_tools_support_file_is_a_menu_entry(self):
|
||||
"""EmulationStation lists a folder only when it holds a file of the
|
||||
system's extension (.sh for Tools and Ports): batocera-emulationstation
|
||||
and EmulationStation-fcamod SystemData.cpp drop a folder with none.
|
||||
The support folders must keep none, or each shows up in the menu."""
|
||||
for name in (build_port.TOOLS_ZIP, build_port.PORT_ZIP, build_port.BATOCERA_ZIP):
|
||||
with self.subTest(archive=name):
|
||||
with zipfile.ZipFile(build_port.build_archive(name, self.out / name)) as zf:
|
||||
inside = [n for n in zf.namelist()
|
||||
if "/" in n and n.lower().endswith(".sh")]
|
||||
self.assertEqual(inside, [])
|
||||
|
||||
def test_each_carries_the_pinned_installer(self):
|
||||
pinned = build_port.pinned_installer_sha256()
|
||||
for name, (_script, folder) in self.LAYOUT.items():
|
||||
@@ -1334,13 +1356,15 @@ class ThePortMasterPort(unittest.TestCase):
|
||||
device.assert_console_restored()
|
||||
|
||||
def test_the_font_is_put_back_without_setfont_o(self):
|
||||
"""busybox setfont has no -O: the font is changed only when the
|
||||
Lat7-Terminus20x10 ArkOS tools restore exists, and that one is put
|
||||
"""busybox setfont has no -O: on ArkOS the font is changed only when
|
||||
the Lat7-Terminus20x10 its tools restore exists, and that one is put
|
||||
back."""
|
||||
device = self.device(FAKE_NO_SAVE="1")
|
||||
fonts = device.env["RB_FONTS"]
|
||||
(Path(fonts) / "Lat7-Terminus20x10.psf.gz").write_bytes(b"")
|
||||
device.run(["bash", self.script])
|
||||
marker = device.root / "text.plymouth"
|
||||
marker.write_text("")
|
||||
device.run(["bash", self.script], RB_ARK_MARKER=str(marker))
|
||||
chosen = f"{fonts}/Lat7-TerminusBold22x11.psf.gz"
|
||||
saved = str(self.gamedir / "run" / "font.psf")
|
||||
self.assertEqual(device.tools(), [
|
||||
@@ -1349,6 +1373,14 @@ class ThePortMasterPort(unittest.TestCase):
|
||||
f"setfont -C {device.console} {fonts}/Lat7-Terminus20x10.psf.gz",
|
||||
])
|
||||
|
||||
def test_elsewhere_a_font_that_cannot_be_saved_is_left(self):
|
||||
"""Lat7-Terminus20x10 is ArkOS's font: put back on another device,
|
||||
it left the console in a font it never had."""
|
||||
device = self.device(FAKE_NO_SAVE="1")
|
||||
(Path(device.env["RB_FONTS"]) / "Lat7-Terminus20x10.psf.gz").write_bytes(b"")
|
||||
device.run(["bash", self.script])
|
||||
self.assertEqual(len([t for t in device.tools() if t.startswith("setfont")]), 1)
|
||||
|
||||
def test_without_the_fallback_font_the_font_is_left(self):
|
||||
device = self.device(FAKE_NO_SAVE="1")
|
||||
device.run(["bash", self.script])
|
||||
|
||||
@@ -2411,6 +2411,10 @@ class ASilentServerIsBounded(unittest.TestCase):
|
||||
self.assertLess(elapsed, 5)
|
||||
self.assertEqual(len(held), install.STALLED_ATTEMPTS)
|
||||
self.assertIn(f"after {install.STALLED_ATTEMPTS} attempts", err.getvalue())
|
||||
# The retry line counts the tries this failure gets: "(2/3)" promised
|
||||
# a third that never came.
|
||||
self.assertIn(f"(2/{install.STALLED_ATTEMPTS})", err.getvalue())
|
||||
self.assertNotIn(f"/{install.MAX_RETRIES})", err.getvalue())
|
||||
# A download keeps its own, longer wait between chunks.
|
||||
self.assertGreater(install.LIST_TIMEOUT, 1)
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ honours Retry-After, and large files that resume where they stopped.
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import errno
|
||||
import hashlib
|
||||
import http.server
|
||||
import io
|
||||
@@ -981,5 +982,247 @@ class WindowsMandatoryLock(unittest.TestCase):
|
||||
self.assertEqual((ok, reason), (False, install.NOT_WRITABLE))
|
||||
|
||||
|
||||
@unittest.skipIf(os.name == "nt" or os.geteuid() == 0,
|
||||
"a folder without search permission is POSIX")
|
||||
class AClosedFolderBehindALink(unittest.TestCase):
|
||||
"""~/Emulation linked into a card at 000: the line named the link's
|
||||
parent, telling the user they cannot open their own HOME. A link
|
||||
that loops was named by its open parent."""
|
||||
|
||||
def setUp(self):
|
||||
self.base = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
|
||||
self.addCleanup(shutil.rmtree, self.base, True)
|
||||
self.home = self.base / "home"
|
||||
self.home.mkdir()
|
||||
self.card = self.base / "card"
|
||||
(self.card / "Emulation" / "bios").mkdir(parents=True)
|
||||
|
||||
def closed(self, path: Path):
|
||||
os.chmod(self.card, 0o000)
|
||||
try:
|
||||
return install.closed_folder(path)
|
||||
finally:
|
||||
os.chmod(self.card, 0o755)
|
||||
|
||||
def test_a_link_into_the_closed_folder_names_that_folder(self):
|
||||
(self.home / "Emulation").symlink_to(self.card / "Emulation")
|
||||
self.assertEqual(self.closed(self.home / "Emulation" / "bios"),
|
||||
(self.card, "Permission denied"))
|
||||
|
||||
def test_a_link_to_the_closed_folder_itself_names_it(self):
|
||||
(self.home / "Emulation").symlink_to(self.card)
|
||||
self.assertEqual(self.closed(self.home / "Emulation" / "Emulation" / "bios"),
|
||||
(self.card, "Permission denied"))
|
||||
|
||||
def test_a_loop_names_the_link(self):
|
||||
loop = self.base / "loop"
|
||||
loop.symlink_to("loop")
|
||||
folder, why = install.closed_folder(loop / "system")
|
||||
self.assertEqual(folder, loop)
|
||||
self.assertEqual(why, os.strerror(errno.ELOOP))
|
||||
|
||||
|
||||
@unittest.skipIf(os.name == "nt" or os.geteuid() == 0,
|
||||
"a folder without search permission is POSIX")
|
||||
class TheQuietConfigReadSaysNothing(unittest.TestCase):
|
||||
"""With --dest the run searches for no setup, and the configs read for
|
||||
the save directory wrote an unsearched event after detect, for a line
|
||||
the human output never showed."""
|
||||
|
||||
def test_no_event_and_no_line(self):
|
||||
base = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
|
||||
self.addCleanup(shutil.rmtree, base, True)
|
||||
closed = base / "home" / ".config" / "EmuDeck"
|
||||
closed.mkdir(parents=True)
|
||||
os.chmod(closed, 0o000)
|
||||
self.addCleanup(os.chmod, closed, 0o755)
|
||||
emitted = []
|
||||
install._DETECTION_REFUSED.clear()
|
||||
self.addCleanup(install._DETECTION_REFUSED.clear)
|
||||
with mock.patch.dict(os.environ, {"HOME": str(base / "home"),
|
||||
"XDG_CONFIG_HOME": str(base / "home" / ".config")}), \
|
||||
mock.patch.object(install, "_CONFIGS_READ", []), \
|
||||
mock.patch.object(install, "_RETROARCH_CONFIGS", {}), \
|
||||
mock.patch.object(install.EVENTS, "emit",
|
||||
lambda kind, **fields: emitted.append(kind)), \
|
||||
contextlib.redirect_stderr(io.StringIO()) as err:
|
||||
install._retroarch_config_for(base / "bios")
|
||||
self.assertNotIn("unsearched", emitted)
|
||||
self.assertEqual(err.getvalue(), "")
|
||||
self.assertEqual(install._DETECTION_REFUSED, [])
|
||||
|
||||
|
||||
@unittest.skipIf(os.name == "nt" or os.geteuid() == 0,
|
||||
"a folder without search permission is POSIX")
|
||||
class NotFoundWithAClosedFolder(unittest.TestCase):
|
||||
"""The not-found line spoke of a default set aside for RetroBat on
|
||||
Linux, which has none, as soon as any folder was closed, and the exit
|
||||
wrote no error event."""
|
||||
|
||||
def run_named(self, platform: str, closed_home: bool) -> tuple:
|
||||
with FakeRepository() as repo:
|
||||
home = repo.root / "home"
|
||||
closed = home / ".config" / "EmuDeck"
|
||||
closed.mkdir(parents=True)
|
||||
env = {**repo.env(), "HOME": str(home), "RETROBIOS_OS": "linux"}
|
||||
if closed_home:
|
||||
os.chmod(closed, 0o000)
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[sys.executable, str(REPO_ROOT / "install.py"), "--platform", platform,
|
||||
"--dry-run", "--no-input", "--events"],
|
||||
env=env, capture_output=True, text=True, timeout=60,
|
||||
stdin=subprocess.DEVNULL)
|
||||
finally:
|
||||
os.chmod(closed, 0o755)
|
||||
events = [json.loads(line) for line in proc.stdout.splitlines()]
|
||||
return proc, events
|
||||
|
||||
def test_a_platform_with_no_folder_here_says_so(self):
|
||||
for closed_home in (False, True):
|
||||
with self.subTest(closed=closed_home):
|
||||
proc, events = self.run_named("retrobat", closed_home)
|
||||
self.assertEqual(proc.returncode, 1, proc.stderr)
|
||||
said = " ".join(proc.stderr.split())
|
||||
self.assertIn("no folder on this system is one it reads", said)
|
||||
self.assertNotIn("its default folder may not be", said)
|
||||
[error] = [e for e in events if e["event"] == "error"]
|
||||
self.assertEqual((error["stage"], error["reason"], error["platform"]),
|
||||
("detect", "not_found", "retrobat"))
|
||||
|
||||
def test_a_default_set_aside_says_so(self):
|
||||
proc, events = self.run_named("retroarch", True)
|
||||
self.assertEqual(proc.returncode, 1, proc.stderr)
|
||||
self.assertIn("its default folder may not be the one it reads",
|
||||
" ".join(proc.stderr.split()))
|
||||
self.assertEqual([e["reason"] for e in events if e["event"] == "error"],
|
||||
["not_found"])
|
||||
|
||||
def test_a_card_system_and_a_portable_one_write_the_event(self):
|
||||
for platform in ("minui", "bizhawk"):
|
||||
with self.subTest(platform=platform):
|
||||
proc, events = self.run_named(platform, False)
|
||||
self.assertEqual(proc.returncode, 1, proc.stderr)
|
||||
self.assertEqual([(e["stage"], e["reason"]) for e in events
|
||||
if e["event"] == "error"], [("detect", "not_found")])
|
||||
|
||||
def test_the_manual_prompt_says_why_there_is_no_default(self):
|
||||
home = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
|
||||
self.addCleanup(shutil.rmtree, home, True)
|
||||
asked = []
|
||||
install._DETECTION_REFUSED[:] = [(home / ".config" / "EmuDeck", "Permission denied")]
|
||||
self.addCleanup(install._DETECTION_REFUSED.clear)
|
||||
with mock.patch.dict(os.environ, {"HOME": str(home)}), \
|
||||
mock.patch.object(install, "_choose_platform", return_value="retroarch"), \
|
||||
mock.patch.object(install, "_ask",
|
||||
lambda prompt: asked.append(prompt) or str(home / "b")):
|
||||
install._prompt_manual_platform("linux")
|
||||
self.assertEqual(len(asked), 1)
|
||||
self.assertNotIn("no default on this system", asked[0])
|
||||
self.assertIn("could not be opened", asked[0])
|
||||
|
||||
|
||||
@unittest.skipIf(os.name == "nt" or os.geteuid() == 0,
|
||||
"a folder without search permission is POSIX")
|
||||
class ADetectedClosedFolderSaysWhereItCameFrom(unittest.TestCase):
|
||||
"""The refusal said "the one detected here" for a folder retroarch.cfg
|
||||
names, and with two setups found the line saying nothing was installed
|
||||
came above the error it continues."""
|
||||
|
||||
def run_detected(self, emudeck: bool) -> subprocess.CompletedProcess:
|
||||
with FakeRepository() as repo:
|
||||
home = repo.root / "home"
|
||||
lock = repo.root / "lock"
|
||||
cfg = home / ".config" / "retroarch" / "retroarch.cfg"
|
||||
cfg.parent.mkdir(parents=True)
|
||||
(lock / "system").mkdir(parents=True)
|
||||
cfg.write_text(f'system_directory = "{lock / "system"}"\n')
|
||||
if emudeck:
|
||||
(home / "Emulation" / "bios").mkdir(parents=True)
|
||||
env = {**repo.env(), "HOME": str(home), "XDG_CONFIG_HOME": str(home / ".config"),
|
||||
"RETROBIOS_OS": "linux"}
|
||||
os.chmod(lock, 0o000)
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[sys.executable, str(REPO_ROOT / "install.py"), "--check", "--no-input"],
|
||||
env=env, capture_output=True, text=True, timeout=60,
|
||||
stdin=subprocess.DEVNULL)
|
||||
finally:
|
||||
os.chmod(lock, 0o755)
|
||||
self.cfg = cfg
|
||||
return proc
|
||||
|
||||
def test_the_config_is_named(self):
|
||||
proc = self.run_detected(False)
|
||||
self.assertEqual(proc.returncode, 1, proc.stdout + proc.stderr)
|
||||
said = " ".join(proc.stderr.split())
|
||||
self.assertIn(f"than the one {self.cfg} names (name another with --dest)", said)
|
||||
|
||||
def test_nothing_installed_comes_after_the_error(self):
|
||||
proc = self.run_detected(True)
|
||||
self.assertEqual(proc.returncode, 1, proc.stdout + proc.stderr)
|
||||
error = proc.stderr.index("Error: ")
|
||||
self.assertGreater(proc.stderr.index("Nothing was installed."), error, proc.stderr)
|
||||
|
||||
def test_an_assumed_default_reads_plainly(self):
|
||||
self.assertIn("used by default, since {display_name(name)} was not found",
|
||||
_source_of(install._install))
|
||||
|
||||
|
||||
class OfflineWithNothingFound(unittest.TestCase):
|
||||
"""An --offline run on a machine with nothing installed ended on "No
|
||||
file list for emudeck", a platform nobody asked about: the standalone
|
||||
folders are named by file lists that run cannot fetch."""
|
||||
|
||||
def test_the_hint_is_given_and_no_other_platform_named(self):
|
||||
with FakeRepository() as repo:
|
||||
home = repo.root / "home"
|
||||
home.mkdir()
|
||||
env = {**repo.env(), "HOME": str(home), "XDG_CONFIG_HOME": str(home / ".config"),
|
||||
"RETROBIOS_OS": "linux"}
|
||||
proc = subprocess.run(
|
||||
[sys.executable, str(REPO_ROOT / "install.py"), "--offline", "--check",
|
||||
"--no-input"],
|
||||
env=env, capture_output=True, text=True, timeout=60,
|
||||
stdin=subprocess.DEVNULL)
|
||||
said = proc.stdout + proc.stderr
|
||||
self.assertEqual(proc.returncode, 1, said)
|
||||
self.assertNotIn("emudeck", said.lower())
|
||||
self.assertIn("Use --platform and --dest", said)
|
||||
|
||||
|
||||
class TheDryRunWords(unittest.TestCase):
|
||||
"""The FAQ's dry run asked "Enter) install everything" before a run
|
||||
that installs nothing, then counted 88 systems where the full screen
|
||||
lists 66: a shared file made a group of its own."""
|
||||
|
||||
def test_the_question_says_nothing_is_written(self):
|
||||
out = io.StringIO()
|
||||
files = [{"dest": "a.bin", "size": 1, "systems": ["x"]}]
|
||||
with mock.patch.object(install, "_ask", return_value=""), \
|
||||
contextlib.redirect_stdout(out):
|
||||
install._prompt_custom_selection("retroarch", files, [], None, "RetroArch",
|
||||
dry_run=True)
|
||||
self.assertNotIn("install everything", out.getvalue())
|
||||
self.assertIn("nothing is written", out.getvalue())
|
||||
|
||||
def test_the_largest_systems_are_counted_as_systems(self):
|
||||
files = [{"dest": f"{n}.bin", "size": n + 1, "systems": [f"s{n}"]}
|
||||
for n in range(12)]
|
||||
files.append({"dest": "shared.bin", "size": 1, "systems": ["s0", "s1"]})
|
||||
out = io.StringIO()
|
||||
with mock.patch.object(install.shutil, "get_terminal_size",
|
||||
return_value=os.terminal_size((80, 10))), \
|
||||
contextlib.redirect_stdout(out):
|
||||
install._print_plan(files, Path("dest"), install.Terminal(io.StringIO()))
|
||||
self.assertIn("12 systems in 13 groups, the largest first", out.getvalue())
|
||||
|
||||
|
||||
def _source_of(function) -> str:
|
||||
import inspect
|
||||
|
||||
return " ".join(inspect.getsource(function).split())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+272
-8
@@ -2535,7 +2535,8 @@ class AuditRoundThree(unittest.TestCase):
|
||||
text = " ".join(" ".join(install.tui_view(state, 120, 40)).split())
|
||||
self.assertIn("Read the first 200,000 files of E:\\.", text)
|
||||
self.assertIn("E:\\D is another drive: add it to copy from it too.", text)
|
||||
self.assertIn("2 files on your old card could not be read.", text)
|
||||
# The folder by its name: "your old card" was said of any folder.
|
||||
self.assertIn("2 files in E:\\ could not be read.", text)
|
||||
rows = install.tui_rows(state)
|
||||
state.focus["plan"] = next(i for i, row in enumerate(rows) if row.action == "unreadable")
|
||||
press(state, "enter")
|
||||
@@ -2642,10 +2643,11 @@ class IdleExit(unittest.TestCase):
|
||||
self.assertEqual(farewell[:2], ["RetroBIOS: No button pressed, closing.",
|
||||
"RetroBIOS: Finished with errors"])
|
||||
|
||||
def test_any_other_screen_closes_with_0(self):
|
||||
def test_any_other_screen_closes_as_a_quit(self):
|
||||
"""0 read as "everything asked for is installed" with nothing done."""
|
||||
state = fresh_state()
|
||||
state.stack = ["setup"]
|
||||
self.assertEqual(self.idle_close(state), 0)
|
||||
self.assertEqual(self.idle_close(state), 130)
|
||||
|
||||
def idle_close(self, state: install.TuiState) -> int:
|
||||
"""run_tui on state, no key ever pressed, closed by the idle timer."""
|
||||
@@ -2719,13 +2721,47 @@ class AuditRoundFour(unittest.TestCase):
|
||||
"free": 50 * 1024 ** 3, "writable": True, "cores": []}):
|
||||
install.tui_receive(state, ("ended", state.run_id, 1, []))
|
||||
line = ("2 files read from RetroArch's save directory left out: its place is "
|
||||
"not known. Name it with --save-dir.")
|
||||
"not known. ")
|
||||
plan = " ".join(" ".join(install.tui_view(state, 120, 40)).split())
|
||||
self.assertIn(line, plan)
|
||||
# The plan names the row that sets it, the result the option a
|
||||
# command carries.
|
||||
self.assertIn(line + "Choose it in Options, Save folder.", plan)
|
||||
state.stack.append("result")
|
||||
state.code = 0
|
||||
state.summary = {**summary, "mode": "install", "installed": 5}
|
||||
self.assertIn(line, install.tui_outcome(state)[1])
|
||||
self.assertIn(line + "Name it with --save-dir.", install.tui_outcome(state)[1])
|
||||
|
||||
|
||||
class TheSaveFolderHasARow(unittest.TestCase):
|
||||
"""A card on a PC left the save-directory files out and named
|
||||
--save-dir, which Options had no row for: the interface user had to
|
||||
leave it and type a command."""
|
||||
|
||||
def state(self) -> install.TuiState:
|
||||
state = checked_state()
|
||||
state.manifest = {**MANIFEST, "save_directory": {"folder": "saves", "by_core": True}}
|
||||
return state
|
||||
|
||||
def test_options_list_it_and_the_browser_sets_it(self):
|
||||
state = self.state()
|
||||
rows = install._option_rows(state)
|
||||
[row] = [row for row in rows if row.action == "savedir"]
|
||||
self.assertEqual((row.label, row.right), ("Save folder: not known", "--save-dir"))
|
||||
saves = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
|
||||
self.addCleanup(shutil.rmtree, saves, True)
|
||||
state.stack = ["setup", "plan", "options"]
|
||||
install._option(state, "savedir", 0)
|
||||
self.assertEqual(state.screen, "browse")
|
||||
self.assertIn("save directory", " ".join(" ".join(install.tui_view(state, 120, 40))
|
||||
.split()))
|
||||
install._browse_to(state, saves)
|
||||
effects = install._activate(state, install.TuiRow("use", "Use this folder"))
|
||||
self.assertIn(("--save-dir", str(saves)), install.tui_flags(state))
|
||||
self.assertTrue(any(effect[0] == "run" for effect in effects), effects)
|
||||
self.assertEqual(install._save_folder_words(state), str(saves))
|
||||
|
||||
def test_a_list_without_a_save_directory_has_no_row(self):
|
||||
self.assertNotIn("savedir", [row.action for row in install._option_rows(checked_state())])
|
||||
|
||||
|
||||
class OfflineInTheInterface(unittest.TestCase):
|
||||
@@ -2807,6 +2843,35 @@ class NarrowSystemNames(unittest.TestCase):
|
||||
self.assertIn("3 files, 30.0 MB + 27 shared",
|
||||
"\n".join(install.tui_view(state, 120, 40)))
|
||||
|
||||
def test_one_spelling_of_shared_and_the_focused_size_below(self):
|
||||
"""At 40 columns the shared part read "+ 1689 shared", "+ 28 sh.",
|
||||
", shared" and ", all shared" on one screen, and a long name kept
|
||||
whole lost its size."""
|
||||
state = checked_state()
|
||||
names = {"epoch-scv": "Epoch Super Cassette Vision", "sega-dc": "Sega Dreamcast",
|
||||
"snk-ng": "SNK Neo Geo"}
|
||||
state.manifest = {**state.manifest, "system_titles": names}
|
||||
state.systems = [
|
||||
{"event": "system", "platform": "retroarch", "system": system, "name": system,
|
||||
"files": count, "present": 0, "missing": count, "wrong_hash": 0,
|
||||
"bytes_needed": count * 1024}
|
||||
for system, count in (("epoch-scv", 30), ("sega-dc", 1690), ("snk-ng", 3))]
|
||||
state.files = [{"event": "file", "platform": "retroarch", "dest": f"s{n}.bin",
|
||||
"systems": ["sega-dc", "snk-ng"], "size": 1024, "state": "missing"}
|
||||
for n in range(3)]
|
||||
state.ticked = set(names)
|
||||
state.stack.append("systems")
|
||||
spellings = set()
|
||||
for cols in (40, 46, 52, 80):
|
||||
text = "\n".join(install.tui_view(state, cols, 15))
|
||||
spellings |= set(re.findall(r"\+ \d+ sh\w*\.?|, (?:all )?shared", text))
|
||||
self.assertLessEqual(spellings, {"+ 3 shared", ", all shared"}, spellings)
|
||||
state.focus["systems"] = next(
|
||||
index for index, row in enumerate(install.tui_rows(state))
|
||||
if row.value == "epoch-scv")
|
||||
text = " ".join(" ".join(install.tui_view(state, 40, 15)).split())
|
||||
self.assertIn("Epoch Super Cassette Vision: 30 files, 30.0 KB", text)
|
||||
|
||||
|
||||
class Crash(unittest.TestCase):
|
||||
"""An unexpected error ends on one sentence and a log, never a traceback."""
|
||||
@@ -3896,7 +3961,7 @@ class EndToEnd(unittest.TestCase):
|
||||
self.env[install.TUI_IDLE_ENV] = "1"
|
||||
term = self.start()
|
||||
term.wait("Where do you play?")
|
||||
self.assertEqual(term.close(), 0)
|
||||
self.assertEqual(term.close(), 130)
|
||||
self.assertIn("No button pressed, closing.", term.after())
|
||||
# A key pressed, the screen stays: the person is there.
|
||||
term = self.start()
|
||||
@@ -3917,7 +3982,7 @@ class EndToEnd(unittest.TestCase):
|
||||
self.env[install.TUI_IDLE_ENV] = "1"
|
||||
term = self.start("--platform", "retroarch", "--dest", str(repo.dest))
|
||||
term.wait("> Install")
|
||||
self.assertEqual(term.close(), 0)
|
||||
self.assertEqual(term.close(), 130)
|
||||
self.assertIn("No button pressed, closing.", term.after())
|
||||
self.assertEqual(repo.installed(), [])
|
||||
|
||||
@@ -3976,5 +4041,204 @@ class EndToEnd(unittest.TestCase):
|
||||
self.assertNotIn(b"\x1b", term.raw)
|
||||
|
||||
|
||||
class TheFolderScreenSaysWhatItOffers(unittest.TestCase):
|
||||
"""The folder screen said "usually reads its BIOS files from" for any
|
||||
folder it offered: the folder named with --dest, reached by Change
|
||||
folder, and the built-in default while the retroarch.cfg found here
|
||||
names another. A closed folder found here opened on "No usual
|
||||
RetroArch folder here" under the setup that listed it."""
|
||||
|
||||
def said(self, state: install.TuiState) -> str:
|
||||
return " ".join(" ".join(install.tui_view(state, 200, 40)).split())
|
||||
|
||||
def test_change_folder_names_the_chosen_folder(self):
|
||||
state = checked_state()
|
||||
state.stack = ["setup", "plan"]
|
||||
mine = Path("/home/user/typed/mybios")
|
||||
state.dest = mine
|
||||
install._activate(state, install.TuiRow("folder", "Change folder"))
|
||||
self.assertEqual(state.screen, "folder")
|
||||
screen = self.said(state)
|
||||
self.assertNotIn("usually reads", screen)
|
||||
self.assertIn(f"The BIOS folder chosen for RetroArch: {mine}", screen)
|
||||
|
||||
def test_another_setup_offers_the_folder_found_here(self):
|
||||
state = fresh_state()
|
||||
found = Path("/home/user/Games/bios")
|
||||
state.setups = [("retroarch", found, "RetroArch")]
|
||||
state.stack = ["setup", "platforms"]
|
||||
install._activate(state, install.TuiRow("platform", "RetroArch", value="retroarch"))
|
||||
self.assertEqual(state.folder_default, found)
|
||||
self.assertIn(f"RetroArch reads its BIOS files from: {found}", self.said(state))
|
||||
|
||||
def test_with_none_found_the_usual_folder_is_offered(self):
|
||||
state = fresh_state()
|
||||
state.setups = []
|
||||
state.stack = ["setup", "platforms"]
|
||||
install._activate(state, install.TuiRow("platform", "RetroArch", value="retroarch"))
|
||||
self.assertIn("RetroArch usually reads its BIOS files from:", self.said(state))
|
||||
|
||||
def test_a_closed_folder_found_here_asks_for_another(self):
|
||||
state = fresh_state()
|
||||
state.stack = ["setup"]
|
||||
with mock.patch.object(install, "closed_folder",
|
||||
return_value=(Path("/home/user/lock"), "Permission denied")):
|
||||
install.tui_update(state, Key("enter"))
|
||||
self.assertEqual(state.screen, "folder")
|
||||
screen = self.said(state)
|
||||
self.assertNotIn("No usual RetroArch folder here", screen)
|
||||
self.assertIn("Choose another folder for RetroArch", screen)
|
||||
|
||||
def test_a_refused_folder_puts_browse_under_a(self):
|
||||
for facts in ({"free": 10, "writable": True, "cores": []},
|
||||
{"free": 50 * 1024 ** 3, "writable": False, "cores": []}):
|
||||
with self.subTest(facts=facts):
|
||||
state = checked_state(facts)
|
||||
self.assertEqual(install.tui_rows(state)[0].action, "folder")
|
||||
install.tui_update(state, Key("enter"))
|
||||
self.assertEqual(state.screen, "folder")
|
||||
rows = install.tui_rows(state)
|
||||
self.assertEqual(rows[state.focus["folder"]].action, "browse")
|
||||
|
||||
|
||||
class ThePlanNumbersAddUp(unittest.TestCase):
|
||||
""""47 files to download" sat above "49 missing, 1 wrong copy": the 3
|
||||
found on this machine were the difference, left to the reader."""
|
||||
|
||||
def test_the_copied_files_are_the_difference(self):
|
||||
state = checked_state()
|
||||
state.found = [found("scph5500.bin", str(SYSTEM_DIR), "one folder too deep")]
|
||||
screen = " ".join(" ".join(install.tui_view(state, 120, 40)).split())
|
||||
self.assertIn("3 files to download", screen)
|
||||
self.assertIn("1 in place, 3 missing, 1 wrong copy: 1 of them is copied from this "
|
||||
"machine, 3 downloaded", screen)
|
||||
|
||||
|
||||
class GameDataIsNoFirmwareAlarm(unittest.TestCase):
|
||||
"""Every RetroArch plan named jazz2/Source/Anims.j2a (jazz-jackrabbit-2)
|
||||
as the file needed to start an emulator: a game's data, under a raw id."""
|
||||
|
||||
def test_game_data_has_its_own_line(self):
|
||||
state = checked_state()
|
||||
state.systems = []
|
||||
state.manifest = {**MANIFEST, "omitted_files": [
|
||||
{"dest": "jazz2/Source/Anims.j2a", "system": "jazz-jackrabbit-2",
|
||||
"required": True, "category": "game_data"},
|
||||
{"dest": "missing.bin", "system": "sony-playstation", "required": True}]}
|
||||
lines = install._unavailable_lines(state)
|
||||
self.assertEqual(len(lines), 2, lines)
|
||||
self.assertIn("needed to start its emulator: missing.bin", lines[0])
|
||||
self.assertNotIn("Anims.j2a", lines[0])
|
||||
self.assertIn("Game data not available here", lines[1])
|
||||
self.assertIn("Anims.j2a (Jazz Jackrabbit 2)", lines[1])
|
||||
|
||||
|
||||
class TheHelpNamesKeysThatCome(unittest.TestCase):
|
||||
"""Every route's help listed "?, F1 This help", which no pad sends, and
|
||||
a key as wide as the column touched its text."""
|
||||
|
||||
def test_pad_routes_name_no_help_key(self):
|
||||
for pad in ROUTES:
|
||||
with self.subTest(route=pad):
|
||||
state = checked_state()
|
||||
state.pad = pad
|
||||
keys = [key for key, _what in install._help_lines(state)]
|
||||
self.assertEqual("?, F1" in keys, pad == "keys")
|
||||
|
||||
def test_a_wide_key_keeps_two_spaces(self):
|
||||
state = checked_state()
|
||||
state.help = True
|
||||
with mock.patch.object(install, "_help_lines",
|
||||
return_value=[("A, Start, Enter, Space, x", "Choose"),
|
||||
("q", "Quit")]):
|
||||
lines = install._view_help(state, 80, 24)[1]
|
||||
self.assertTrue(any("A, Start, Enter, Space, x Choose" in line for line in lines))
|
||||
|
||||
|
||||
class CopyFromStartsInDownloads(unittest.TestCase):
|
||||
"""A ZIP in Downloads took 18 presses from the first screen: the copy
|
||||
browser opened at HOME, under every mounted drive."""
|
||||
|
||||
def test_a_zip_in_downloads_opens_there(self):
|
||||
home = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
|
||||
self.addCleanup(shutil.rmtree, home, True)
|
||||
(home / "Downloads").mkdir()
|
||||
state = checked_state()
|
||||
state.home = home
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[]):
|
||||
install._open_from_browser(state)
|
||||
self.assertEqual(state.browse_dir, home)
|
||||
state.stack.pop()
|
||||
(home / "Downloads" / "PSX BIOS.zip").write_bytes(b"")
|
||||
install._open_from_browser(state)
|
||||
self.assertEqual(state.browse_dir, home / "Downloads")
|
||||
|
||||
|
||||
class NothingFoundIsSaid(unittest.TestCase):
|
||||
"""With nothing detected the first screen was the list of 18 setups,
|
||||
with no line at 40x15 saying nothing was found, which the beginner's
|
||||
page names."""
|
||||
|
||||
def test_at_every_size(self):
|
||||
with mock.patch.object(install, "detect_platforms", return_value=[]), \
|
||||
mock.patch.object(install, "detect_frontends", return_value=[]), \
|
||||
mock.patch.object(install, "_mounted_cards", return_value=[]):
|
||||
state, _effects = install.tui_start(_args(), "linux")
|
||||
self.assertEqual(state.screen, "platforms")
|
||||
for cols, rows in ((40, 12), (40, 15), (80, 24)):
|
||||
with self.subTest(size=f"{cols}x{rows}"):
|
||||
screen = " ".join(" ".join(install.tui_view(state, cols, rows)).split())
|
||||
self.assertIn("Nothing was found here", screen)
|
||||
page = (REPO_ROOT / "wiki" / "first-install.md").read_text(encoding="utf-8")
|
||||
self.assertIn("Nothing was found here", page)
|
||||
|
||||
|
||||
class AFocusesNoSystemFolder(unittest.TestCase):
|
||||
"""At / A went into bin/, and at a drive root holding $RECYCLE.BIN into
|
||||
the recycle bin, which Use this folder then took as the BIOS folder."""
|
||||
|
||||
def setUp(self):
|
||||
self.home = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
|
||||
self.addCleanup(shutil.rmtree, self.home, True)
|
||||
self.state = install.TuiState(os_type="linux", home=self.home)
|
||||
self.state.platform = "retroarch"
|
||||
self.state.stack = ["folder", "browse"]
|
||||
|
||||
def focused(self) -> install.TuiRow:
|
||||
return self.state.browse_rows[self.state.focus["browse"]]
|
||||
|
||||
def test_the_recycle_bin_and_restore_points_are_passed_over(self):
|
||||
for name in ("$RECYCLE.BIN", "System Volume Information", "Games"):
|
||||
(self.home / name).mkdir()
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[]):
|
||||
install._browse_to(self.state, self.home)
|
||||
self.assertEqual(self.focused().label, "Games/")
|
||||
|
||||
def test_explorer_hidden_folders_are_passed_over(self):
|
||||
for name in ("Boot", "Games"):
|
||||
(self.home / name).mkdir()
|
||||
real_stat = os.stat
|
||||
|
||||
def stat(path, *args, **kwargs):
|
||||
result = real_stat(path, *args, **kwargs)
|
||||
hidden = 0x2 | 0x4 if Path(path).name == "Boot" else 0
|
||||
return types.SimpleNamespace(st_file_attributes=hidden, st_mode=result.st_mode)
|
||||
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[]), \
|
||||
mock.patch.object(install.os, "stat", stat):
|
||||
install._browse_to(self.state, self.home)
|
||||
self.assertEqual(self.focused().label, "Games/")
|
||||
|
||||
def test_at_the_root_no_system_folder_is_focused(self):
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[]):
|
||||
install._browse_to(self.state, Path("/"))
|
||||
row = self.focused()
|
||||
if row.action == "dir":
|
||||
self.assertNotIn(row.value.name, install._POSIX_ROOT_SYSTEM)
|
||||
names = {r.value.name for r in self.state.browse_rows if r.action == "dir"}
|
||||
if "bin" in names and names - install._POSIX_ROOT_SYSTEM - {""}:
|
||||
self.assertEqual(row.action, "dir")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -134,7 +134,8 @@ The result says how to restore them.
|
||||
|
||||
### Nothing was found here.
|
||||
|
||||
Choose **Another setup or folder**, then your setup and its folder.
|
||||
Choose your setup in the list, then its folder. Where the screen offers
|
||||
**Another setup or folder**, choose it first.
|
||||
|
||||
### Error: Python 3.8 or newer is required.
|
||||
|
||||
|
||||
+18
-20
@@ -133,7 +133,8 @@ much space is free, before anything is written.
|
||||
The other choices sit one press away. **Choose systems** ticks the consoles you
|
||||
play, with the total to download under the list. **Options** sets the
|
||||
preferred regions, game data, the installed cores only, the cores, the parallel
|
||||
downloads and the folder. Each row names the option it sets, and **Show the
|
||||
downloads, the folder and, for a list with files read from the frontend's save
|
||||
directory, that directory. Each row names the option it sets, and **Show the
|
||||
command line** gives the same choice as one command for a script. **Preview the
|
||||
file list** shows what would be downloaded and writes nothing. **Copy the BIOS
|
||||
files from your old card or a folder** opens the folder browser, mounted cards
|
||||
@@ -1077,9 +1078,15 @@ moves with the D-pad.
|
||||
|
||||
Each launcher also sets `RETROBIOS_IDLE_EXIT` to 300: with no button pressed
|
||||
for 5 minutes since it opened, and no check or install running, the interface
|
||||
closes by itself. A pad the
|
||||
system has no mapping for sends nothing, and would otherwise leave the
|
||||
console stuck on the interface with no way out.
|
||||
closes by itself. A pad the system has no mapping for sends nothing, and would
|
||||
otherwise leave the console stuck on the interface with no way out. It closes
|
||||
with `RetroBIOS: No button pressed, closing.` and exit status 130, as a quit
|
||||
before the install. Closed on an error or a result screen, it prints that
|
||||
screen's words after the line and exits with its code (1 for `No internet:
|
||||
offline mode`), so the launcher can tell an error from a close with nothing to
|
||||
report. The time counts from the end of the last check, so a launcher that
|
||||
names the platform, whose interface moves on to the plan by itself, closes the
|
||||
same way. Any launcher can set it to another number of seconds.
|
||||
|
||||
Each launcher tells the interface which buttons reach it, in
|
||||
`RETROBIOS_PAD`, so the hints can name them:
|
||||
@@ -1093,19 +1100,10 @@ Each launcher tells the interface which buttons reach it, in
|
||||
| `miyoo` | buttons that are already keys: A as Space, Start as Enter, L1 and R1 as E and T, L2 as Tab, R2 as Backspace | no launcher yet |
|
||||
|
||||
A launcher with no mapper at all (a keyboard) leaves it unset.
|
||||
A launcher can also set `RETROBIOS_IDLE_EXIT` to a number of seconds: when
|
||||
no button has reached the interface since it opened, and nothing has run for
|
||||
that long, it closes with `RetroBIOS: No button pressed, closing.` and exit status 0.
|
||||
Closed on an error or a result screen, it prints that screen's words after
|
||||
the line and exits with its code (1 for `No internet: offline mode`), so
|
||||
the launcher can tell an error from a close with nothing to report.
|
||||
The time counts from the end of the last check, so a launcher that names the
|
||||
platform, whose interface moves on to the plan by itself, closes the same way.
|
||||
A mapper that never opened the pad would otherwise hold the device on that
|
||||
screen. On a Miyoo, B,
|
||||
X, Y and Select are Left Ctrl, Shift, Alt and Right Ctrl: pressed alone, a
|
||||
modifier sends nothing to a terminal, so the interface goes back with R2
|
||||
instead, and the bottom line says so.
|
||||
|
||||
On a Miyoo, B, X, Y and Select are Left Ctrl, Shift, Alt and Right Ctrl:
|
||||
pressed alone, a modifier sends nothing to a terminal, so the interface goes
|
||||
back with R2 instead, and the bottom line says so.
|
||||
|
||||
**Without Wi-Fi.** On a computer, download the BIOS pack that serves your
|
||||
system (every part, for a pack in parts): RetroArch's on muOS, ArkOS,
|
||||
@@ -1286,7 +1284,7 @@ fields, the installer refuses to narrow rather than install everything.
|
||||
| `0` | everything asked for is installed or already up to date |
|
||||
| `1` | a failure: the one-liner could not download or verify `install.py`, or found no Python 3.8 (nothing was run), an unusable base URL, an unknown platform or target, `--installed-cores` with no core found, a manifest that fails validation, a fetch that never succeeded, no platform to install for in a non-interactive run, some files failed while others installed, `--check` found files to fetch, a `--from-pack` pack that cannot be read, lacks a part or is for another platform, a `--from` folder that does not exist or cannot be read, `--restore` could not put a file back, or `--add-app` could not put every file of the app on the card |
|
||||
| `2` | nothing was installed: every file the run tried failed (a BIOS folder that cannot be written, for one), the drive has too little free space for the download (checked before any folder is created), or the question about a folder that does not exist, named with `--dest` or typed at the folder question, was answered no. Also an option the installer refused before any download: an unknown option, `--jobs` outside 1 to 32, `--target` with `--every-board` or `--from-pack`, `--target` or `--list-targets` with `--offline` (or `RETROBIOS_INSTALL_OFFLINE` set), which read a core list from the network, `--add-app` with `--check`, `--dry-run`, a listing, `--from-pack` or `--offline`. The message then starts with `install.py: error:`. Also `--add-app` for a folder on no card that takes the app, said in an `Error:` line and an `app` event before anything is written. With `--restore`, a folder that is not a dated folder inside `retrobios-backup` |
|
||||
| `130` | stopped with Ctrl+C, or with `q`, Ctrl+C or Ctrl+D at a question. Run the same command again to carry on |
|
||||
| `130` | stopped with Ctrl+C, or with `q`, Ctrl+C or Ctrl+D at a question, or the full screen closed with no button pressed (`RETROBIOS_IDLE_EXIT`) before an install. Run the same command again to carry on |
|
||||
| `141` | the output was piped to a reader that closed early (`--list-platforms \| head`) |
|
||||
|
||||
The full screen ended by SIGTERM (Start+Select on a handheld) before any
|
||||
@@ -1327,7 +1325,7 @@ other schemas under `api/v1/schemas/`.
|
||||
| `start` | `schema`, `installer`, `mode` | First line. `schema` is 1, `installer` the identity `--version` prints, `mode` one of `install`, `check`, `dry-run`, `list` |
|
||||
| `unsearched` | `folder`, `reason`, `message` | Before `detect`, once for each folder the search for setups could not look in, this user being refused it. What it would hold counts as absent, and the other places are still searched: `detect` then lists what they hold, and an empty `platforms` after an `unsearched` line means nothing was found where the installer could look. `reason` says why in words (`Permission denied`), `message` says it in a sentence |
|
||||
| `detect` | `os`, `os_name`, `platforms` | Once the platforms are known. `os_name` comes from `/etc/os-release`. `platforms` lists `{platform, name, bios_dir}`, and is empty when nothing supported was found |
|
||||
| `error` | `stage`, `reason`, `platform`, `message` | The file list could not be read (`stage` is `manifest`): `reason` is `network`, `not_found` or `invalid`, `clock` when the server's certificate was refused and the system clock reads a date before the installer was written, `no_ca` when the machine holds no list of trusted certificates (`SSL_CERT_FILE` names one), or `offline` when `--offline` forbids the request. A folder named with `--from` cannot be read (`stage` is `from`, `reason` `not_found`, `not_a_folder` for a file that is no ZIP, `unreadable`, or `system` for the root of the system, `/proc`, `/sys` or `/dev`). The BIOS folder sits under a folder this user cannot open (`stage` is `dest`, `reason` `unreadable`), and the run ends with code 1 before anything is written. Two systems are detected on one machine and no `--dest` names the folder (`stage` is `detect`, `reason` `conflict`). `message` says it in words |
|
||||
| `error` | `stage`, `reason`, `platform`, `message` | The file list could not be read (`stage` is `manifest`): `reason` is `network`, `not_found` or `invalid`, `clock` when the server's certificate was refused and the system clock reads a date before the installer was written, `no_ca` when the machine holds no list of trusted certificates (`SSL_CERT_FILE` names one), or `offline` when `--offline` forbids the request. A folder named with `--from` cannot be read (`stage` is `from`, `reason` `not_found`, `not_a_folder` for a file that is no ZIP, `unreadable`, or `system` for the root of the system, `/proc`, `/sys` or `/dev`). The BIOS folder sits under a folder this user cannot open (`stage` is `dest`, `reason` `unreadable`), and the run ends with code 1 before anything is written. Two systems are detected on one machine and no `--dest` names the folder (`stage` is `detect`, `reason` `conflict`). The platform named with `--platform` is found nowhere and no folder can be assumed for it: a card system with no card named, a portable emulator, or no usual folder on this system or one set aside behind a folder this user cannot open (`stage` is `detect`, `reason` `not_found`). `message` says it in words |
|
||||
| `scanned` | `folder`, `files`, `truncated`, `other_drives` | Each folder named with `--from`, once read and before `detect`. `files` counts the files found, `truncated` is true when the search stopped at 200,000 of them, and `other_drives` lists the folders below it on another drive, which were not entered |
|
||||
| `checking` | `files_done`, `files_total` | While the files already in the folder are read and hashed, at most every half second and once at the end. `files_total` counts the files found in place |
|
||||
| `file` | `platform`, `dest`, `systems`, `size`, `state` | Each file of the list after the check. `state` is `present` (hash verified), `wrong_hash` or `missing` |
|
||||
@@ -1380,7 +1378,7 @@ RetroArch folder that holds only `scph5501.bin`, in the order written:
|
||||
| `SSL_CERT_FILE`, `SSL_CERT_DIR` | The trusted certificates to check servers with, for a Python that finds none of its own |
|
||||
| `RETROBIOS_PLAIN`, `ACCESSIBLE` | Set to anything but `0`: the questions come line by line, as with `--plain`, instead of the full-screen interface |
|
||||
| `RETROBIOS_PAD` | The buttons a handheld launcher's mapper sends to the full screen (`gptokeyb`, `gptokeyb_muxterm`, `muxterm`, `evmapy`, `miyoo`), so its hints name them. See [On a handheld](#on-a-handheld-the-retrobios-port) |
|
||||
| `RETROBIOS_IDLE_EXIT` | Seconds the full screen waits for a first button, counted while nothing runs. With none pressed, it closes with exit status 0, or with the code of the error or result screen it closed on |
|
||||
| `RETROBIOS_IDLE_EXIT` | Seconds the full screen waits for a first button, counted while nothing runs. With none pressed, it closes with exit status 130, or with the code of the error or result screen it closed on |
|
||||
| `HTTPS_PROXY`, `NO_PROXY` | Honoured for every download through the standard library's default proxy handling: one names the proxy, the other the hosts to reach directly |
|
||||
|
||||
## When something goes wrong
|
||||
|
||||
Reference in new issue
Block a user