chore: merge the removal and backups from main

This commit is contained in:
Abdessamad Derraz committed 2026-11-11 15:35:44 +01:00
commit f218998f59
51 files changed
+5727 -110

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@
# and Invoke-WebRequest throws on a cut transfer before the hash is read:
# the site's build rewrites this line there (scripts/sitebootstrap.py).
$mismatchAdvice = "The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that."
$defaultInstallSha256 = "8c8ead297c502f40f02f4407bdbb3edcb2af31b68f0a3fca06ed963c6cbc3cb0"
$defaultInstallSha256 = "300f5f9f82f21af68582b1cbd6cd1512883a2ce7fc9185dd04537b91f11b5f48"
# The pin above refuses any changed byte: this cap only stops a body that
# cannot be the installer (a sign-in portal's page) before it is hashed.
$maximumInstallerBytes = 8MB
+1456 -49
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -23,7 +23,7 @@ DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/ins
# curl or wget stop on a cut transfer before the hash is read: the site's
# build rewrites this line there (scripts/sitebootstrap.py).
MISMATCH_ADVICE="The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that."
DEFAULT_INSTALL_SHA256="8c8ead297c502f40f02f4407bdbb3edcb2af31b68f0a3fca06ed963c6cbc3cb0"
DEFAULT_INSTALL_SHA256="300f5f9f82f21af68582b1cbd6cd1512883a2ce7fc9185dd04537b91f11b5f48"
# The pin above refuses any changed byte: this cap only stops a body that
# cannot be the installer (a sign-in portal's page) before it is hashed.
MAX_INSTALLER_BYTES=8388608
+8 -8
View File
@@ -27,8 +27,8 @@
{
"dest": "roms/ports/retrobios/install.py",
"repo_path": "install.py",
"size": 1222786,
"sha256": "8c8ead297c502f40f02f4407bdbb3edcb2af31b68f0a3fca06ed963c6cbc3cb0",
"size": 1287576,
"sha256": "300f5f9f82f21af68582b1cbd6cd1512883a2ce7fc9185dd04537b91f11b5f48",
"executable": false
},
{
@@ -71,8 +71,8 @@
"muos": {
"archive": {
"dest": "ARCHIVE/RetroBIOS.muxapp",
"size": 1276411,
"sha256": "8008566a12fc1d18a19d9510c1884919d60ede9eb227bef25973082ac1b204e3"
"size": 1341201,
"sha256": "e03a69b28579955aac4970ece642996c5d81dead48fcbd7fef363b5179ba99a7"
},
"members": [
{
@@ -92,8 +92,8 @@
{
"name": "RetroBIOS/install.py",
"repo_path": "install.py",
"size": 1222786,
"sha256": "8c8ead297c502f40f02f4407bdbb3edcb2af31b68f0a3fca06ed963c6cbc3cb0",
"size": 1287576,
"sha256": "300f5f9f82f21af68582b1cbd6cd1512883a2ce7fc9185dd04537b91f11b5f48",
"executable": false
},
{
@@ -166,8 +166,8 @@
{
"dest": ".config/modules/retrobios/install.py",
"repo_path": "install.py",
"size": 1222786,
"sha256": "8c8ead297c502f40f02f4407bdbb3edcb2af31b68f0a3fca06ed963c6cbc3cb0",
"size": 1287576,
"sha256": "300f5f9f82f21af68582b1cbd6cd1512883a2ce7fc9185dd04537b91f11b5f48",
"executable": false
},
{
-3
View File
@@ -8,8 +8,6 @@ install.py:0: error: "object" has no attribute "get" [attr-defined]
install.py:0: error: "object" has no attribute "get" [attr-defined]
install.py:0: error: "object" object is not iterable [misc]
install.py:0: error: "object" object is not iterable [misc]
install.py:0: error: "str" has no attribute "append" [attr-defined]
install.py:0: error: "str" has no attribute "append" [attr-defined]
install.py:0: error: Argument 1 to "_ZippedLookup" has incompatible type "Optional[FolderIndex]"; expected "FolderIndex" [arg-type]
install.py:0: error: Argument 1 to "__iadd__" of "list" has incompatible type "Invocation"; expected "Iterable[str]" [arg-type]
install.py:0: error: Argument 1 to "_digest_file" has incompatible type "Optional[Path]"; expected "Path" [arg-type]
@@ -118,7 +116,6 @@ install.py:0: error: Incompatible types in assignment (expression has type "int"
install.py:0: error: Incompatible types in assignment (expression has type "int", variable has type "str") [assignment]
install.py:0: error: Incompatible types in assignment (expression has type "list[dict[Any, Any]]", variable has type "int") [assignment]
install.py:0: error: Incompatible types in assignment (expression has type "list[str]", variable has type "Invocation") [assignment]
install.py:0: error: Incompatible types in assignment (expression has type "list[str]", variable has type "str") [assignment]
install.py:0: error: Incompatible types in assignment (expression has type "set[int]", variable has type "int") [assignment]
install.py:0: error: Incompatible types in assignment (expression has type "set[int]", variable has type "list[dict[Any, Any]]") [assignment]
install.py:0: error: Incompatible types in assignment (expression has type "set[int]", variable has type "list[dict[Any, Any]]") [assignment]
+215 -3
View File
@@ -39,6 +39,10 @@
"summary",
"app",
"record",
"removal_plan",
"removed",
"restored",
"backup",
"end"
]
}
@@ -131,6 +135,18 @@
{
"$ref": "#/$defs/record"
},
{
"$ref": "#/$defs/removal_plan"
},
{
"$ref": "#/$defs/removed"
},
{
"$ref": "#/$defs/restored"
},
{
"$ref": "#/$defs/backup"
},
{
"$ref": "#/$defs/end"
}
@@ -160,7 +176,9 @@
"install",
"check",
"dry-run",
"list"
"list",
"remove",
"restore"
]
}
}
@@ -1002,7 +1020,9 @@
"install",
"check",
"dry-run",
"list"
"list",
"remove",
"restore"
]
},
"installed": {
@@ -1127,7 +1147,11 @@
"applied",
"written",
"unreadable",
"not_written"
"not_written",
"listed",
"made",
"deleted",
"not_deleted"
]
},
"choice": {
@@ -1148,6 +1172,194 @@
}
}
},
"removal_plan": {
"type": "object",
"additionalProperties": false,
"required": [
"event",
"platform",
"folder",
"files",
"bytes",
"systems",
"dests",
"kept",
"gone",
"backup",
"message"
],
"properties": {
"event": {
"const": "removal_plan"
},
"platform": {
"type": "string"
},
"folder": {
"type": "string"
},
"files": {
"type": "integer",
"minimum": 0
},
"bytes": {
"type": "integer",
"minimum": 0
},
"systems": {
"type": "array",
"items": {
"type": "string"
}
},
"dests": {
"type": "array",
"items": {
"type": "string"
}
},
"kept": {
"type": "array",
"items": {
"type": "object"
}
},
"gone": {
"type": "integer",
"minimum": 0
},
"backup": {
"type": "boolean"
},
"message": {
"type": "string"
}
}
},
"removed": {
"type": "object",
"additionalProperties": false,
"required": [
"event",
"platform",
"dest",
"size",
"ok",
"reason",
"moved_to"
],
"properties": {
"event": {
"const": "removed"
},
"platform": {
"type": "string"
},
"dest": {
"type": "string"
},
"size": {
"type": "integer",
"minimum": 0
},
"ok": {
"type": "boolean"
},
"reason": {
"type": "string"
},
"moved_to": {
"type": "string"
}
}
},
"restored": {
"type": "object",
"additionalProperties": false,
"required": [
"event",
"folder",
"path",
"size",
"ok",
"reason"
],
"properties": {
"event": {
"const": "restored"
},
"folder": {
"type": "string"
},
"path": {
"type": "string"
},
"size": {
"type": "integer",
"minimum": 0
},
"ok": {
"type": "boolean"
},
"reason": {
"type": "string"
}
}
},
"backup": {
"type": "object",
"additionalProperties": false,
"required": [
"event",
"folder",
"date",
"files",
"bytes",
"source",
"status",
"message"
],
"properties": {
"event": {
"const": "backup"
},
"folder": {
"type": "string"
},
"date": {
"type": "string"
},
"files": {
"type": "integer",
"minimum": 0
},
"bytes": {
"type": "integer",
"minimum": 0
},
"source": {
"enum": [
"remove",
"install"
]
},
"status": {
"enum": [
"applied",
"written",
"unreadable",
"not_written",
"listed",
"made",
"deleted",
"not_deleted"
]
},
"message": {
"type": "string"
}
}
},
"end": {
"type": "object",
"additionalProperties": false,
+8 -5
View File
@@ -37,18 +37,21 @@ KEY_TYPES: dict[str, dict] = {
**dict.fromkeys((
"installer", "os", "os_name", "stage", "reason", "platform", "message",
"folder", "dest", "system", "name", "bios_dir", "path", "member",
"list_version", "installed_version", "board", "core", "date",
"list_version", "installed_version", "board", "core", "date", "moved_to",
), _TEXT),
"mode": {"enum": ["install", "check", "dry-run", "list"]},
"mode": {"enum": ["install", "check", "dry-run", "list", "remove", "restore"]},
"source": {"enum": ["remove", "install"]},
"state": {"enum": ["present", "wrong_hash", "missing"]},
"status": {"enum": ["applied", "written", "unreadable", "not_written"]},
"status": {"enum": ["applied", "written", "unreadable", "not_written", "listed", "made",
"deleted", "not_deleted"]},
**dict.fromkeys((
"files", "files_done", "files_total", "bytes", "bytes_done", "bytes_total",
"bytes_local", "bytes_network", "size", "present", "missing", "wrong_hash",
"bytes_needed", "data_missing", "not_installable", "installed", "up_to_date",
"pending", "code", "games", "recognised", "not_games",
"pending", "code", "games", "recognised", "not_games", "gone",
), _COUNT),
**dict.fromkeys(("truncated", "ok", "interrupted", "required_only", "applied", "info"), _FLAG),
**dict.fromkeys(("truncated", "ok", "interrupted", "required_only", "applied", "info",
"backup"), _FLAG),
**dict.fromkeys(("systems", "other_drives", "dests", "written", "choice"), _NAMES),
**dict.fromkeys(("platforms", "failed", "skipped", "left_out", "kept", "found",
"fallback"), _RECORDS),
+3 -1
View File
@@ -1002,7 +1002,9 @@ GLANCE_GROUPS: tuple[tuple[str, tuple[str, ...]], ...] = (
"--cores-dir")),
("Look first", ("--check", "--dry-run", "--list-platforms", "--list-targets",
"--list-systems", "--list-cores", "--list-emulators")),
("From files on hand", ("--from", "--from-pack", "--manifest", "--offline", "--restore")),
("From files on hand", ("--from", "--from-pack", "--manifest", "--offline")),
("Remove and put back", ("--remove", "--file", "--backup", "--backups", "--restore",
"--delete-backup")),
("How the run talks", ("--tui", "--plain", "--no-input", "--yes", "--quiet", "--verbose",
"--events", "--jobs", "--version", "--help")),
)
+40
View File
@@ -0,0 +1,40 @@
RetroBIOS: Backups
Newest first.
> 3 Nov 12:00 2 files, 512.0 KB
20 Oct 09:30 1 file, 16.0 KB
Back
A/Enter Choose B/Esc Back ? Help
+12
View File
@@ -0,0 +1,12 @@
RetroBIOS: Backups
Newest first.
> 3 Nov 12:00 2 files, 512.0 KB
20 Oct 09:30 1 file, 16.0 KB
Back
A Choose B Back ? Help
+15
View File
@@ -0,0 +1,15 @@
RetroBIOS: Backups
Newest first.
> 3 Nov 12:00 2 files, 512.0 KB
20 Oct 09:30 1 file, 16.0 KB
Back
A Choose B Back ? Help
+24
View File
@@ -0,0 +1,24 @@
RetroBIOS: Backups
Newest first.
> 3 Nov 12:00 2 files, 512.0 KB
20 Oct 09:30 1 file, 16.0 KB
Back
A/Enter Choose B/Esc Back ? Help
+247 -1
View File
@@ -1160,6 +1160,7 @@
Change where
Details
Change
More
Quit
@@ -1183,7 +1184,252 @@
^[[0;1;35mA/Enter^[[0m Install ^[[0;2mB/Esc^[[0m Back ^[[0;2mY/o^[[0m Change ^[[0;2m?^[[0m Help
=== more
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: More^[[0m
^[[0;7;35m > Remove files^[[0m
Backups
Put back a backup
Delete a backup
^[[0;1;35mA/Enter^[[0m Open ^[[0;2mB/Esc^[[0m Back ^[[0;2m?^[[0m Help
=== remove
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Remove files^[[0m
Choose what to remove. A file changed or added by hand stays.
^[[0;7;35m > All RetroBIOS files 528.0 KB^[[0m
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
^[[0;1;35mA/Enter^[[0m Choose ^[[0;2mB/Esc^[[0m Back ^[[0;2m?^[[0m Help
=== removal
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Remove files^[[0m
2 files go, 512.0 KB freed.
BIOS folder: ^[[0;1m~/.config/retroarch/system^[[0m
1 file stays: 1 changed since it was installed.
^[[0;7;35m > Remove 512.0 KB^[[0m
Keep a backup: no --backup
Details
^[[0;1;35mA/Enter^[[0m Remove ^[[0;2mB/Esc^[[0m Back ^[[0;2m?^[[0m Help
=== removal-confirm
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Confirm^[[0m
Remove 2 files for good? 512.0 KB is freed.
^[[0;7;35m > Remove^[[0m
Back
^[[0;1;35mA/Enter^[[0m Confirm ^[[0;2mB/Esc^[[0m Back ^[[0;2m?^[[0m Help
=== removal-done
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: ^[[0;1;32mDone^[[0m
^[[0;1m Removed ^[[0;1;35m2^[[0;1m files, ^[[0;1;35m512.0 KB^[[0;1m freed.^[[0m
BIOS folder: ^[[0;1m~/.config/retroarch/system^[[0m
^[[0;7;35m > Close^[[0m
Check again
^[[0;1;35mA/Enter^[[0m Close ^[[0;2mB/Esc^[[0m Back ^[[0;2m?^[[0m Help
=== backups
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Backups^[[0m
Newest first.
^[[0;7;35m > 3 Nov 12:00 2 files, 512.0 KB^[[0m
20 Oct 09:30 1 file, 16.0 KB
Back
^[[0;1;35mA/Enter^[[0m Choose ^[[0;2mB/Esc^[[0m Back ^[[0;2m?^[[0m Help
+79 -1
View File
@@ -373,5 +373,83 @@
Same choice as on 3 Nov: yes
Choose systems
Copy from an old card or a folder
^[[0;2m 1-4 of 8^[[0m
^[[0;2m 1-4 of 9^[[0m
^[[0;1;35mA^[[0m Install ^[[0;2mB^[[0m Back ^[[0;2mY^[[0m Change ^[[0;2m?^[[0m Help
=== more
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: More^[[0m
^[[0;7;35m > Remove files^[[0m
Backups
Put back a backup
Delete a backup
^[[0;1;35mA^[[0m Open ^[[0;2mB^[[0m Back ^[[0;2m?^[[0m Help
=== remove
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Remove files^[[0m
Choose what to remove. A file changed
or added by hand stays.
^[[0;7;35m > All RetroBIOS files 528.0 KB^[[0m
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
^[[0;1;35mA^[[0m Choose ^[[0;2mB^[[0m Back ^[[0;2m?^[[0m Help
=== removal
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Remove files^[[0m
2 files go, 512.0 KB freed.
BIOS folder:
^[[0;1m~/.config/retroarch/system^[[0m
1 file stays: 1 changed since it was
installed.
^[[0;7;35m > Remove 512.0 KB^[[0m
Keep a backup: no --backup
Details
^[[0;1;35mA^[[0m Remove ^[[0;2mB^[[0m Back ^[[0;2m?^[[0m Help
=== removal-confirm
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Confirm^[[0m
Remove 2 files for good? 512.0 KB is
freed.
^[[0;7;35m > Remove^[[0m
Back
^[[0;1;35mA^[[0m Confirm ^[[0;2mB^[[0m Back ^[[0;2m?^[[0m Help
=== removal-done
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: ^[[0;1;32mDone^[[0m
^[[0;1m Removed ^[[0;1;35m2^[[0;1m files, ^[[0;1;35m512.0 KB^[[0;1m freed.^[[0m
BIOS folder: ^[[0;1m~/.co.../retroarch/system^[[0m
^[[0;7;35m > Close^[[0m
Check again
^[[0;1;35mA^[[0m Close ^[[0;2mB^[[0m Back ^[[0;2m?^[[0m Help
=== backups
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Backups^[[0m
Newest first.
^[[0;7;35m > 3 Nov 12:00 2 files, 512.0 KB^[[0m
20 Oct 09:30 1 file, 16.0 KB
Back
^[[0;1;35mA^[[0m Choose ^[[0;2mB^[[0m Back ^[[0;2m?^[[0m Help
+97 -1
View File
@@ -452,7 +452,6 @@
^[[0;1m ^[[0;1;35m1^[[0;1m file to download, ^[[0;1;35m16.0 KB^[[0m
BIOS folder:
^[[0;1m~/.config/retroarch/system^[[0m
For nintendo-gba.
^[[0;7;35m > Install^[[0m
Same choice as on 3 Nov: yes
Choose systems
@@ -460,5 +459,102 @@
Change where
Details
Change
More
Quit
^[[0;1;35mA^[[0m Install ^[[0;2mB^[[0m Back ^[[0;2mY^[[0m Change ^[[0;2m?^[[0m Help
=== more
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: More^[[0m
^[[0;7;35m > Remove files^[[0m
Backups
Put back a backup
Delete a backup
^[[0;1;35mA^[[0m Open ^[[0;2mB^[[0m Back ^[[0;2m?^[[0m Help
=== remove
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Remove files^[[0m
Choose what to remove. A file changed
or added by hand stays.
^[[0;7;35m > All RetroBIOS files 528.0 KB^[[0m
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
^[[0;1;35mA^[[0m Choose ^[[0;2mB^[[0m Back ^[[0;2m?^[[0m Help
=== removal
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Remove files^[[0m
2 files go, 512.0 KB freed.
BIOS folder:
^[[0;1m~/.config/retroarch/system^[[0m
1 file stays: 1 changed since it was
installed.
^[[0;7;35m > Remove 512.0 KB^[[0m
Keep a backup: no --backup
Details
^[[0;1;35mA^[[0m Remove ^[[0;2mB^[[0m Back ^[[0;2m?^[[0m Help
=== removal-confirm
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Confirm^[[0m
Remove 2 files for good? 512.0 KB is
freed.
^[[0;7;35m > Remove^[[0m
Back
^[[0;1;35mA^[[0m Confirm ^[[0;2mB^[[0m Back ^[[0;2m?^[[0m Help
=== removal-done
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: ^[[0;1;32mDone^[[0m
^[[0;1m Removed ^[[0;1;35m2^[[0;1m files, ^[[0;1;35m512.0 KB^[[0;1m freed.^[[0m
BIOS folder: ^[[0;1m~/.co.../retroarch/system^[[0m
^[[0;7;35m > Close^[[0m
Check again
^[[0;1;35mA^[[0m Close ^[[0;2mB^[[0m Back ^[[0;2m?^[[0m Help
=== backups
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Backups^[[0m
Newest first.
^[[0;7;35m > 3 Nov 12:00 2 files, 512.0 KB^[[0m
20 Oct 09:30 1 file, 16.0 KB
Back
^[[0;1;35mA^[[0m Choose ^[[0;2mB^[[0m Back ^[[0;2m?^[[0m Help
+151 -1
View File
@@ -712,6 +712,7 @@
Change where
Details
Change
More
Quit
@@ -721,5 +722,154 @@
^[[0;1;35mA/Enter^[[0m Install ^[[0;2mB/Esc^[[0m Back ^[[0;2mY/o^[[0m Change ^[[0;2m?^[[0m Help
=== more
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: More^[[0m
^[[0;7;35m > Remove files^[[0m
Backups
Put back a backup
Delete a backup
^[[0;1;35mA/Enter^[[0m Open ^[[0;2mB/Esc^[[0m Back ^[[0;2m?^[[0m Help
=== remove
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Remove files^[[0m
Choose what to remove. A file changed or added by hand stays.
^[[0;7;35m > All RetroBIOS files 528.0 KB^[[0m
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
^[[0;1;35mA/Enter^[[0m Choose ^[[0;2mB/Esc^[[0m Back ^[[0;2m?^[[0m Help
=== removal
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Remove files^[[0m
2 files go, 512.0 KB freed.
BIOS folder: ^[[0;1m~/.config/retroarch/system^[[0m
1 file stays: 1 changed since it was installed.
^[[0;7;35m > Remove 512.0 KB^[[0m
Keep a backup: no --backup
Details
^[[0;1;35mA/Enter^[[0m Remove ^[[0;2mB/Esc^[[0m Back ^[[0;2m?^[[0m Help
=== removal-confirm
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Confirm^[[0m
Remove 2 files for good? 512.0 KB is freed.
^[[0;7;35m > Remove^[[0m
Back
^[[0;1;35mA/Enter^[[0m Confirm ^[[0;2mB/Esc^[[0m Back ^[[0;2m?^[[0m Help
=== removal-done
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: ^[[0;1;32mDone^[[0m
^[[0;1m Removed ^[[0;1;35m2^[[0;1m files, ^[[0;1;35m512.0 KB^[[0;1m freed.^[[0m
BIOS folder: ^[[0;1m~/.config/retroarch/system^[[0m
^[[0;7;35m > Close^[[0m
Check again
^[[0;1;35mA/Enter^[[0m Close ^[[0;2mB/Esc^[[0m Back ^[[0;2m?^[[0m Help
=== backups
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Backups^[[0m
Newest first.
^[[0;7;35m > 3 Nov 12:00 2 files, 512.0 KB^[[0m
20 Oct 09:30 1 file, 16.0 KB
Back
^[[0;1;35mA/Enter^[[0m Choose ^[[0;2mB/Esc^[[0m Back ^[[0;2m?^[[0m Help
+40
View File
@@ -0,0 +1,40 @@
RetroBIOS: More
> Remove files
Backups
Put back a backup
Delete a backup
A/Enter Open B/Esc Back ? Help
+12
View File
@@ -0,0 +1,12 @@
RetroBIOS: More
> Remove files
Backups
Put back a backup
Delete a backup
A Open B Back ? Help
+15
View File
@@ -0,0 +1,15 @@
RetroBIOS: More
> Remove files
Backups
Put back a backup
Delete a backup
A Open B Back ? Help
+24
View File
@@ -0,0 +1,24 @@
RetroBIOS: More
> Remove files
Backups
Put back a backup
Delete a backup
A/Enter Open B/Esc Back ? Help
+1 -1
View File
@@ -11,6 +11,7 @@
Change where
Details
Change
More
Quit
@@ -34,7 +35,6 @@
A/Enter Install B/Esc Back Y/o Change ? Help
+1 -1
View File
@@ -8,5 +8,5 @@
Same choice as on 3 Nov: yes
Choose systems
Copy from an old card or a folder
1-4 of 8
1-4 of 9
A Install B Back Y Change ? Help
+1 -1
View File
@@ -3,7 +3,6 @@
1 file to download, 16.0 KB
BIOS folder:
~/.config/retroarch/system
For nintendo-gba.
> Install
Same choice as on 3 Nov: yes
Choose systems
@@ -11,5 +10,6 @@
Change where
Details
Change
More
Quit
A Install B Back Y Change ? Help
+1 -1
View File
@@ -11,6 +11,7 @@
Change where
Details
Change
More
Quit
@@ -20,5 +21,4 @@
A/Enter Install B/Esc Back Y/o Change ? Help
+40
View File
@@ -0,0 +1,40 @@
RetroBIOS: Remove files
2 files go, 512.0 KB freed.
BIOS folder: ~/.config/retroarch/system
1 file stays: 1 changed since it was installed.
> Remove 512.0 KB
Keep a backup: no --backup
Details
A/Enter Remove B/Esc Back ? Help
+12
View File
@@ -0,0 +1,12 @@
RetroBIOS: Remove files
2 files go, 512.0 KB freed.
BIOS folder:
~/.config/retroarch/system
1 file stays: 1 changed since it was
installed.
> Remove 512.0 KB
Keep a backup: no --backup
Details
A Remove B Back ? Help
+15
View File
@@ -0,0 +1,15 @@
RetroBIOS: Remove files
2 files go, 512.0 KB freed.
BIOS folder:
~/.config/retroarch/system
1 file stays: 1 changed since it was
installed.
> Remove 512.0 KB
Keep a backup: no --backup
Details
A Remove B Back ? Help
+24
View File
@@ -0,0 +1,24 @@
RetroBIOS: Remove files
2 files go, 512.0 KB freed.
BIOS folder: ~/.config/retroarch/system
1 file stays: 1 changed since it was installed.
> Remove 512.0 KB
Keep a backup: no --backup
Details
A/Enter Remove B/Esc Back ? Help
+40
View File
@@ -0,0 +1,40 @@
RetroBIOS: Confirm
Remove 2 files for good? 512.0 KB is freed.
> Remove
Back
A/Enter Confirm B/Esc Back ? Help
+12
View File
@@ -0,0 +1,12 @@
RetroBIOS: Confirm
Remove 2 files for good? 512.0 KB is
freed.
> Remove
Back
A Confirm B Back ? Help
+15
View File
@@ -0,0 +1,15 @@
RetroBIOS: Confirm
Remove 2 files for good? 512.0 KB is
freed.
> Remove
Back
A Confirm B Back ? Help
+24
View File
@@ -0,0 +1,24 @@
RetroBIOS: Confirm
Remove 2 files for good? 512.0 KB is freed.
> Remove
Back
A/Enter Confirm B/Esc Back ? Help
+40
View File
@@ -0,0 +1,40 @@
RetroBIOS: Done
Removed 2 files, 512.0 KB freed.
BIOS folder: ~/.config/retroarch/system
> Close
Check again
A/Enter Close B/Esc Back ? Help
+12
View File
@@ -0,0 +1,12 @@
RetroBIOS: Done
Removed 2 files, 512.0 KB freed.
BIOS folder: ~/.co.../retroarch/system
> Close
Check again
A Close B Back ? Help
+15
View File
@@ -0,0 +1,15 @@
RetroBIOS: Done
Removed 2 files, 512.0 KB freed.
BIOS folder: ~/.co.../retroarch/system
> Close
Check again
A Close B Back ? Help
+24
View File
@@ -0,0 +1,24 @@
RetroBIOS: Done
Removed 2 files, 512.0 KB freed.
BIOS folder: ~/.config/retroarch/system
> Close
Check again
A/Enter Close B/Esc Back ? Help
+40
View File
@@ -0,0 +1,40 @@
RetroBIOS: Remove files
Choose what to remove. A file changed or added by hand stays.
> All RetroBIOS files 528.0 KB
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
A/Enter Choose B/Esc Back ? Help
+12
View File
@@ -0,0 +1,12 @@
RetroBIOS: Remove files
Choose what to remove. A file changed
or added by hand stays.
> All RetroBIOS files 528.0 KB
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
A Choose B Back ? Help
+15
View File
@@ -0,0 +1,15 @@
RetroBIOS: Remove files
Choose what to remove. A file changed
or added by hand stays.
> All RetroBIOS files 528.0 KB
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
A Choose B Back ? Help
+24
View File
@@ -0,0 +1,24 @@
RetroBIOS: Remove files
Choose what to remove. A file changed or added by hand stays.
> All RetroBIOS files 528.0 KB
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
A/Enter Choose B/Esc Back ? Help
+327 -3
View File
@@ -1302,7 +1302,7 @@
Same choice as on 3 Nov: yes
Choose systems
Copy from an old card or a folder
1-4 of 8
1-4 of 9
A Install B Back Y Change
=== plan-record 40x15
RetroBIOS: RetroArch
@@ -1310,7 +1310,6 @@
1 file to download, 16.0 KB
BIOS folder:
~/.config/retroarch/system
For nintendo-gba.
> Install
Same choice as on 3 Nov: yes
Choose systems
@@ -1318,6 +1317,7 @@
Change where
Details
Change
More
Quit
A Install B Back Y Change
=== plan-record 80x24
@@ -1334,6 +1334,7 @@
Change where
Details
Change
More
Quit
@@ -1343,5 +1344,328 @@
A Install B Back Y Change
=== more 40x12
RetroBIOS: More
> Remove files
Backups
Put back a backup
Delete a backup
A Open B Back
=== more 40x15
RetroBIOS: More
> Remove files
Backups
Put back a backup
Delete a backup
A Open B Back
=== more 80x24
RetroBIOS: More
> Remove files
Backups
Put back a backup
Delete a backup
A Open B Back
=== remove 40x12
RetroBIOS: Remove files
Choose what to remove. A file changed
or added by hand stays.
> All RetroBIOS files 528.0 KB
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
A Choose B Back
=== remove 40x15
RetroBIOS: Remove files
Choose what to remove. A file changed
or added by hand stays.
> All RetroBIOS files 528.0 KB
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
A Choose B Back
=== remove 80x24
RetroBIOS: Remove files
Choose what to remove. A file changed or added by hand stays.
> All RetroBIOS files 528.0 KB
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
A Choose B Back
=== removal 40x12
RetroBIOS: Remove files
2 files go, 512.0 KB freed.
BIOS folder:
~/.config/retroarch/system
1 file stays: 1 changed since it was
installed.
> Remove 512.0 KB
Keep a backup: no --backup
Details
A Remove B Back
=== removal 40x15
RetroBIOS: Remove files
2 files go, 512.0 KB freed.
BIOS folder:
~/.config/retroarch/system
1 file stays: 1 changed since it was
installed.
> Remove 512.0 KB
Keep a backup: no --backup
Details
A Remove B Back
=== removal 80x24
RetroBIOS: Remove files
2 files go, 512.0 KB freed.
BIOS folder: ~/.config/retroarch/system
1 file stays: 1 changed since it was installed.
> Remove 512.0 KB
Keep a backup: no --backup
Details
A Remove B Back
=== removal-confirm 40x12
RetroBIOS: Confirm
Remove 2 files for good? 512.0 KB is
freed.
> Remove
Back
A Confirm B Back
=== removal-confirm 40x15
RetroBIOS: Confirm
Remove 2 files for good? 512.0 KB is
freed.
> Remove
Back
A Confirm B Back
=== removal-confirm 80x24
RetroBIOS: Confirm
Remove 2 files for good? 512.0 KB is freed.
> Remove
Back
A Confirm B Back
=== removal-done 40x12
RetroBIOS: Done
Removed 2 files, 512.0 KB freed.
BIOS folder: ~/.co.../retroarch/system
> Close
Check again
A Close B Back
=== removal-done 40x15
RetroBIOS: Done
Removed 2 files, 512.0 KB freed.
BIOS folder: ~/.co.../retroarch/system
> Close
Check again
A Close B Back
=== removal-done 80x24
RetroBIOS: Done
Removed 2 files, 512.0 KB freed.
BIOS folder: ~/.config/retroarch/system
> Close
Check again
A Close B Back
=== backups 40x12
RetroBIOS: Backups
Newest first.
> 3 Nov 12:00 2 files, 512.0 KB
20 Oct 09:30 1 file, 16.0 KB
Back
A Choose B Back
=== backups 40x15
RetroBIOS: Backups
Newest first.
> 3 Nov 12:00 2 files, 512.0 KB
20 Oct 09:30 1 file, 16.0 KB
Back
A Choose B Back
=== backups 80x24
RetroBIOS: Backups
Newest first.
> 3 Nov 12:00 2 files, 512.0 KB
20 Oct 09:30 1 file, 16.0 KB
Back
A Choose B Back
+327 -3
View File
@@ -1302,7 +1302,7 @@
Same choice as on 3 Nov: yes
Choose systems
Copy from an old card or a folder
1-4 of 8
1-4 of 9
A Install B Back Y Change
=== plan-record 40x15
RetroBIOS: RetroArch
@@ -1310,7 +1310,6 @@
1 file to download, 16.0 KB
BIOS folder:
~/.config/retroarch/system
For nintendo-gba.
> Install
Same choice as on 3 Nov: yes
Choose systems
@@ -1318,6 +1317,7 @@
Change where
Details
Change
More
Quit
A Install B Back Y Change
=== plan-record 80x24
@@ -1334,6 +1334,7 @@
Change where
Details
Change
More
Quit
@@ -1343,5 +1344,328 @@
A Install B Back Y Change
=== more 40x12
RetroBIOS: More
> Remove files
Backups
Put back a backup
Delete a backup
A Open B Back
=== more 40x15
RetroBIOS: More
> Remove files
Backups
Put back a backup
Delete a backup
A Open B Back
=== more 80x24
RetroBIOS: More
> Remove files
Backups
Put back a backup
Delete a backup
A Open B Back
=== remove 40x12
RetroBIOS: Remove files
Choose what to remove. A file changed
or added by hand stays.
> All RetroBIOS files 528.0 KB
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
A Choose B Back
=== remove 40x15
RetroBIOS: Remove files
Choose what to remove. A file changed
or added by hand stays.
> All RetroBIOS files 528.0 KB
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
A Choose B Back
=== remove 80x24
RetroBIOS: Remove files
Choose what to remove. A file changed or added by hand stays.
> All RetroBIOS files 528.0 KB
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
A Choose B Back
=== removal 40x12
RetroBIOS: Remove files
2 files go, 512.0 KB freed.
BIOS folder:
~/.config/retroarch/system
1 file stays: 1 changed since it was
installed.
> Remove 512.0 KB
Keep a backup: no --backup
Details
A Remove B Back
=== removal 40x15
RetroBIOS: Remove files
2 files go, 512.0 KB freed.
BIOS folder:
~/.config/retroarch/system
1 file stays: 1 changed since it was
installed.
> Remove 512.0 KB
Keep a backup: no --backup
Details
A Remove B Back
=== removal 80x24
RetroBIOS: Remove files
2 files go, 512.0 KB freed.
BIOS folder: ~/.config/retroarch/system
1 file stays: 1 changed since it was installed.
> Remove 512.0 KB
Keep a backup: no --backup
Details
A Remove B Back
=== removal-confirm 40x12
RetroBIOS: Confirm
Remove 2 files for good? 512.0 KB is
freed.
> Remove
Back
A Confirm B Back
=== removal-confirm 40x15
RetroBIOS: Confirm
Remove 2 files for good? 512.0 KB is
freed.
> Remove
Back
A Confirm B Back
=== removal-confirm 80x24
RetroBIOS: Confirm
Remove 2 files for good? 512.0 KB is freed.
> Remove
Back
A Confirm B Back
=== removal-done 40x12
RetroBIOS: Done
Removed 2 files, 512.0 KB freed.
BIOS folder: ~/.co.../retroarch/system
> Close
Check again
A Close B Back
=== removal-done 40x15
RetroBIOS: Done
Removed 2 files, 512.0 KB freed.
BIOS folder: ~/.co.../retroarch/system
> Close
Check again
A Close B Back
=== removal-done 80x24
RetroBIOS: Done
Removed 2 files, 512.0 KB freed.
BIOS folder: ~/.config/retroarch/system
> Close
Check again
A Close B Back
=== backups 40x12
RetroBIOS: Backups
Newest first.
> 3 Nov 12:00 2 files, 512.0 KB
20 Oct 09:30 1 file, 16.0 KB
Back
A Choose B Back
=== backups 40x15
RetroBIOS: Backups
Newest first.
> 3 Nov 12:00 2 files, 512.0 KB
20 Oct 09:30 1 file, 16.0 KB
Back
A Choose B Back
=== backups 80x24
RetroBIOS: Backups
Newest first.
> 3 Nov 12:00 2 files, 512.0 KB
20 Oct 09:30 1 file, 16.0 KB
Back
A Choose B Back
+327 -3
View File
@@ -1302,7 +1302,7 @@
Same choice as on 3 Nov: yes
Choose systems
Copy from an old card or a folder
1-4 of 8
1-4 of 9
A Install B Back Y Change
=== plan-record 40x15
RetroBIOS: RetroArch
@@ -1310,7 +1310,6 @@
1 file to download, 16.0 KB
BIOS folder:
~/.config/retroarch/system
For nintendo-gba.
> Install
Same choice as on 3 Nov: yes
Choose systems
@@ -1318,6 +1317,7 @@
Change where
Details
Change
More
Quit
A Install B Back Y Change
=== plan-record 80x24
@@ -1334,6 +1334,7 @@
Change where
Details
Change
More
Quit
@@ -1343,5 +1344,328 @@
A Install B Back Y Change
=== more 40x12
RetroBIOS: More
> Remove files
Backups
Put back a backup
Delete a backup
A Open B Back
=== more 40x15
RetroBIOS: More
> Remove files
Backups
Put back a backup
Delete a backup
A Open B Back
=== more 80x24
RetroBIOS: More
> Remove files
Backups
Put back a backup
Delete a backup
A Open B Back
=== remove 40x12
RetroBIOS: Remove files
Choose what to remove. A file changed
or added by hand stays.
> All RetroBIOS files 528.0 KB
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
A Choose B Back
=== remove 40x15
RetroBIOS: Remove files
Choose what to remove. A file changed
or added by hand stays.
> All RetroBIOS files 528.0 KB
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
A Choose B Back
=== remove 80x24
RetroBIOS: Remove files
Choose what to remove. A file changed or added by hand stays.
> All RetroBIOS files 528.0 KB
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
A Choose B Back
=== removal 40x12
RetroBIOS: Remove files
2 files go, 512.0 KB freed.
BIOS folder:
~/.config/retroarch/system
1 file stays: 1 changed since it was
installed.
> Remove 512.0 KB
Keep a backup: no --backup
Details
A Remove B Back
=== removal 40x15
RetroBIOS: Remove files
2 files go, 512.0 KB freed.
BIOS folder:
~/.config/retroarch/system
1 file stays: 1 changed since it was
installed.
> Remove 512.0 KB
Keep a backup: no --backup
Details
A Remove B Back
=== removal 80x24
RetroBIOS: Remove files
2 files go, 512.0 KB freed.
BIOS folder: ~/.config/retroarch/system
1 file stays: 1 changed since it was installed.
> Remove 512.0 KB
Keep a backup: no --backup
Details
A Remove B Back
=== removal-confirm 40x12
RetroBIOS: Confirm
Remove 2 files for good? 512.0 KB is
freed.
> Remove
Back
A Confirm B Back
=== removal-confirm 40x15
RetroBIOS: Confirm
Remove 2 files for good? 512.0 KB is
freed.
> Remove
Back
A Confirm B Back
=== removal-confirm 80x24
RetroBIOS: Confirm
Remove 2 files for good? 512.0 KB is freed.
> Remove
Back
A Confirm B Back
=== removal-done 40x12
RetroBIOS: Done
Removed 2 files, 512.0 KB freed.
BIOS folder: ~/.co.../retroarch/system
> Close
Check again
A Close B Back
=== removal-done 40x15
RetroBIOS: Done
Removed 2 files, 512.0 KB freed.
BIOS folder: ~/.co.../retroarch/system
> Close
Check again
A Close B Back
=== removal-done 80x24
RetroBIOS: Done
Removed 2 files, 512.0 KB freed.
BIOS folder: ~/.config/retroarch/system
> Close
Check again
A Close B Back
=== backups 40x12
RetroBIOS: Backups
Newest first.
> 3 Nov 12:00 2 files, 512.0 KB
20 Oct 09:30 1 file, 16.0 KB
Back
A Choose B Back
=== backups 40x15
RetroBIOS: Backups
Newest first.
> 3 Nov 12:00 2 files, 512.0 KB
20 Oct 09:30 1 file, 16.0 KB
Back
A Choose B Back
=== backups 80x24
RetroBIOS: Backups
Newest first.
> 3 Nov 12:00 2 files, 512.0 KB
20 Oct 09:30 1 file, 16.0 KB
Back
A Choose B Back
+327 -3
View File
@@ -1467,7 +1467,7 @@
Same choice as on 3 Nov: yes
Choose systems
Copy from an old card or a folder
1-4 of 8
1-4 of 9
Enter Install Esc Back o Change
=== plan-record 40x15
RetroBIOS: RetroArch
@@ -1475,7 +1475,6 @@
1 file to download, 16.0 KB
BIOS folder:
~/.config/retroarch/system
For nintendo-gba.
> Install
Same choice as on 3 Nov: yes
Choose systems
@@ -1483,6 +1482,7 @@
Change where
Details
Change
More
Quit
Enter Install Esc Back o Change
=== plan-record 80x24
@@ -1499,6 +1499,7 @@
Change where
Details
Change
More
Quit
@@ -1508,5 +1509,328 @@
Enter Install Esc Back o Change ? Help
=== more 40x12
RetroBIOS: More
> Remove files
Backups
Put back a backup
Delete a backup
Enter Open Esc Back ? Help
=== more 40x15
RetroBIOS: More
> Remove files
Backups
Put back a backup
Delete a backup
Enter Open Esc Back ? Help
=== more 80x24
RetroBIOS: More
> Remove files
Backups
Put back a backup
Delete a backup
Enter Open Esc Back ? Help
=== remove 40x12
RetroBIOS: Remove files
Choose what to remove. A file changed
or added by hand stays.
> All RetroBIOS files 528.0 KB
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
Enter Choose Esc Back ? Help
=== remove 40x15
RetroBIOS: Remove files
Choose what to remove. A file changed
or added by hand stays.
> All RetroBIOS files 528.0 KB
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
Enter Choose Esc Back ? Help
=== remove 80x24
RetroBIOS: Remove files
Choose what to remove. A file changed or added by hand stays.
> All RetroBIOS files 528.0 KB
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
Enter Choose Esc Back ? Help
=== removal 40x12
RetroBIOS: Remove files
2 files go, 512.0 KB freed.
BIOS folder:
~/.config/retroarch/system
1 file stays: 1 changed since it was
installed.
> Remove 512.0 KB
Keep a backup: no --backup
Details
Enter Remove Esc Back ? Help
=== removal 40x15
RetroBIOS: Remove files
2 files go, 512.0 KB freed.
BIOS folder:
~/.config/retroarch/system
1 file stays: 1 changed since it was
installed.
> Remove 512.0 KB
Keep a backup: no --backup
Details
Enter Remove Esc Back ? Help
=== removal 80x24
RetroBIOS: Remove files
2 files go, 512.0 KB freed.
BIOS folder: ~/.config/retroarch/system
1 file stays: 1 changed since it was installed.
> Remove 512.0 KB
Keep a backup: no --backup
Details
Enter Remove Esc Back ? Help
=== removal-confirm 40x12
RetroBIOS: Confirm
Remove 2 files for good? 512.0 KB is
freed.
> Remove
Back
Enter Confirm Esc Back ? Help
=== removal-confirm 40x15
RetroBIOS: Confirm
Remove 2 files for good? 512.0 KB is
freed.
> Remove
Back
Enter Confirm Esc Back ? Help
=== removal-confirm 80x24
RetroBIOS: Confirm
Remove 2 files for good? 512.0 KB is freed.
> Remove
Back
Enter Confirm Esc Back ? Help
=== removal-done 40x12
RetroBIOS: Done
Removed 2 files, 512.0 KB freed.
BIOS folder: ~/.co.../retroarch/system
> Close
Check again
Enter Close Esc Back ? Help
=== removal-done 40x15
RetroBIOS: Done
Removed 2 files, 512.0 KB freed.
BIOS folder: ~/.co.../retroarch/system
> Close
Check again
Enter Close Esc Back ? Help
=== removal-done 80x24
RetroBIOS: Done
Removed 2 files, 512.0 KB freed.
BIOS folder: ~/.config/retroarch/system
> Close
Check again
Enter Close Esc Back ? Help
=== backups 40x12
RetroBIOS: Backups
Newest first.
> 3 Nov 12:00 2 files, 512.0 KB
20 Oct 09:30 1 file, 16.0 KB
Back
Enter Choose Esc Back ? Help
=== backups 40x15
RetroBIOS: Backups
Newest first.
> 3 Nov 12:00 2 files, 512.0 KB
20 Oct 09:30 1 file, 16.0 KB
Back
Enter Choose Esc Back ? Help
=== backups 80x24
RetroBIOS: Backups
Newest first.
> 3 Nov 12:00 2 files, 512.0 KB
20 Oct 09:30 1 file, 16.0 KB
Back
Enter Choose Esc Back ? Help
+327 -3
View File
@@ -1405,7 +1405,7 @@
Same choice as on 3 Nov: yes
Choose systems
Copy from an old card or a folder
1-4 of 8
1-4 of 9
A Install R2 Back L2 Change
=== plan-record 40x15
RetroBIOS: RetroArch
@@ -1413,7 +1413,6 @@
1 file to download, 16.0 KB
BIOS folder:
~/.config/retroarch/system
For nintendo-gba.
> Install
Same choice as on 3 Nov: yes
Choose systems
@@ -1421,6 +1420,7 @@
Change where
Details
Change
More
Quit
A Install R2 Back L2 Change
=== plan-record 80x24
@@ -1437,6 +1437,7 @@
Change where
Details
Change
More
Quit
@@ -1446,5 +1447,328 @@
A Install R2 Back L2 Change
=== more 40x12
RetroBIOS: More
> Remove files
Backups
Put back a backup
Delete a backup
A Open R2 Back
=== more 40x15
RetroBIOS: More
> Remove files
Backups
Put back a backup
Delete a backup
A Open R2 Back
=== more 80x24
RetroBIOS: More
> Remove files
Backups
Put back a backup
Delete a backup
A Open R2 Back
=== remove 40x12
RetroBIOS: Remove files
Choose what to remove. A file changed
or added by hand stays.
> All RetroBIOS files 528.0 KB
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
A Choose R2 Back
=== remove 40x15
RetroBIOS: Remove files
Choose what to remove. A file changed
or added by hand stays.
> All RetroBIOS files 528.0 KB
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
A Choose R2 Back
=== remove 80x24
RetroBIOS: Remove files
Choose what to remove. A file changed or added by hand stays.
> All RetroBIOS files 528.0 KB
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
A Choose R2 Back
=== removal 40x12
RetroBIOS: Remove files
2 files go, 512.0 KB freed.
BIOS folder:
~/.config/retroarch/system
1 file stays: 1 changed since it was
installed.
> Remove 512.0 KB
Keep a backup: no --backup
Details
A Remove R2 Back
=== removal 40x15
RetroBIOS: Remove files
2 files go, 512.0 KB freed.
BIOS folder:
~/.config/retroarch/system
1 file stays: 1 changed since it was
installed.
> Remove 512.0 KB
Keep a backup: no --backup
Details
A Remove R2 Back
=== removal 80x24
RetroBIOS: Remove files
2 files go, 512.0 KB freed.
BIOS folder: ~/.config/retroarch/system
1 file stays: 1 changed since it was installed.
> Remove 512.0 KB
Keep a backup: no --backup
Details
A Remove R2 Back
=== removal-confirm 40x12
RetroBIOS: Confirm
Remove 2 files for good? 512.0 KB is
freed.
> Remove
Back
A Confirm R2 Back
=== removal-confirm 40x15
RetroBIOS: Confirm
Remove 2 files for good? 512.0 KB is
freed.
> Remove
Back
A Confirm R2 Back
=== removal-confirm 80x24
RetroBIOS: Confirm
Remove 2 files for good? 512.0 KB is freed.
> Remove
Back
A Confirm R2 Back
=== removal-done 40x12
RetroBIOS: Done
Removed 2 files, 512.0 KB freed.
BIOS folder: ~/.co.../retroarch/system
> Close
Check again
A Close R2 Back
=== removal-done 40x15
RetroBIOS: Done
Removed 2 files, 512.0 KB freed.
BIOS folder: ~/.co.../retroarch/system
> Close
Check again
A Close R2 Back
=== removal-done 80x24
RetroBIOS: Done
Removed 2 files, 512.0 KB freed.
BIOS folder: ~/.config/retroarch/system
> Close
Check again
A Close R2 Back
=== backups 40x12
RetroBIOS: Backups
Newest first.
> 3 Nov 12:00 2 files, 512.0 KB
20 Oct 09:30 1 file, 16.0 KB
Back
A Choose R2 Back
=== backups 40x15
RetroBIOS: Backups
Newest first.
> 3 Nov 12:00 2 files, 512.0 KB
20 Oct 09:30 1 file, 16.0 KB
Back
A Choose R2 Back
=== backups 80x24
RetroBIOS: Backups
Newest first.
> 3 Nov 12:00 2 files, 512.0 KB
20 Oct 09:30 1 file, 16.0 KB
Back
A Choose R2 Back
+327 -3
View File
@@ -1521,7 +1521,7 @@
Same choice as on 3 Nov: yes
Choose systems
Copy from an old card or a folder
1-4 of 8
1-4 of 9
Start Install B Back
=== plan-record 40x15
RetroBIOS: RetroArch
@@ -1529,7 +1529,6 @@
1 file to download, 16.0 KB
BIOS folder:
~/.config/retroarch/system
For nintendo-gba.
> Install
Same choice as on 3 Nov: yes
Choose systems
@@ -1537,6 +1536,7 @@
Change where
Details
Change
More
Quit
Start Install B Back
=== plan-record 80x24
@@ -1553,6 +1553,7 @@
Change where
Details
Change
More
Quit
@@ -1562,5 +1563,328 @@
Start Install B Back
=== more 40x12
RetroBIOS: More
> Remove files
Backups
Put back a backup
Delete a backup
Start Open B Back
=== more 40x15
RetroBIOS: More
> Remove files
Backups
Put back a backup
Delete a backup
Start Open B Back
=== more 80x24
RetroBIOS: More
> Remove files
Backups
Put back a backup
Delete a backup
Start Open B Back
=== remove 40x12
RetroBIOS: Remove files
Choose what to remove. A file changed
or added by hand stays.
> All RetroBIOS files 528.0 KB
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
Start Choose B Back
=== remove 40x15
RetroBIOS: Remove files
Choose what to remove. A file changed
or added by hand stays.
> All RetroBIOS files 528.0 KB
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
Start Choose B Back
=== remove 80x24
RetroBIOS: Remove files
Choose what to remove. A file changed or added by hand stays.
> All RetroBIOS files 528.0 KB
nintendo-gba 16.0 KB
sony-playstation 512.0 KB
Start Choose B Back
=== removal 40x12
RetroBIOS: Remove files
2 files go, 512.0 KB freed.
BIOS folder:
~/.config/retroarch/system
1 file stays: 1 changed since it was
installed.
> Remove 512.0 KB
Keep a backup: no --backup
Details
Start Remove B Back
=== removal 40x15
RetroBIOS: Remove files
2 files go, 512.0 KB freed.
BIOS folder:
~/.config/retroarch/system
1 file stays: 1 changed since it was
installed.
> Remove 512.0 KB
Keep a backup: no --backup
Details
Start Remove B Back
=== removal 80x24
RetroBIOS: Remove files
2 files go, 512.0 KB freed.
BIOS folder: ~/.config/retroarch/system
1 file stays: 1 changed since it was installed.
> Remove 512.0 KB
Keep a backup: no --backup
Details
Start Remove B Back
=== removal-confirm 40x12
RetroBIOS: Confirm
Remove 2 files for good? 512.0 KB is
freed.
> Remove
Back
Start Confirm B Back
=== removal-confirm 40x15
RetroBIOS: Confirm
Remove 2 files for good? 512.0 KB is
freed.
> Remove
Back
Start Confirm B Back
=== removal-confirm 80x24
RetroBIOS: Confirm
Remove 2 files for good? 512.0 KB is freed.
> Remove
Back
Start Confirm B Back
=== removal-done 40x12
RetroBIOS: Done
Removed 2 files, 512.0 KB freed.
BIOS folder: ~/.co.../retroarch/system
> Close
Check again
Start Close B Back
=== removal-done 40x15
RetroBIOS: Done
Removed 2 files, 512.0 KB freed.
BIOS folder: ~/.co.../retroarch/system
> Close
Check again
Start Close B Back
=== removal-done 80x24
RetroBIOS: Done
Removed 2 files, 512.0 KB freed.
BIOS folder: ~/.config/retroarch/system
> Close
Check again
Start Close B Back
=== backups 40x12
RetroBIOS: Backups
Newest first.
> 3 Nov 12:00 2 files, 512.0 KB
20 Oct 09:30 1 file, 16.0 KB
Back
Start Choose B Back
=== backups 40x15
RetroBIOS: Backups
Newest first.
> 3 Nov 12:00 2 files, 512.0 KB
20 Oct 09:30 1 file, 16.0 KB
Back
Start Choose B Back
=== backups 80x24
RetroBIOS: Backups
Newest first.
> 3 Nov 12:00 2 files, 512.0 KB
20 Oct 09:30 1 file, 16.0 KB
Back
Start Choose B Back
+4 -2
View File
@@ -63,9 +63,11 @@ JOURNEY_KEYS = {
JOURNEY_WORDS = {
"first_install": 47,
"card_with_games": 87,
"update": 70,
# A plan whose folder has a record names More (one word): an update
# and a second run of one system.
"update": 71,
"one_system": 179,
"one_system_again": 67,
"one_system_again": 68,
# Change holds every option one level down now (design 4.4): its
# headings and the rows for Emulators, a pack, the device and the
# internet, the intro sentence dropped, 10 words on each of its two
+648
View File
@@ -0,0 +1,648 @@
"""Removing what the installer placed, and putting a backup back.
Removal deletes for good by default, or moves into a backup with --backup.
Only the install record says what may go: a file it lists, still its
recorded bytes, that the collection can serve again. Each test drives the
real install.py against a loopback repository, or the planner directly for
records no install writes (a crafted one, a drive that folds case).
"""
from __future__ import annotations
import tests # noqa: F401 offline, scratch under tmp/, the suite's own cache
import contextlib
import hashlib
import io
import json
import os
import shutil
import subprocess
import sys
import tempfile
import types
import unittest
from pathlib import Path
from unittest import mock
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT))
sys.path.insert(0, str(REPO_ROOT / "tests"))
import install # noqa: E402
from test_install_selection import FakeRepository # noqa: E402
try:
import pty
except ImportError: # Windows has no pseudo-terminal module.
pty = None
class SharedRepository(FakeRepository):
"""The loopback repository with a file two systems read."""
PAYLOADS = {**FakeRepository.PAYLOADS,
"shared.bin": (b"shared", ["sony-playstation", "nintendo-gba"], None, None)}
def run(self, *options: str, stdin: str | None = None) -> subprocess.CompletedProcess:
# --restore and --delete-backup name their folder alone.
command = self.command(*options)
if options[0] in ("--restore", "--delete-backup"):
command = [*command[:2], *options]
return subprocess.run(
command, env=self.env(), capture_output=True, text=True,
timeout=60, check=False, input=stdin,
stdin=None if stdin is not None else subprocess.DEVNULL,
)
def answer(self, reply: str, *options: str) -> tuple[int, str]:
"""A run at a terminal that answers reply to its question: (code, text)."""
main, other = pty.openpty()
try:
proc = subprocess.Popen(self.command(*options), env=self.env(), stdin=other,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
os.write(main, reply.encode() + b"\n")
out, _err = proc.communicate(timeout=60)
finally:
os.close(main)
os.close(other)
return proc.returncode, out.decode()
@property
def record(self) -> Path:
return self.dest.parent / install.BACKUP_DIR / f"{self.dest.name}.install.json"
def snapshot(self) -> dict[str, bytes]:
return {path.relative_to(self.dest).as_posix(): path.read_bytes()
for path in sorted(self.dest.rglob("*")) if path.is_file()}
def events(self, proc: subprocess.CompletedProcess) -> list[dict]:
return [json.loads(line) for line in proc.stdout.splitlines() if line.startswith("{")]
def setup(self) -> dict:
return json.loads(self.record.read_text())["setups"]["retroarch"]
def install_both(self) -> None:
proc = self.run("--system", "sony-playstation", "--system", "nintendo-gba", "--yes")
assert proc.returncode == 0, proc.stdout + proc.stderr
GBA_ONLY = ["gba_bios.bin", "mgba/extra.bin"]
PSX = ["pcsx/extra.bin", "scph5500.bin", "scph5501.bin"]
class RemoveOneOfTwoSystems(unittest.TestCase):
def test_the_shared_file_stays_and_a_backup_puts_the_folder_back(self):
with SharedRepository() as repo:
repo.install_both()
before = repo.snapshot()
self.assertEqual(sorted(before), sorted(GBA_ONLY + PSX + ["shared.bin"]))
proc = repo.run("--remove", "--system", "nintendo-gba", "--backup", "--yes")
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
self.assertEqual(sorted(repo.snapshot()), sorted(PSX + ["shared.bin"]))
self.assertIn("shared.bin: still needed by sony-playstation", proc.stdout)
setup = repo.setup()
self.assertEqual(setup["choice"], ["--system", "sony-playstation"])
self.assertEqual(sorted(item["dest"] for item in setup["files"]),
sorted(PSX + ["shared.bin"]))
[backup] = install.list_backups(repo.dest)
self.assertEqual((backup["files"], backup["source"]), (2, "remove"))
proc = repo.run("--restore", str(backup["folder"]))
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
self.assertEqual(repo.snapshot(), before)
setup = repo.setup()
self.assertEqual(sorted(item["dest"] for item in setup["files"]), sorted(before))
self.assertEqual(install.record_options(setup["choice"]),
[("--system", "sony-playstation"), ("--system", "nintendo-gba")])
def test_everything_into_a_backup_and_back_with_its_record(self):
with SharedRepository() as repo:
repo.install_both()
before, setup = repo.snapshot(), repo.setup()
proc = repo.run("--remove", "--backup", "--yes")
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
self.assertEqual((repo.snapshot(), repo.record.exists()), ({}, False))
[backup] = install.list_backups(repo.dest)
self.assertEqual(repo.run("--restore", str(backup["folder"])).returncode, 0)
self.assertEqual(repo.snapshot(), before)
self.assertEqual((repo.setup()["choice"], repo.setup()["files"]),
(setup["choice"], setup["files"]))
def test_removal_deletes_for_good_by_default(self):
with SharedRepository() as repo:
repo.install_both()
proc = repo.run("--remove", "--system", "nintendo-gba", "--yes")
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
self.assertIn("Removed 2 files, 12 B freed.", proc.stdout)
self.assertEqual(install.list_backups(repo.dest), [])
holder = repo.dest.parent / install.BACKUP_DIR
self.assertEqual(sorted(path.name for path in holder.iterdir()),
[f"{repo.dest.name}.install.json"])
def test_removing_everything_leaves_no_record(self):
with SharedRepository() as repo:
repo.install_both()
proc = repo.run("--remove", "--yes")
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
self.assertEqual(repo.snapshot(), {})
self.assertFalse(repo.record.exists())
def test_dry_run_lists_and_removes_nothing(self):
with SharedRepository() as repo:
repo.install_both()
before = repo.snapshot()
proc = repo.run("--remove", "--system", "nintendo-gba", "--dry-run")
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
for dest in GBA_ONLY:
self.assertIn(f" {dest}\n", proc.stdout)
self.assertIn("Nothing was removed (--dry-run).", proc.stdout)
self.assertEqual(repo.snapshot(), before)
def test_one_file(self):
with SharedRepository() as repo:
repo.install_both()
proc = repo.run("--remove", "--file", "gba_bios.bin", "--file",
str(repo.dest / "not-ours.bin"), "--yes")
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
self.assertNotIn("gba_bios.bin", repo.snapshot())
self.assertIn("not-ours.bin: not installed by RetroBIOS", proc.stdout)
outside = repo.run("--remove", "--file", str(repo.root / "x.bin"), "--yes")
self.assertEqual(outside.returncode, 2)
self.assertIn("is not inside", outside.stderr)
def test_a_core(self):
with SharedRepository() as repo:
repo.install_both()
proc = repo.run("--remove", "--core", "mgba", "--yes")
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
self.assertEqual(sorted(repo.snapshot()),
sorted(["gba_bios.bin", "shared.bin"] + PSX))
self.assertIn(("--core", "pcsx_rearmed"),
install.record_options(repo.setup()["choice"]))
class NeverTouched(unittest.TestCase):
def test_a_changed_file_and_one_added_by_hand_stay(self):
with SharedRepository() as repo:
repo.install_both()
(repo.dest / "scph5501.bin").write_bytes(b"the user's own")
(repo.dest / "mine.bin").write_bytes(b"added by hand")
proc = repo.run("--remove", "--yes")
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
self.assertEqual(repo.snapshot(), {"scph5501.bin": b"the user's own",
"mine.bin": b"added by hand"})
self.assertIn("1 file stays: 1 changed since it was installed.", proc.stdout)
self.assertIn("scph5501.bin: changed since it was installed", proc.stdout)
self.assertIn("1 other file stays, not installed by RetroBIOS.", proc.stdout)
# The file that stayed keeps its entry: the setup is not dropped.
self.assertEqual([item["dest"] for item in repo.setup()["files"]],
["scph5501.bin"])
def test_a_link_in_place_of_a_file_stays_and_its_target_too(self):
with SharedRepository() as repo:
repo.install_both()
target = repo.root / "elsewhere.bin"
shutil.copy(repo.dest / "gba_bios.bin", target)
(repo.dest / "gba_bios.bin").unlink()
(repo.dest / "gba_bios.bin").symlink_to(target)
proc = repo.run("--remove", "--system", "nintendo-gba", "--yes")
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
self.assertTrue((repo.dest / "gba_bios.bin").is_symlink())
self.assertTrue(target.is_file())
self.assertIn("gba_bios.bin: a link, never followed", proc.stdout)
class ExitCodes(unittest.TestCase):
@unittest.skipIf(pty is None, "a question needs a pseudo-terminal")
def test_zero_one_and_two(self):
with SharedRepository() as repo:
no_record = repo.run("--remove", "--yes")
self.assertEqual(no_record.returncode, 2)
repo.install_both()
asked = repo.run("--remove")
self.assertEqual(asked.returncode, 2)
self.assertIn("add --yes", asked.stderr)
declined, said = repo.answer("n", "--remove")
self.assertEqual(declined, 2, said)
self.assertIn("Remove them for good? [y/N]", said)
self.assertIn("Nothing was removed.", said)
self.assertEqual(len(repo.installed()), 6)
if not (hasattr(os, "geteuid") and os.geteuid() == 0):
(repo.dest / "pcsx").chmod(0o555)
try:
failed = repo.run("--remove", "--system", "sony-playstation", "--yes")
finally:
(repo.dest / "pcsx").chmod(0o755)
self.assertEqual(failed.returncode, 1, failed.stdout + failed.stderr)
self.assertIn("failed: pcsx/extra.bin", failed.stderr)
# The record keeps what is still there.
self.assertIn("pcsx/extra.bin",
[item["dest"] for item in repo.setup()["files"]])
done = repo.run("--remove", "--yes")
self.assertEqual(done.returncode, 0, done.stdout + done.stderr)
nothing = repo.run("--remove", "--yes")
self.assertEqual(nothing.returncode, 2)
def test_options_an_action_does_not_read_are_refused(self):
with SharedRepository() as repo:
for argv, words in (
(("--file", "a.bin"), "--file applies to --remove"),
(("--backup",), "--backup applies to --remove"),
(("--remove", "--region", "us"), "--remove takes no --region"),
(("--remove", "--system", "nintendo-gba", "--core", "mgba"),
"not --system and --core"),
(("--remove", "--backups"), "two actions"),
(("--backups", "--yes"), "--backups takes no --yes"),
):
with self.subTest(argv=argv):
proc = repo.run(*argv)
self.assertEqual(proc.returncode, 2)
self.assertIn(words, proc.stderr)
class WithoutARecord(unittest.TestCase):
def test_a_missing_or_damaged_record_removes_nothing_in_one_line(self):
with SharedRepository() as repo:
repo.install_both()
before = repo.snapshot()
repo.record.write_text("{not json")
proc = repo.run("--remove", "--yes")
self.assertEqual(proc.returncode, 2)
self.assertEqual(repo.snapshot(), before)
self.assertEqual(proc.stderr.strip().count("\n"), 0, proc.stderr)
self.assertIn("Nothing was removed", proc.stderr)
self.assertIn("is damaged", proc.stderr)
repo.record.unlink()
proc = repo.run("--remove", "--yes")
self.assertEqual(proc.returncode, 2)
self.assertEqual(proc.stderr.strip().count("\n"), 0, proc.stderr)
self.assertIn("no install record", proc.stderr)
self.assertEqual(repo.snapshot(), before)
class Events(unittest.TestCase):
def test_every_new_kind(self):
with SharedRepository() as repo:
repo.install_both()
plan = repo.events(repo.run("--remove", "--system", "nintendo-gba", "--dry-run",
"--events"))
self.assertEqual([event["event"] for event in plan],
["start", "removal_plan", "end"])
self.assertEqual(plan[0]["mode"], "remove")
self.assertEqual((plan[1]["files"], plan[1]["bytes"], plan[1]["dests"]),
(2, 12, GBA_ONLY))
self.assertEqual(plan[1]["kept"], [{"dest": "shared.bin", "reason": "shared"}])
run = repo.events(repo.run("--remove", "--system", "nintendo-gba", "--backup",
"--events", "--yes"))
kinds = [event["event"] for event in run]
self.assertEqual(kinds, ["start", "removal_plan", "removed", "removed", "backup",
"end"])
self.assertTrue(all(event["ok"] and event["moved_to"]
for event in run if event["event"] == "removed"))
self.assertEqual(run[4]["status"], "made")
listed = repo.events(repo.run("--backups", "--events"))
self.assertEqual([event["event"] for event in listed], ["start", "backup", "end"])
self.assertEqual((listed[0]["mode"], listed[1]["status"], listed[1]["files"]),
("list", "listed", 2))
folder = listed[1]["folder"]
back = repo.events(repo.run("--restore", folder, "--events"))
self.assertEqual([event["event"] for event in back],
["start", "restored", "restored", "end"])
self.assertEqual(back[0]["mode"], "restore")
gone = repo.events(repo.run("--delete-backup", folder, "--events", "--yes"))
self.assertEqual([event["event"] for event in gone], ["start", "backup", "end"])
self.assertEqual((gone[1]["status"], gone[2]["code"]), ("deleted", 0))
self.assertFalse(Path(folder).exists())
for event in plan + run + listed + back + gone:
self.assertEqual(sorted(event), sorted(
["event", *install.EVENT_KEYS[event["event"]]]))
class DeleteABackup(unittest.TestCase):
def test_only_a_backup_folder_and_only_with_a_yes(self):
with SharedRepository() as repo:
repo.install_both()
repo.run("--remove", "--system", "nintendo-gba", "--backup", "--yes")
[backup] = install.list_backups(repo.dest)
asked = repo.run("--delete-backup", str(backup["folder"]))
self.assertEqual(asked.returncode, 2)
self.assertTrue(backup["folder"].is_dir())
refused = repo.run("--delete-backup", str(repo.dest), "--yes")
self.assertEqual(refused.returncode, 2)
self.assertTrue((repo.dest / "shared.bin").is_file())
done = repo.run("--delete-backup", str(backup["folder"]), "--yes")
self.assertEqual(done.returncode, 0, done.stdout + done.stderr)
self.assertFalse(backup["folder"].exists())
def _record(folder: Path, files: list[dict], others: dict | None = None,
choice: list[str] | None = None) -> dict:
setup = {"written": "2026-11-03T12:00:00Z", "list": {"version": "v1", "generated": ""},
"choice": choice or ["--all-files"], "files": files}
return {"schema": 1, "folder": str(folder), "setups": {"retroarch": setup, **(others or {})}}
def _item(dest: str, payload: bytes, root: str = "") -> dict:
return {"dest": dest, "root": root, "sha1": hashlib.sha1(payload).hexdigest(),
"size": len(payload), "from": "download"}
def _served(*dests_payloads: tuple[str, bytes]) -> dict:
return {"files": [{"dest": dest, "sha1": hashlib.sha1(payload).hexdigest(),
"size": len(payload), "cores": None, "systems": ["nintendo-gba"]}
for dest, payload in dests_payloads]}
class ACraftedRecord(unittest.TestCase):
"""A record anyone could write never makes the installer delete outside
the BIOS folder, through a link, or anything but a regular file."""
def setUp(self):
self.root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
self.addCleanup(shutil.rmtree, self.root, True)
self.folder = self.root / "bios"
self.folder.mkdir()
@unittest.skipUnless(hasattr(os, "mkfifo"), "a FIFO is POSIX")
def test_nothing_outside_or_through_a_link_is_removed(self):
payload = b"precious"
(self.root / "outside.bin").write_bytes(payload)
elsewhere = self.root / "elsewhere"
elsewhere.mkdir()
(elsewhere / "deep.bin").write_bytes(payload)
(self.folder / "linked").symlink_to(elsewhere)
(self.folder / "link.bin").symlink_to(self.root / "outside.bin")
os.mkfifo(self.folder / "pipe.bin")
dests = ["../outside.bin", str(self.root / "outside.bin"), "linked/deep.bin",
"link.bin", "pipe.bin", "a/../../outside.bin", "C:/outside.bin"]
record = _record(self.folder, [_item(dest, payload) for dest in dests]
+ [_item("saved.bin", payload, "save")])
manifest = _served(*[(dest, payload) for dest in [*dests, "saved.bin"]])
removal = install.plan_removal("retroarch", self.folder, record, manifest)
self.assertEqual(removal.remove, [])
reasons = {item["dest"]: item["reason"] for item in removal.kept}
self.assertEqual(reasons, {
"../outside.bin": "outside", str(self.root / "outside.bin"): "outside",
"linked/deep.bin": "link", "link.bin": "link", "pipe.bin": "not_a_file",
"a/../../outside.bin": "outside", "C:/outside.bin": "outside",
"saved.bin": "outside"})
install.remove_files(removal, record["setups"]["retroarch"], False)
self.assertEqual((self.root / "outside.bin").read_bytes(), payload)
self.assertEqual((elsewhere / "deep.bin").read_bytes(), payload)
def test_a_file_swapped_after_the_plan_stays(self):
payload = b"ours"
(self.folder / "a.bin").write_bytes(payload)
record = _record(self.folder, [_item("a.bin", payload)])
removal = install.plan_removal("retroarch", self.folder, record,
_served(("a.bin", payload)))
self.assertEqual(len(removal.remove), 1)
(self.folder / "a.bin").unlink()
(self.folder / "a.bin").symlink_to(self.root / "nowhere")
removed, failed, _run = install.remove_files(removal, record["setups"]["retroarch"],
False)
self.assertEqual((removed, [item["dest"] for item in failed]), ([], ["a.bin"]))
self.assertTrue((self.folder / "a.bin").is_symlink())
def test_a_file_the_list_no_longer_serves_stays(self):
payload = b"a key the user supplied"
(self.folder / "keys.bin").write_bytes(payload)
record = _record(self.folder, [_item("keys.bin", payload)])
removal = install.plan_removal("retroarch", self.folder, record, {"files": []})
self.assertEqual([item["reason"] for item in removal.kept], ["not_servable"])
other = install.plan_removal("retroarch", self.folder, record,
_served(("keys.bin", b"another release")))
self.assertEqual([item["reason"] for item in other.kept], ["not_servable"])
class AFoldingDrive(unittest.TestCase):
"""FAT and exFAT open a name whatever its case: the record, the list and
another setup are compared folded there."""
def setUp(self):
self.root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
self.addCleanup(shutil.rmtree, self.root, True)
self.folder = self.root / "bios"
self.folder.mkdir()
self.payload = b"bios"
(self.folder / "SCPH5501.BIN").write_bytes(self.payload)
def plan(self, keeps_case: bool, others: dict | None = None) -> install.Removal:
record = _record(self.folder, [_item("SCPH5501.BIN", self.payload)], others)
with mock.patch.object(install, "keeps_case", return_value=keeps_case):
return install.plan_removal("retroarch", self.folder, record,
_served(("scph5501.bin", self.payload)))
def test_the_list_s_name_in_another_case_is_the_same_file(self):
self.assertEqual([entry["dest"] for entry in self.plan(False).remove],
["SCPH5501.BIN"])
self.assertEqual([item["reason"] for item in self.plan(True).kept], ["not_servable"])
def test_another_setup_s_file_in_another_case_stays(self):
other = {"lakka": {"written": "", "choice": ["--all-files"],
"files": [_item("scph5501.bin", self.payload)]}}
removal = self.plan(False, other)
self.assertEqual([(item["reason"], item["what"]) for item in removal.kept],
[("shared", "Lakka")])
def test_the_removal_runs_on_a_folding_drive(self):
removal = self.plan(False)
record = _record(self.folder, [_item("SCPH5501.BIN", self.payload)])
with contextlib.redirect_stdout(io.StringIO()):
removed, failed, _run = install.remove_files(
removal, record["setups"]["retroarch"], False)
self.assertEqual((len(removed), failed), (1, []))
self.assertFalse((self.folder / "SCPH5501.BIN").exists())
class RestoreAnInstallBackup(unittest.TestCase):
def test_the_record_lets_go_of_the_user_s_files_put_back(self):
with SharedRepository() as repo:
repo.dest.mkdir(parents=True)
(repo.dest / "gba_bios.bin").write_bytes(b"the user's own")
repo.install_both()
[backup] = install.list_backups(repo.dest)
self.assertEqual(backup["source"], "install")
self.assertIn("gba_bios.bin", [item["dest"] for item in repo.setup()["files"]])
proc = repo.run("--restore", str(backup["folder"]))
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
self.assertEqual((repo.dest / "gba_bios.bin").read_bytes(), b"the user's own")
self.assertNotIn("gba_bios.bin", [item["dest"] for item in repo.setup()["files"]])
class ReviewOfTheRemoval(unittest.TestCase):
"""What a review of the first version found, each held by a test."""
def setUp(self):
self.root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
self.addCleanup(shutil.rmtree, self.root, True)
self.folder = self.root / "bios"
self.folder.mkdir()
def test_restore_keeps_the_file_in_place_aside(self):
with SharedRepository() as repo:
repo.install_both()
proc = repo.run("--remove", "--system", "nintendo-gba", "--backup", "--yes")
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
[backup] = install.list_backups(repo.dest)
(repo.dest / "gba_bios.bin").write_bytes(b"a newer one the user put")
proc = repo.run("--restore", str(backup["folder"]))
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
self.assertEqual((repo.dest / "gba_bios.bin").read_bytes(), b"gba bios")
self.assertIn("Kept 1 file that were in their place", proc.stdout)
kept = [item for item in install.list_backups(repo.dest)
if item["folder"] != backup["folder"]]
self.assertEqual([item["files"] for item in kept], [1])
[saved] = [path for path in kept[0]["folder"].rglob("gba_bios.bin")]
self.assertEqual(saved.read_bytes(), b"a newer one the user put")
# The same bytes in place: nothing more is kept.
again = repo.run("--restore", str(backup["folder"]))
self.assertEqual(again.returncode, 0, again.stdout + again.stderr)
self.assertNotIn("Kept", again.stdout)
def _plan(self, payload: bytes, manifest: dict | None = None) -> tuple:
(self.folder / "a.bin").write_bytes(payload)
record = _record(self.folder, [_item("a.bin", payload)])
return record, install.plan_removal(
"retroarch", self.folder, record, manifest or _served(("a.bin", payload)))
def test_a_file_edited_in_place_after_the_plan_stays(self):
record, removal = self._plan(b"ours")
path = self.folder / "a.bin"
before = os.stat(path)
with open(path, "r+b") as handle:
handle.write(b"user")
# The same inode, size and even time: only its bytes tell.
os.utime(path, ns=(before.st_atime_ns, before.st_mtime_ns))
removed, failed, _run = install.remove_files(removal, record["setups"]["retroarch"],
False)
self.assertEqual(removed, [])
self.assertEqual([(item["reason"], item["changed"]) for item in failed],
[(install.CHANGED_SINCE_PLAN, True)])
self.assertEqual(path.read_bytes(), b"user")
def test_a_file_that_failed_keeps_its_entry(self):
with SharedRepository() as repo:
repo.install_both()
if hasattr(os, "geteuid") and os.geteuid() == 0:
self.skipTest("root deletes anywhere")
(repo.dest / "pcsx").chmod(0o555)
try:
proc = repo.run("--remove", "--yes")
finally:
(repo.dest / "pcsx").chmod(0o755)
self.assertEqual(proc.returncode, 1, proc.stdout + proc.stderr)
self.assertEqual([item["dest"] for item in repo.setup()["files"]],
["pcsx/extra.bin"])
def test_an_unmounted_folder_or_a_refusal_leaves_the_record(self):
with SharedRepository() as repo:
repo.install_both()
record = repo.record.read_bytes()
for path in repo.dest.rglob("*"):
if path.is_file():
path.unlink()
asked = repo.run("--remove")
self.assertEqual(asked.returncode, 2)
self.assertEqual(repo.record.read_bytes(), record)
nothing = repo.run("--remove", "--system", "nintendo-gba", "--yes")
self.assertEqual(nothing.returncode, 2)
self.assertEqual(repo.record.read_bytes(), record)
shutil.rmtree(repo.dest)
gone = repo.run("--remove", "--yes")
self.assertEqual(gone.returncode, 2)
self.assertEqual(gone.stderr.strip().count("\n"), 0, gone.stderr)
self.assertIn("is not there", gone.stderr)
self.assertEqual(repo.record.read_bytes(), record)
def junction(self, path: Path):
"""lstat as Windows reads a directory junction at path, which Python
before 3.12 does not report as a link."""
real = os.lstat
def lstat(target, *args, **kwargs):
info = real(target, *args, **kwargs)
if Path(target) != path:
return info
return types.SimpleNamespace(**{name: getattr(info, name) for name in (
"st_mode", "st_ino", "st_dev", "st_size", "st_mtime_ns", "st_uid")},
st_file_attributes=0x400)
return mock.patch.object(install.os, "lstat", side_effect=lstat)
def test_a_junction_is_a_link(self):
(self.folder / "linked").mkdir()
payload = b"elsewhere"
(self.folder / "linked" / "deep.bin").write_bytes(payload)
record = _record(self.folder, [_item("linked/deep.bin", payload)])
with self.junction(self.folder / "linked"):
removal = install.plan_removal("retroarch", self.folder, record,
_served(("linked/deep.bin", payload)))
self.assertEqual([item["reason"] for item in removal.kept], ["link"])
def test_a_backup_holding_a_junction_is_never_entered(self):
holder = self.root / install.BACKUP_DIR
run = holder / "20261103-120000"
(run / "bios").mkdir(parents=True)
(run / install.BACKUP_MARKER).write_text("marker")
(run / "bios" / "x.bin").write_bytes(b"kept")
junction = run / "bios" / "elsewhere"
junction.mkdir()
(junction / "precious.bin").write_bytes(b"not the backup's")
with self.junction(junction):
self.assertEqual(install._backup_files(run), [run / "bios" / "x.bin"])
args = types.SimpleNamespace(delete_backup=run, dry_run=False, yes=True,
no_input=True)
with contextlib.redirect_stdout(io.StringIO()), \
contextlib.redirect_stderr(io.StringIO()):
install.run_delete_backup(args)
self.assertEqual((junction / "precious.bin").read_bytes(), b"not the backup's")
self.assertFalse((run / "bios" / "x.bin").exists())
def test_a_core_never_takes_a_file_the_platform_lists_too(self):
payload = b"bios"
manifest = {"files": [
{"dest": "a.bin", "sha1": hashlib.sha1(payload).hexdigest(), "size": 4,
"cores": ["mgba"], "systems": ["nintendo-gba"]},
{"dest": "a.bin", "sha1": hashlib.sha1(payload).hexdigest(), "size": 4,
"cores": None, "systems": ["nintendo-gba"]}]}
(self.folder / "a.bin").write_bytes(payload)
record = _record(self.folder, [_item("a.bin", payload)])
removal = install.plan_removal("retroarch", self.folder, record, manifest,
cores=["mgba"])
self.assertEqual(removal.remove, [])
self.assertTrue((self.folder / "a.bin").exists())
def test_two_names_one_file_are_planned_once(self):
payload = b"bios"
(self.folder / "A.BIN").write_bytes(payload)
record = _record(self.folder, [_item("A.BIN", payload), _item("a.bin", payload)])
# The drive opens a.bin as A.BIN, as FAT does.
found = ("", os.lstat(self.folder / "A.BIN"))
with mock.patch.object(install, "keeps_case", return_value=False), \
mock.patch.object(install, "_check_ours", return_value=found):
removal = install.plan_removal("retroarch", self.folder, record,
_served(("a.bin", payload)))
self.assertEqual([entry["dest"] for entry in removal.remove], ["A.BIN"])
def test_a_restore_into_a_moved_folder_says_the_record_was_not_updated(self):
with SharedRepository() as repo:
repo.install_both()
repo.run("--remove", "--system", "nintendo-gba", "--backup", "--yes")
[backup] = install.list_backups(repo.dest)
moved = repo.root.with_name(repo.root.name + "-moved")
repo.root.rename(moved)
try:
run = moved / install.BACKUP_DIR / backup["folder"].name
with contextlib.redirect_stdout(io.StringIO()), \
contextlib.redirect_stderr(io.StringIO()) as err:
code = install.restore_kept(run)
self.assertEqual(code, 1)
self.assertIn("The install record could not be updated", err.getvalue())
self.assertTrue((moved / "dest" / "gba_bios.bin").is_file())
finally:
moved.rename(repo.root)
if __name__ == "__main__":
unittest.main()
+201 -3
View File
@@ -1063,9 +1063,83 @@ def golden_states() -> dict:
told.stack.append("details")
states["result-details"] = told
states["plan-record"] = record_state()
states["more"] = more_state("more")
states["remove"] = more_state("more", "remove")
states["removal"] = removal_state()
states["removal-confirm"] = removal_state()
install.tui_update(states["removal-confirm"], Key("enter"))
states["removal-done"] = removal_done_state()
states["backups"] = backups_state()
return states
# What an install of the PlayStation and the Game Boy Advance recorded.
MORE_RECORD = {"written": "2026-11-03T12:00:00Z", "list": {"version": "v1", "generated": ""},
"choice": ["--all-files"],
"files": [{"dest": dest, "root": "", "sha1": "0" * 40, "size": size,
"from": "download"}
for dest, size in (("gba_bios.bin", 16384), ("pcsx/extra.bin", 4),
("scph5500.bin", 524288))]}
REMOVAL_PLAN = {"event": "removal_plan", "platform": "retroarch", "folder": str(SYSTEM_DIR),
"files": 2, "bytes": 524292, "systems": ["sony-playstation"],
"dests": ["pcsx/extra.bin", "scph5500.bin"],
"kept": [{"dest": "scph5501.bin", "reason": "changed"}], "gone": 0,
"backup": False, "message": ""}
BACKUPS = [{"event": "backup", "folder": str(HOME / ".config" / "retroarch" / "retrobios-backup"
/ name), "date": date, "files": files,
"bytes": size, "source": source, "status": "listed", "message": ""}
for name, date, files, size, source in (
("20261103-120000", "2026-11-03 12:00:00", 2, 524292, "remove"),
("20261020-093000", "2026-10-20 09:30:00", 1, 16384, "install"))]
def more_state(*screens: str) -> install.TuiState:
"""The plan of a folder whose record holds three files, More opened."""
state = checked_state()
state.record = json.loads(json.dumps(MORE_RECORD))
for screen in screens:
rows = install.tui_rows(state)
state.focus[state.screen] = [row.action for row in rows].index(screen)
assert install.tui_update(state, Key("enter")) == [], screen
return state
def removal_state(system: bool = True) -> install.TuiState:
"""The removal plan of the PlayStation, read by its dry run."""
state = more_state("more", "remove")
values = [row.value for row in install.tui_rows(state)]
state.focus["remove"] = values.index("sony-playstation") if system else 0
effects = install.tui_update(state, Key("enter"))
assert effects == [("run", "removal_plan")], effects
install.tui_receive(state, ("event", state.run_id, REMOVAL_PLAN))
install.tui_receive(state, ("ended", state.run_id, 0, []))
return state
def removal_done_state() -> install.TuiState:
state = removal_state()
install.tui_update(state, Key("enter"))
effects = install.tui_update(state, Key("enter"))
assert effects == [("run", "remove")], effects
for dest, size in (("pcsx/extra.bin", 4), ("scph5500.bin", 524288)):
install.tui_receive(state, ("event", state.run_id, {
"event": "removed", "platform": "retroarch", "dest": dest, "size": size,
"ok": True, "reason": "", "moved_to": ""}))
install.tui_receive(state, ("ended", state.run_id, 0, []))
return state
def backups_state(purpose: str = "backups") -> install.TuiState:
state = more_state("more")
state.focus["more"] = [row.action for row in install.tui_rows(state)].index(purpose)
effects = install.tui_update(state, Key("enter"))
assert effects == [("run", "backups")], effects
for event in BACKUPS:
install.tui_receive(state, ("event", state.run_id, event))
install.tui_receive(state, ("ended", state.run_id, 0, []))
return state
# A board list as install/targets/ publishes it, read by Device: the boards
# by the names the list gives them, the device images each one runs.
TARGETS = {"bcm2711": {"cores": ["pcsx_rearmed"], "images": ["rpi4"]},
@@ -1187,7 +1261,7 @@ class RecordedPlan(unittest.TestCase):
self.assertTrue(state.opened_on_plan)
self.assertEqual(state.stack, ["setup", "plan"])
self.assertEqual(install.tui_rows(state)[0].action, "install")
press(state, "end", "up", "up", "up")
press(state, "end", "up", "up", "up", "up")
self.assertEqual(install.tui_rows(state)[state.focus["plan"]].label, "Change where")
press(state, "enter")
self.assertEqual(state.screen, "setup")
@@ -15182,8 +15256,9 @@ SCREEN_WORDS = {
"result-details": (30, 30, 19, 19),
# The plan's words, plus the recorded choice by name, its row (the one
# press back to the whole list) and Change where (back to the setups,
# the plan having opened on the one found).
"plan-record": (38, 42, 42, 42),
# the plan having opened on the one found), and More, the record being
# what a removal reads.
"plan-record": (38, 42, 43, 43),
# Change holds every option one level below the plan (design 4.4): the
# rows for Emulators, a pack, the device and the internet, and the five
# headings that group them, the intro sentence they replace dropped.
@@ -15191,6 +15266,14 @@ SCREEN_WORDS = {
# A list of boards and one of emulators, each with the line that says
# what the choice does and the gloss of "board".
"device": (35, 35, 33, 33), "emulators": (29, 29, 27, 27),
# A plan names More (one word) where the folder has a record or a
# backup (design 4.3), the first install's plan unchanged. More's
# four rows, Remove files with each system the record holds and its
# size, the removal plan (what goes, the space freed, the folder, what
# stays and why), its confirm and result, and the backups newest first.
"more": (17, 17, 15, 15), "remove": (31, 31, 29, 29), "removal": (34, 34, 32, 32),
"removal-confirm": (18, 18, 16, 16), "removal-done": (18, 18, 16, 16),
"backups": (24, 24, 22, 22),
}
@@ -15200,6 +15283,121 @@ def screen_words(lines: list) -> int:
if re.search(r"[A-Za-z0-9]", token) and "/" not in token)
class MoreScreens(unittest.TestCase):
"""More: removal, the backups, putting one back and deleting one, each
a child run with its own options and none of the plan's."""
def setUp(self):
patcher = mock.patch.object(install, "_mounted_cards", return_value=[])
patcher.start()
self.addCleanup(patcher.stop)
def test_a_plan_names_more_where_it_has_something_to_act_on(self):
self.assertNotIn("more", [row.action for row in install.tui_rows(checked_state())])
self.assertEqual([row.action for row in install.tui_rows(more_state())][-2:],
["more", "quit"])
done = golden_states()["plan-in-place"]
done.has_backups = True
self.assertEqual(install.tui_rows(done)[-1].action, "more")
def test_the_removal_plan_asks_its_own_child(self):
state = removal_state()
state.regions = ["japan"]
argv = install.tui_argv(state, "removal_plan", "python", "install.py")
self.assertEqual(argv[-4:], ["--remove", "--system=sony-playstation", "--dry-run",
"--events"])
self.assertNotIn("--region=japan", argv)
self.assertIn(f"--dest={SYSTEM_DIR}", argv)
self.assertEqual(state.screen, "removal")
self.assertEqual(install.tui_rows(state)[0].label, "Remove 512.0 KB")
def test_everything_removes_with_no_system(self):
state = removal_state(system=False)
argv = install.tui_argv(state, "removal_plan", "python", "install.py")
self.assertEqual(argv[-3:], ["--remove", "--dry-run", "--events"])
def test_a_backup_is_one_row_away_and_the_removal_says_so(self):
state = removal_state()
state.focus["removal"] = 1
press(state, "enter")
self.assertTrue(state.removal_backup)
self.assertEqual(install.tui_rows(state)[0].label, "Move 512.0 KB to a backup")
state.focus["removal"] = 0
press(state, "enter")
self.assertEqual(state.screen, "actconfirm")
effects = press(state, "enter")
self.assertEqual(effects, [("run", "remove")])
argv = install.tui_argv(state, "remove", "python", "install.py")
self.assertEqual(argv[-4:], ["--system=sony-playstation", "--backup", "--events",
"--yes"])
def test_close_ends_with_the_removal_s_code_and_back_counts_again(self):
state = removal_done_state()
self.assertEqual(state.screen, "actdone")
self.assertIn("Removed 2 files, 512.0 KB freed.",
screen_text(install.tui_view(state, 80, 24)))
press(state, "enter")
self.assertEqual(state.exit_code, 0)
state = removal_done_state()
with mock.patch.object(install, "setup_record", return_value=(None, None, "")):
effects = press(state, "esc")
self.assertEqual(effects, [("run", "check")])
self.assertEqual(state.screen, "checking")
# The record is read again: the removal changed it.
self.assertIsNone(state.record)
def test_a_failure_is_said_and_ends_on_one(self):
state = removal_state()
press(state, "enter", "enter")
install.tui_receive(state, ("event", state.run_id, {
"event": "removed", "platform": "retroarch", "dest": "scph5500.bin",
"size": 524288, "ok": False, "reason": "Permission denied", "moved_to": ""}))
install.tui_receive(state, ("ended", state.run_id, 1, []))
self.assertIn("1 file failed.", screen_text(install.tui_view(state, 80, 24)))
press(state, "enter")
self.assertEqual(state.exit_code, 1)
def test_put_back_and_delete_name_the_backup_alone(self):
folder = BACKUPS[0]["folder"]
for purpose, run, argv in (
("putback", "restore", [f"--restore={folder}", "--events"]),
("dropbackup", "delete_backup",
[f"--delete-backup={folder}", "--events", "--yes"])):
with self.subTest(purpose=purpose):
state = backups_state(purpose)
press(state, "enter")
self.assertEqual(state.screen, "actconfirm")
self.assertEqual(press(state, "enter"), [("run", run)])
self.assertEqual(install.tui_argv(state, run, "python", "install.py"),
["python", "install.py", *argv])
def test_a_backup_looked_at_offers_both(self):
state = backups_state()
press(state, "down", "enter")
self.assertEqual(state.screen, "backup")
self.assertEqual(state.backup_chosen["date"], BACKUPS[1]["date"])
self.assertEqual([row.action for row in install.tui_rows(state)],
["bkrestore", "bkdelete", "back"])
argv = install.tui_argv(state, "backups", "python", "install.py")
self.assertEqual(argv[-2:], ["--backups", "--events"])
def test_without_a_record_nothing_is_offered_to_remove(self):
state = checked_state()
state.record = None
state.stack += ["more", "remove"]
self.assertEqual([row.action for row in install.tui_rows(state)], ["back"])
self.assertIn("No install record here", screen_text(install.tui_view(state, 80, 24)))
def test_the_files_and_what_stays_are_on_details(self):
state = removal_state()
state.focus["removal"] = [row.action for row in install.tui_rows(state)].index(
"details")
press(state, "enter")
text = screen_text(install.tui_view(state, 80, 24))
self.assertIn("pcsx/extra.bin", text)
self.assertIn("scph5501.bin: changed since it was installed", text)
class ScreenWordBudgets(unittest.TestCase):
def setUp(self):
patcher = mock.patch.object(install, "_mounted_cards", return_value=[])
+95 -7
View File
@@ -795,7 +795,12 @@ Kept 1 file already there (512.0 KB), which the collection's copies replaced, in
```
`--restore FOLDER` copies every file of that folder back to its place and
leaves the folder as it is, to delete once the emulators start as before.
leaves the folder as it is, to delete once the emulators start as before. A
file at that place with other contents (a newer one, one put there by hand) is
first moved into a new dated backup, as an install keeps what it replaces, and
the end of the run names that folder. The installer's own copy, as the
install record names it, is replaced without one: the collection gives it
again.
Copying the contents of the dated folder into the folder that holds
`retrobios-backup` does the same by hand. `--dry-run --restore FOLDER` lists
what would go back. `--dry-run` alone counts the files an install would keep.
@@ -821,6 +826,80 @@ beyond what it already used. A link sitting at a file's name is not kept: the
file it points to is never touched. Data trees and `--standalone-copies` keep
their replaced files the same way, beside the folder they were in.
## Removing files { #removing-files }
`--remove` takes out of the BIOS folder what the installer put there, and only
that. The [install record](#the-install-record) says which files these are, so
a folder without one loses nothing:
```
python3 install.py --remove --system nintendo-gba
Remove 2 files from ~/.config/retroarch/system for RetroArch, 12.0 KB freed.
1 file stays: 1 still needed.
shared.bin: still needed by PlayStation
Remove them for good? [y/N]
```
Without a narrowing option every file of the setup's record goes. `--system`,
`--core` or `--emulator` takes one part, and `--file PATH` one file. The
question comes once, names what goes and the space freed, and only `y` or
`yes` goes on. `--dry-run` lists the files and removes nothing, and `--yes`
answers it for a script. A run without a terminal and without `--yes` stops
with code 2 and names `--yes`.
Removal deletes for good. `--backup` moves the files into a new dated folder of
`retrobios-backup` instead, and the end of the run prints the `--restore`
command that puts them back. On one drive a move frees no space until the
backup is deleted.
A file is removed only when all of these hold:
- the record lists it for this setup and this folder
- its bytes are still the SHA-1 the record holds
- it is a regular file, below the BIOS folder, reached through no link
- the setup's current list still serves that SHA-1 at that place, so a later
install can put it back.
Anything else stays and is named with its reason: a file changed since, a
link, a file outside the BIOS folder (the save directory's included), a file
the list no longer serves (a key the user supplied, a dump of another
release), and a file still needed by what stays. A file two systems share
stays while one of them is still chosen, and a file another setup of the same
folder recorded stays too. Removing everything also counts the files the
record never names, added by hand or by another program, and leaves them.
A record that cannot be read is said in one line, and nothing is removed. So
is a BIOS folder that is not there (a card not mounted), with code 2. Each
file is read and hashed again just before it goes: one edited during the
question stays, as changed since the plan.
On FAT and exFAT cards, which open a name whatever its case, names are
compared without case.
The record is updated only after the yes, and only for what was done: the
files removed leave it, the systems or cores removed leave its choice, a file
that stayed (changed, failed, still needed) keeps its entry, and a setup with
no file left goes from it. `--restore` on a removal's backup gives the files back to the record with
the choice they had. A folder moved since the removal is said, the record
left as it is. On a backup of files an install replaced, the record lets
go of each file put back: it is the user's again.
`--backups` lists the backups of the BIOS folder, newest first, and
`--delete-backup FOLDER` deletes one after a question naming its date and
size. Only a dated folder holding `.retrobios-backup`, inside
`retrobios-backup`, is ever deleted, and no link inside it is followed, a
Windows junction included. On the
full screen, **More** on the plan, shown once the folder has a record or a
backup, holds **Remove files**, **Backups**, **Put back a backup** and
**Delete a backup**. Remove files lists every recorded file or one system,
then the plan of the removal (what goes, the space freed, what stays and why,
the files behind **Details**), **Keep a backup**, and a confirm before
anything is removed.
Exit codes: `0` once done, `1` when a file or the record could not be
written, `2` when nothing was done (no record, nothing to remove, no `--yes`
without a terminal, or no at the question). With `--events` the run writes
`removal_plan`, `removed`, `backup` and `restored` events, see
[Machine-readable events](#machine-readable-events).
## Copy from an old card { #copy-from-an-old-card }
The card a handheld came with, a backup of an old BIOS folder, a Downloads
@@ -1137,7 +1216,12 @@ what the options name, everything by default.
| <span id="option-from-pack"></span>`--from-pack PATH` | Install from a pack already downloaded: its ZIP, one of its parts (the others are found in the same folder), or the folder holding them. Makes no network request and checks every file's hash. See [Without a network](#without-a-network-offline-install) |
| <span id="option-offline"></span>`--offline` | Make no network request. `--check`, `--from`, `--from-pack` and `--manifest` work without one, the file list stops the run at once when none of them gives it, and a file the run would download fails at once. `RETROBIOS_INSTALL_OFFLINE=1` does the same. `--target` and `--list-targets` read the core list per board a pack carries, or `targets/<platform>.json` beside `--manifest`, and are refused when neither gives one |
| <span id="option-manifest"></span>`--manifest FILE` | Read the file list from FILE, a copy of `install/<platform>.json`, instead of downloading it. See [Without a network](#without-a-network-offline-install) |
| <span id="option-restore"></span>`--restore FOLDER` | Put back the files a run replaced and kept in FOLDER, a dated folder inside `retrobios-backup`. Takes no other option but `--dry-run` and `--no-input`. See [Files already in the folder](#files-you-already-had) |
| <span id="option-restore"></span>`--restore FOLDER` | Put back the files a run replaced or a removal moved and kept in FOLDER, a dated folder inside `retrobios-backup`, and update the install record. Takes no other option but `--dry-run`, `--events`, `--yes`, `--no-input` and `--verbose`. See [Files already in the folder](#files-you-already-had) |
| <span id="option-remove"></span>`--remove` | Remove what the installer placed in the BIOS folder, as its [install record](#the-install-record) proves it: every file, or those of one `--system`, `--core`, `--emulator` or `--file`. Deleted for good unless `--backup` is given. See [Removing files](#removing-files) |
| <span id="option-file"></span>`--file PATH` | With `--remove`, one file of the BIOS folder, by its path there or in full. Repeat for several |
| <span id="option-backup"></span>`--backup` | With `--remove`, move the files into a dated folder of `retrobios-backup` instead of deleting them, for `--restore` to put back |
| <span id="option-backups"></span>`--backups` | List the backups of the BIOS folder, newest first, with the date, the files and the size of each |
| <span id="option-delete-backup"></span>`--delete-backup FOLDER` | Delete one backup listed by `--backups`, after one question that names its date and size (`--yes` skips it) |
| <span id="option-tui"></span>`--tui` | Open the full-screen interface, or stop with the reason it cannot open here (code 2) instead of falling back to the questions. Wins over `RETROBIOS_PLAIN` and `ACCESSIBLE`. See [The full-screen interface](#the-full-screen-interface) |
| <span id="option-plain"></span>`--plain` | Ask the questions line by line instead of the full-screen interface, the mode for a screen reader. See [The full-screen interface](#the-full-screen-interface) |
| <span id="option-version"></span>`--version` | Print the installer's identity, the first 12 hex digits of the SHA-256 the bootstraps pin |
@@ -1935,9 +2019,9 @@ fields, the installer refuses to narrow rather than install everything.
| Code | Meaning |
|------|---------|
| `0` | everything asked for is installed or already in place |
| `1` | a failure: the one-liner could not download or verify `install.py`, or found no Python 3.8 with its ssl, unicodedata and zlib modules (nothing was run), an unusable base URL, `--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 or were left out by `--offline` while others installed, 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, a BIOS folder that is a file or lies under one, a `--dest` that is not the card of the system named (another system's card, a folder inside one, a drive with none of its files where its card always has them, or a folder that does not exist), `--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), or `--offline` left every file out, 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`, `--target` with `--from-pack` or `--offline` (or `RETROBIOS_INSTALL_OFFLINE` set) when no core list per board is at hand (the pack carries none and none is beside `--manifest` as `targets/<platform>.json`), or for a platform that publishes none, which is said to have no hardware targets, `--list-targets` with `--offline` and neither, `--add-app` with `--check`, `--dry-run`, a listing, `--from-pack` or `--offline`. The message then starts with `install.py: error:`. Also an unknown platform, target, system, core or emulator, said in an `Error:` or `Unknown platform` line and an `error` event whose `stage` is `option`. 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`. Also a quit chosen before the install, as `gh` ends a cancelled prompt: `q` at a question, `q` or **Quit** on the full screen, which then keeps `RetroBIOS: quit. Nothing was installed.`, and the full screen closed with no button pressed (`RETROBIOS_IDLE_EXIT`). No code but `0` closes a Windows Terminal tab started on the installer itself, so such a tab stays open on a quit |
| `0` | everything asked for is installed or already in place, or removed, put back or deleted |
| `1` | a failure: the one-liner could not download or verify `install.py`, or found no Python 3.8 with its ssl, unicodedata and zlib modules (nothing was run), an unusable base URL, `--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 or were left out by `--offline` while others installed, 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, a BIOS folder that is a file or lies under one, a `--dest` that is not the card of the system named (another system's card, a folder inside one, a drive with none of its files where its card always has them, or a folder that does not exist), `--restore` could not put a file back, `--remove` or `--delete-backup` could not delete a file or update the install record, 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), or `--offline` left every file out, 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`, `--target` with `--from-pack` or `--offline` (or `RETROBIOS_INSTALL_OFFLINE` set) when no core list per board is at hand (the pack carries none and none is beside `--manifest` as `targets/<platform>.json`), or for a platform that publishes none, which is said to have no hardware targets, `--list-targets` with `--offline` and neither, `--add-app` with `--check`, `--dry-run`, a listing, `--from-pack` or `--offline`. The message then starts with `install.py: error:`. Also an unknown platform, target, system, core or emulator, said in an `Error:` or `Unknown platform` line and an `error` event whose `stage` is `option`. 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` or `--delete-backup`, a folder that is not a dated folder inside `retrobios-backup`. With `--remove`, nothing removed: no install record of the setup, nothing it can remove, no `--yes` without a terminal, or no at its question, and the same for `--delete-backup`. Also a quit chosen before the install, as `gh` ends a cancelled prompt: `q` at a question, `q` or **Quit** on the full screen, which then keeps `RetroBIOS: quit. Nothing was installed.`, and the full screen closed with no button pressed (`RETROBIOS_IDLE_EXIT`). No code but `0` closes a Windows Terminal tab started on the installer itself, so such a tab stays open on a quit |
| `3` | `--check` ran and found files to fetch or wrong copies, nothing having failed. A failure takes `1` as before: a manifest refused, a `--from` folder that does not exist. Until November 2026 this case also ended on `1`, so a script that reads any code but `0` as "not ready" needs no change |
| `130` | stopped with Ctrl+C, or with Ctrl+C or Ctrl+D at a question, the terminal keeping `RetroBIOS: stopped. Nothing was installed.` 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`) |
@@ -1981,7 +2065,7 @@ other schemas under `api/v1/schemas/`.
| Kind | Keys | When |
|------|------|------|
| `start` | `schema`, `installer`, `mode` | First line. `schema` is 1, `installer` the identity `--version` prints, `mode` one of `install`, `check`, `dry-run`, `list` |
| `start` | `schema`, `installer`, `mode` | First line. `schema` is 1, `installer` the identity `--version` prints, `mode` one of `install`, `check`, `dry-run`, `list` (also `--backups`), `remove` (`--remove`, `--delete-backup`), `restore` |
| `unsearched` | `folder`, `reason`, `message` | Before `detect`, once for each folder the search for setups could not look in, this user being refused it or a read failing there (a failing card, a link that loops), or under WSL the Windows side when Windows did not answer (`folder` is then the drive mount, `/mnt`). 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), `no_ssl` when this Python was built without its `ssl` module and cannot use HTTPS, 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`), or is a file or lies under one (`reason` `not_a_folder`), and the run ends with code 1 before anything is written. A platform, target, system, core or emulator named on the command line is unknown or cannot be used here (`stage` is `option`, `reason` `unknown`, `platform` the platform it was checked against or the name typed), refused before any download with code 2. The BIOS folder cannot be created (`stage` is `dest`, `reason` `not_writable`): a `summary` follows with what the run installed before it. 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, Batocera or Recalbox named on a computer (their folder is the `bios` folder of their SHARE drive), or no usual folder on this system or one set aside behind a folder this user cannot open (`stage` is `detect`, `reason` `not_found`). So is a platform named by a file list or a pack, in a run without questions where it is found nowhere here. A `--dest` refused as the card of the system named carries the same `stage` and `reason`: another system's card, a folder inside a card, a drive with none of the files its card always has, or a folder that does not exist. `message` says it in words, and is the only thing that tells these cases apart |
@@ -2010,11 +2094,15 @@ other schemas under `api/v1/schemas/`.
| `summary` | `mode`, `installed`, `up_to_date`, `pending`, `failed`, `skipped`, `left_out`, `kept`, `interrupted` | The outcome. `pending` counts what a next run would still fetch, `failed` lists `{dest, reason}`, `skipped` lists `{reason, label, files, required}` for the files the collection cannot supply, `left_out` lists `{category, label, files, dests}` for what `--no-game-data` left out, and is empty without it. `kept` lists `{folder, files, bytes}`, one per backup folder, for the files in place the run moved aside before replacing them (with `--dry-run`, would move), and is empty when none. Not written for a listing |
| `app` | `system`, `folder`, `ok`, `reason`, `dests`, `written`, `kept` | After `summary`, with `--add-app` or a yes to its question: the RetroBIOS app on the card of `system` whose root is `folder`. `dests` lists the app's files below that root, `written` the ones this run wrote (empty when each was in place), `kept` the backup folders as in `summary`. `ok` is false, with `reason`, when the card takes no app (before anything is written, code 2), when the install did not finish, or when a file could not be fetched, verified or written (code 1) |
| `record` | `platform`, `folder`, `path`, `status`, `choice`, `date`, `files`, `message` | The [install record](#the-install-record) of `folder`, kept at `path`. `status` is `applied` before `choices` when the run took its choice, nothing on the command line having chosen, `written` before `summary` once an install placed files, `unreadable` when it cannot be read (said once, ignored), or `not_written` when it could not be saved, the install itself unharmed. `choice` is the recorded options as words, `date` when it was written (UTC), `files` how many files it holds for the platform |
| `removal_plan` | `platform`, `folder`, `files`, `bytes`, `systems`, `dests`, `kept`, `gone`, `backup`, `message` | With `--remove`, before anything is removed, and all a `--dry-run` writes: `dests` the files that go in record order, `bytes` the space they free, `systems` those losing a file, `kept` a `{dest, reason}` for each file left (`shared`, `changed`, `link`, `not_a_file`, `outside`, `not_servable`, `unreadable`, `not_recorded`), `gone` the recorded files no longer there, `backup` whether they are moved rather than deleted. With `files` 0 and a `message` when no record says what is the installer's |
| `removed` | `platform`, `dest`, `size`, `ok`, `reason`, `moved_to` | Each file `--remove` took. `ok` is false, with `reason`, for a file that stays. `moved_to` is its place in the backup with `--backup`, empty otherwise |
| `restored` | `folder`, `path`, `size`, `ok`, `reason` | Each file `--restore` put back from the backup `folder` to `path` |
| `backup` | `folder`, `date`, `files`, `bytes`, `source`, `status`, `message` | A backup folder: `status` is `listed` by `--backups`, `made` by `--remove --backup`, `deleted` or `not_deleted` by `--delete-backup`. `date` is when it was made, local time. `source` is `remove` for a removal's backup and `install` for the files an install replaced |
| `end` | `code` | Last line, with the exit status the process returns (see [Exit status](#exit-status)) |
<!-- /generated: events -->
Schema 1 can gain a kind (`found` came with `--from`, `declared` with `--declared-only`, `unsearched` with the search that goes on past a closed folder, `app` with `--add-app`, `data` with the size of the data trees, `version` with the list's version, `record` with the install record), never a key of an
Schema 1 can gain a kind (`found` came with `--from`, `declared` with `--declared-only`, `unsearched` with the search that goes on past a closed folder, `app` with `--add-app`, `data` with the size of the data trees, `version` with the list's version, `record` with the install record, `removal_plan`, `removed`, `restored` and `backup` with removal), never a key of an
existing kind: a reader skips a kind it does not know. An option refused
before the run starts (`install.py: error:`, code 2) writes
no event at all. A program reads a missing `end` as that case, or as a
+1 -1
View File
@@ -59,7 +59,7 @@ Every module in `tests/`, by the area it guards.
| Packs and manifests | synthetic + real packs | `test_artifact_lock`, `test_atomic_artifacts`, `test_batocera_cores`, `test_core_data_trees`, `test_deterministic_zip`, `test_emulator_pack_counts`, `test_finalize_pack_names`, `test_large_file_cache`, `test_libretro_layout`, `test_manifest_entry`, `test_minui_platform`, `test_no_game_data`, `test_pack_conformance_dirs`, `test_pack_core_extras`, `test_pack_counts`, `test_pack_destinations`, `test_pack_failures`, `test_pack_file_names`, `test_pack_grouping`, `test_pack_integrity`, `test_pack_member_identity`, `test_pack_readme`, `test_platform_inheritance`, `test_platform_keys`, `test_release_packs`, `test_required_only_archives`, `test_required_only_twins`, `test_save_root`, `test_split_extras`, `test_split_names`, `test_split_pack`, `test_system_pack_extras`, `test_system_pack_parity`, `test_torrentzip`, `test_which_pack` |
| Slots and regions | synthetic + profiles | `test_region`, `test_region_agnostic_scan`, `test_region_groups`, `test_region_held`, `test_region_platform_index`, `test_region_required_only`, `test_slot`, `test_slots`, `test_slots_shipped_file` |
| Hardware targets and systems | synthetic + target files | `test_amberelec_targets`, `test_emuelec_targets`, `test_extract_targets`, `test_lakka_targets`, `test_libretro_slugs`, `test_system_ids`, `test_system_selection`, `test_target_names`, `test_target_scrapers`, `test_target_systems`, `test_target_verify` |
| Installer and downloads | loopback server | `test_check_release`, `test_download`, `test_install`, `test_install_cores`, `test_install_experience`, `test_install_filesystems`, `test_install_app`, `test_install_from`, `test_install_games`, `test_install_journeys`, `test_install_offline`, `test_install_selection`, `test_install_tui`, `test_pypi_package` |
| Installer and downloads | loopback server | `test_check_release`, `test_download`, `test_install`, `test_install_cores`, `test_install_experience`, `test_install_filesystems`, `test_install_app`, `test_install_from`, `test_install_games`, `test_install_journeys`, `test_install_offline`, `test_install_removal`, `test_install_selection`, `test_install_tui`, `test_pypi_package` |
| Handheld launchers | a pty as the console, fake installer and firmware tools, stubbed PortMaster dialogs | `test_handheld_launchers`, `test_port`, `test_site_handheld` |
| Running cores | parsers on fixed inputs, a libretro core built from C in the test when a compiler exists | `test_core_probe` |
| Measurement records | synthetic record files, the committed `measurements/` against HEAD, the **Proof** line naming the BIOS a core ran, and that no page shows a Tested block, a region outcome or a time per frame | `test_measurements` |