mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-23 12:19:13 -05:00
feat: add minui, nextui and myminui platforms
minarch hands every core SDCARD_PATH/Bios/<TAG> as its system directory and opens no file in it. The scrapers read each pak's launch.sh (core, tag, devices), each device's core makefile (the project a library is built from: MyMinUI's dosbox_libretro.so is dosbox-pure) and the minarch statement, at a pinned commit. The installer recognises the card from .system/version.txt and refuses a folder inside it.
This commit is contained in:
1 parent
d0802c5360
commit
43bdf83973
17 files changed
+2292
-17
No files matched your search
@@ -44,6 +44,9 @@ body:
|
||||
- ROCKNIX
|
||||
- BizHawk
|
||||
- MiSTer FPGA
|
||||
- MinUI
|
||||
- NextUI
|
||||
- MyMinUI
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
||||
$defaultInstallSha256 = "fe8de4eda0919c268d4d2c0d571c7d0dc1cf675d7604263766f5f99a35aa3724"
|
||||
$defaultInstallSha256 = "526b405a2bef8e7ae181fc5554225a09e74afa25396db9084a15fb3a51e4b7eb"
|
||||
$maximumInstallerBytes = 2MB
|
||||
$installerExit = 0
|
||||
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
|
||||
|
||||
+67
-1
@@ -185,7 +185,7 @@ OTHER_SYSTEMS = "other"
|
||||
AVAILABLE_PLATFORMS = (
|
||||
"retroarch", "batocera", "recalbox", "retrobat", "emudeck",
|
||||
"lakka", "retrodeck", "rocknix", "romm", "bizhawk", "misterfpga",
|
||||
"retropie",
|
||||
"retropie", "minui", "nextui", "myminui",
|
||||
)
|
||||
|
||||
# The names the frontends give themselves, as each manifest's display_name
|
||||
@@ -197,8 +197,23 @@ PLATFORM_NAMES = {
|
||||
"retrobat": "RetroBat", "emudeck": "EmuDeck", "lakka": "Lakka",
|
||||
"retrodeck": "RetroDECK", "rocknix": "ROCKNIX", "romm": "RomM",
|
||||
"bizhawk": "BizHawk", "misterfpga": "MiSTer FPGA", "retropie": "RetroPie",
|
||||
"minui": "MinUI", "nextui": "NextUI", "myminui": "MyMinUI",
|
||||
}
|
||||
|
||||
# Frontends whose BIOS live on their SD card, installed from a computer the
|
||||
# card is mounted on. Each release unpacks as .system/ at the card root and
|
||||
# writes its release name as the first line of .system/version.txt (MinUI
|
||||
# makefile:22-24 and 152-157, NextUI makefile:33-35 and 249, MyMinUI
|
||||
# makefile:16-19 and 124). The registry publishes the same rules as each
|
||||
# manifest's `detect` (card_marker), and a test holds the two equal.
|
||||
CARD_MARKER = ".system/version.txt"
|
||||
_CARD_RULES: tuple[tuple[str, str, str], ...] = (
|
||||
("minui", CARD_MARKER, "MinUI-"),
|
||||
("nextui", CARD_MARKER, "NextUI-"),
|
||||
("myminui", CARD_MARKER, "MyMinUI-"),
|
||||
)
|
||||
CARD_PLATFORMS = frozenset(name for name, _file, _prefix in _CARD_RULES)
|
||||
|
||||
SITE_URL = "https://abdess.github.io/retrobios/"
|
||||
INSTALLER_PAGE = f"{SITE_URL}wiki/installer/"
|
||||
TROUBLESHOOTING_PAGE = f"{SITE_URL}wiki/troubleshooting/"
|
||||
@@ -4830,6 +4845,39 @@ def _no_core_found(plat: str, bios_path: Path, searched: list[Path], dest_given:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def card_platform(root: Path) -> str | None:
|
||||
"""The frontend whose card ``root`` is, from the release name it wrote."""
|
||||
for name, marker, prefix in _CARD_RULES:
|
||||
try:
|
||||
with open(root / marker, encoding="utf-8", errors="replace") as handle:
|
||||
first = handle.readline().strip()
|
||||
except OSError:
|
||||
continue
|
||||
if first.startswith(prefix):
|
||||
return name
|
||||
return None
|
||||
|
||||
|
||||
def _check_card_root(plat: str, dest: Path) -> None:
|
||||
"""Refuse a folder inside the card where its root is meant.
|
||||
|
||||
The file list writes Bios/<TAG>/ below --dest: given the card's Bios
|
||||
folder, every file would land in Bios/Bios/<TAG>/, which no core reads.
|
||||
"""
|
||||
found = card_platform(dest)
|
||||
if found is None:
|
||||
for parent in list(dest.absolute().parents)[:2]:
|
||||
holder = card_platform(parent)
|
||||
if holder:
|
||||
print(f"Error: {dest} is inside a {display_name(holder)} card. "
|
||||
f"Name the card's root: --dest {parent}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
return
|
||||
if found != plat:
|
||||
print(f" Note: this card holds {display_name(found)} ({CARD_MARKER}), and the "
|
||||
f"{display_name(plat)} file list was asked for.")
|
||||
|
||||
|
||||
def _platform_for_dest(os_type: str, dest: Path, interactive: bool) -> str:
|
||||
"""The file list for a --dest given without --platform.
|
||||
|
||||
@@ -4839,6 +4887,11 @@ def _platform_for_dest(os_type: str, dest: Path, interactive: bool) -> str:
|
||||
terminal the user picks, Enter taking the one found. Otherwise the one
|
||||
found is used, RetroArch when none is, and several found are refused.
|
||||
"""
|
||||
card = card_platform(dest)
|
||||
if card:
|
||||
print(f" File list: {display_name(card)} (the card's {CARD_MARKER}). "
|
||||
f"Same choice without detection: --platform {card}")
|
||||
return card
|
||||
print("Detecting platform...")
|
||||
found = list(dict.fromkeys(name for name, _path in detect_platforms(os_type)))
|
||||
default = found[0] if len(found) == 1 else "" if found else "retroarch"
|
||||
@@ -4896,6 +4949,8 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
|
||||
if args.platform:
|
||||
args.platform = normalize_platform(args.platform)
|
||||
if args.platform and args.dest:
|
||||
if args.platform in CARD_PLATFORMS:
|
||||
_check_card_root(args.platform, args.dest)
|
||||
platforms = [(args.platform, args.dest)]
|
||||
elif args.platform:
|
||||
print("Detecting platform...")
|
||||
@@ -4908,6 +4963,17 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
|
||||
# needed, and asking BizHawk's for one refused a question asked
|
||||
# before downloading BizHawk.
|
||||
platforms = [(args.platform, _default_dest(os_type, args.platform) or Path.cwd())]
|
||||
elif args.platform in CARD_PLATFORMS:
|
||||
# Its files go on its SD card, which is never this machine's
|
||||
# own folder: the card's mount point is the user's to name.
|
||||
print(
|
||||
f" {display_name(args.platform)} keeps its BIOS files on its SD card. "
|
||||
f"Mount the card and name its root with --dest, for example: "
|
||||
f"--platform {args.platform} --dest /media/$USER/<card> "
|
||||
f"(Windows: --dest E:\\)",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
elif args.platform in PORTABLE_ONLY:
|
||||
# A portable emulator reads the folder beside its executable:
|
||||
# any default would be a folder it never opens.
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ esac
|
||||
TEMP_INSTALLER=""
|
||||
TEMP_DIRECTORY=""
|
||||
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
||||
DEFAULT_INSTALL_SHA256="fe8de4eda0919c268d4d2c0d571c7d0dc1cf675d7604263766f5f99a35aa3724"
|
||||
DEFAULT_INSTALL_SHA256="526b405a2bef8e7ae181fc5554225a09e74afa25396db9084a15fb3a51e4b7eb"
|
||||
MAX_INSTALLER_BYTES=2097152
|
||||
|
||||
cleanup() {
|
||||
|
||||
@@ -650,3 +650,86 @@ platforms:
|
||||
method: path_exists
|
||||
path: /media/fat/MiSTer
|
||||
bios_path: /media/fat/games
|
||||
minui:
|
||||
config: minui.yml
|
||||
status: active
|
||||
# The card root: minarch hands each core SDCARD_PATH/Bios/<TAG>
|
||||
# (workspace/all/minarch/minarch.c:2943).
|
||||
extract_to: <SD card>/
|
||||
logo: https://avatars.githubusercontent.com/u/225589
|
||||
scraper: minui
|
||||
# minarch hands each core a folder and names no file (workspace/all/minarch/minarch.c:2943):
|
||||
# there is no BIOS list of its own to export corrected.
|
||||
native_list: none
|
||||
source_url: https://github.com/shauninman/MinUI
|
||||
source_format: pak_launch_scripts
|
||||
hash_type: sha1
|
||||
# The primary card is formatted FAT32 (skeleton/BASE/README.txt:15).
|
||||
case_insensitive_fs: true
|
||||
schedule: null
|
||||
target_scraper: null
|
||||
target_source: null
|
||||
install:
|
||||
detect:
|
||||
# The release zip holds the build's SYSTEM tree as .system/ at the card
|
||||
# root with version.txt, whose first line is the release name
|
||||
# (makefile:22-24, 152-157). The installer runs on the computer the
|
||||
# card is mounted on and reads it from --dest.
|
||||
- os: any
|
||||
method: card_marker
|
||||
file: .system/version.txt
|
||||
first_line_prefix: MinUI-
|
||||
nextui:
|
||||
config: nextui.yml
|
||||
status: active
|
||||
# minarch hands each core SDCARD_PATH/Bios/<TAG>
|
||||
# (workspace/all/minarch/ma_core.c:76).
|
||||
extract_to: <SD card>/
|
||||
logo: https://avatars.githubusercontent.com/u/202386615
|
||||
scraper: nextui
|
||||
# minarch hands each core a folder and names no file (workspace/all/minarch/ma_core.c:76):
|
||||
# there is no BIOS list of its own to export corrected.
|
||||
native_list: none
|
||||
source_url: https://github.com/LoveRetro/NextUI
|
||||
source_format: pak_launch_scripts
|
||||
hash_type: sha1
|
||||
# The primary card is formatted FAT32 (skeleton/BASE/README.txt:17).
|
||||
case_insensitive_fs: true
|
||||
schedule: null
|
||||
target_scraper: null
|
||||
target_source: null
|
||||
install:
|
||||
detect:
|
||||
# makefile:33-35 and 249: version.txt in .system/, first line the
|
||||
# release name.
|
||||
- os: any
|
||||
method: card_marker
|
||||
file: .system/version.txt
|
||||
first_line_prefix: NextUI-
|
||||
myminui:
|
||||
config: myminui.yml
|
||||
status: active
|
||||
# minarch hands each core SDCARD_PATH/Bios/<TAG>
|
||||
# (workspace/all/minarch/minarch.c:3532).
|
||||
extract_to: <SD card>/
|
||||
logo: https://avatars.githubusercontent.com/u/10894566
|
||||
scraper: myminui
|
||||
# minarch hands each core a folder and names no file (workspace/all/minarch/minarch.c:3532):
|
||||
# there is no BIOS list of its own to export corrected.
|
||||
native_list: none
|
||||
source_url: https://github.com/Turro75/MyMinUI
|
||||
source_format: pak_launch_scripts
|
||||
hash_type: sha1
|
||||
# The card is formatted FAT32 (MANUAL.md:13).
|
||||
case_insensitive_fs: true
|
||||
schedule: null
|
||||
target_scraper: null
|
||||
target_source: null
|
||||
install:
|
||||
detect:
|
||||
# makefile:16-19 and 124: version.txt in .system/, first line the
|
||||
# release name.
|
||||
- os: any
|
||||
method: card_marker
|
||||
file: .system/version.txt
|
||||
first_line_prefix: MyMinUI-
|
||||
@@ -0,0 +1,354 @@
|
||||
platform: MinUI
|
||||
version: '2025-11-27'
|
||||
homepage: "https://github.com/shauninman/MinUI"
|
||||
source: "https://github.com/shauninman/MinUI/tree/dbf89435a5179788418ff3573042cf9c7e75d9e0"
|
||||
base_destination: ''
|
||||
hash_type: sha1
|
||||
verification_mode: existence
|
||||
system_directory_ref: "workspace/all/minarch/minarch.c:2943"
|
||||
cores:
|
||||
- fake08
|
||||
- fceumm
|
||||
- gambatte
|
||||
- gpsp
|
||||
- mednafen_pce_fast
|
||||
- mednafen_supafaust
|
||||
- mednafen_vb
|
||||
- mgba
|
||||
- pcsx_rearmed
|
||||
- picodrive
|
||||
- pokemini
|
||||
- race
|
||||
- snes9x2005_plus
|
||||
core_system_dirs:
|
||||
Bios/FC:
|
||||
cores:
|
||||
- fceumm
|
||||
devices:
|
||||
- gkdpixel
|
||||
- m17
|
||||
- magicmini
|
||||
- miyoomini
|
||||
- my282
|
||||
- my355
|
||||
- rg35xx
|
||||
- rg35xxplus
|
||||
- rgb30
|
||||
- tg5040
|
||||
- trimuismart
|
||||
- zero28
|
||||
source_ref: "skeleton/SYSTEM/gkdpixel/paks/Emus/FC.pak/launch.sh:3"
|
||||
Bios/GB:
|
||||
cores:
|
||||
- gambatte
|
||||
devices:
|
||||
- gkdpixel
|
||||
- m17
|
||||
- magicmini
|
||||
- miyoomini
|
||||
- my282
|
||||
- my355
|
||||
- rg35xx
|
||||
- rg35xxplus
|
||||
- rgb30
|
||||
- tg5040
|
||||
- trimuismart
|
||||
- zero28
|
||||
source_ref: "skeleton/SYSTEM/gkdpixel/paks/Emus/GB.pak/launch.sh:3"
|
||||
Bios/GBA:
|
||||
cores:
|
||||
- gpsp
|
||||
devices:
|
||||
- gkdpixel
|
||||
- m17
|
||||
- magicmini
|
||||
- miyoomini
|
||||
- my282
|
||||
- my355
|
||||
- rg35xx
|
||||
- rg35xxplus
|
||||
- rgb30
|
||||
- tg5040
|
||||
- trimuismart
|
||||
- zero28
|
||||
source_ref: "skeleton/SYSTEM/gkdpixel/paks/Emus/GBA.pak/launch.sh:3"
|
||||
Bios/GBC:
|
||||
cores:
|
||||
- gambatte
|
||||
devices:
|
||||
- gkdpixel
|
||||
- m17
|
||||
- magicmini
|
||||
- miyoomini
|
||||
- my282
|
||||
- my355
|
||||
- rg35xx
|
||||
- rg35xxplus
|
||||
- rgb30
|
||||
- tg5040
|
||||
- trimuismart
|
||||
- zero28
|
||||
source_ref: "skeleton/SYSTEM/gkdpixel/paks/Emus/GBC.pak/launch.sh:3"
|
||||
Bios/GG:
|
||||
cores:
|
||||
- picodrive
|
||||
devices:
|
||||
- gkdpixel
|
||||
- m17
|
||||
- magicmini
|
||||
- miyoomini
|
||||
- my282
|
||||
- my355
|
||||
- rg35xx
|
||||
- rg35xxplus
|
||||
- rgb30
|
||||
- tg5040
|
||||
- trimuismart
|
||||
- zero28
|
||||
source_ref: "skeleton/EXTRAS/Emus/gkdpixel/GG.pak/launch.sh:3"
|
||||
Bios/MD:
|
||||
cores:
|
||||
- picodrive
|
||||
devices:
|
||||
- gkdpixel
|
||||
- m17
|
||||
- magicmini
|
||||
- miyoomini
|
||||
- my282
|
||||
- my355
|
||||
- rg35xx
|
||||
- rg35xxplus
|
||||
- rgb30
|
||||
- tg5040
|
||||
- trimuismart
|
||||
- zero28
|
||||
source_ref: "skeleton/SYSTEM/gkdpixel/paks/Emus/MD.pak/launch.sh:3"
|
||||
Bios/MGBA:
|
||||
cores:
|
||||
- mgba
|
||||
devices:
|
||||
- gkdpixel
|
||||
- m17
|
||||
- magicmini
|
||||
- miyoomini
|
||||
- my282
|
||||
- my355
|
||||
- rg35xx
|
||||
- rg35xxplus
|
||||
- rgb30
|
||||
- tg5040
|
||||
- trimuismart
|
||||
- zero28
|
||||
source_ref: "skeleton/EXTRAS/Emus/gkdpixel/MGBA.pak/launch.sh:3"
|
||||
Bios/NGP:
|
||||
cores:
|
||||
- race
|
||||
devices:
|
||||
- gkdpixel
|
||||
- m17
|
||||
- magicmini
|
||||
- miyoomini
|
||||
- my282
|
||||
- my355
|
||||
- rg35xx
|
||||
- rg35xxplus
|
||||
- rgb30
|
||||
- tg5040
|
||||
- trimuismart
|
||||
- zero28
|
||||
source_ref: "skeleton/EXTRAS/Emus/gkdpixel/NGP.pak/launch.sh:3"
|
||||
Bios/NGPC:
|
||||
cores:
|
||||
- race
|
||||
devices:
|
||||
- gkdpixel
|
||||
- m17
|
||||
- magicmini
|
||||
- miyoomini
|
||||
- my282
|
||||
- my355
|
||||
- rg35xx
|
||||
- rg35xxplus
|
||||
- rgb30
|
||||
- tg5040
|
||||
- trimuismart
|
||||
- zero28
|
||||
source_ref: "skeleton/EXTRAS/Emus/gkdpixel/NGPC.pak/launch.sh:3"
|
||||
Bios/P8:
|
||||
cores:
|
||||
- fake08
|
||||
devices:
|
||||
- m17
|
||||
- magicmini
|
||||
- miyoomini
|
||||
- my282
|
||||
- my355
|
||||
- rg35xx
|
||||
- rg35xxplus
|
||||
- rgb30
|
||||
- tg5040
|
||||
- trimuismart
|
||||
- zero28
|
||||
source_ref: "skeleton/EXTRAS/Emus/m17/P8.pak/launch.sh:3"
|
||||
Bios/PCE:
|
||||
cores:
|
||||
- mednafen_pce_fast
|
||||
devices:
|
||||
- gkdpixel
|
||||
- m17
|
||||
- magicmini
|
||||
- miyoomini
|
||||
- my282
|
||||
- my355
|
||||
- rg35xx
|
||||
- rg35xxplus
|
||||
- rgb30
|
||||
- tg5040
|
||||
- trimuismart
|
||||
- zero28
|
||||
source_ref: "skeleton/EXTRAS/Emus/gkdpixel/PCE.pak/launch.sh:3"
|
||||
Bios/PKM:
|
||||
cores:
|
||||
- pokemini
|
||||
devices:
|
||||
- gkdpixel
|
||||
- m17
|
||||
- magicmini
|
||||
- miyoomini
|
||||
- my282
|
||||
- my355
|
||||
- rg35xx
|
||||
- rg35xxplus
|
||||
- rgb30
|
||||
- tg5040
|
||||
- trimuismart
|
||||
- zero28
|
||||
source_ref: "skeleton/EXTRAS/Emus/gkdpixel/PKM.pak/launch.sh:3"
|
||||
Bios/PS:
|
||||
cores:
|
||||
- pcsx_rearmed
|
||||
devices:
|
||||
- gkdpixel
|
||||
- m17
|
||||
- magicmini
|
||||
- miyoomini
|
||||
- my282
|
||||
- my355
|
||||
- rg35xx
|
||||
- rg35xxplus
|
||||
- rgb30
|
||||
- tg5040
|
||||
- trimuismart
|
||||
- zero28
|
||||
source_ref: "skeleton/SYSTEM/gkdpixel/paks/Emus/PS.pak/launch.sh:3"
|
||||
Bios/SFC:
|
||||
cores:
|
||||
- snes9x2005_plus
|
||||
devices:
|
||||
- gkdpixel
|
||||
- m17
|
||||
- magicmini
|
||||
- miyoomini
|
||||
- my282
|
||||
- my355
|
||||
- rg35xx
|
||||
- rg35xxplus
|
||||
- rgb30
|
||||
- tg5040
|
||||
- trimuismart
|
||||
- zero28
|
||||
source_ref: "skeleton/SYSTEM/gkdpixel/paks/Emus/SFC.pak/launch.sh:3"
|
||||
Bios/SGB:
|
||||
cores:
|
||||
- mgba
|
||||
devices:
|
||||
- gkdpixel
|
||||
- m17
|
||||
- magicmini
|
||||
- miyoomini
|
||||
- my282
|
||||
- my355
|
||||
- rg35xx
|
||||
- rg35xxplus
|
||||
- rgb30
|
||||
- tg5040
|
||||
- trimuismart
|
||||
- zero28
|
||||
source_ref: "skeleton/EXTRAS/Emus/gkdpixel/SGB.pak/launch.sh:3"
|
||||
Bios/SMS:
|
||||
cores:
|
||||
- picodrive
|
||||
devices:
|
||||
- gkdpixel
|
||||
- m17
|
||||
- magicmini
|
||||
- miyoomini
|
||||
- my282
|
||||
- my355
|
||||
- rg35xx
|
||||
- rg35xxplus
|
||||
- rgb30
|
||||
- tg5040
|
||||
- trimuismart
|
||||
- zero28
|
||||
source_ref: "skeleton/EXTRAS/Emus/gkdpixel/SMS.pak/launch.sh:3"
|
||||
Bios/SUPA:
|
||||
cores:
|
||||
- mednafen_supafaust
|
||||
devices:
|
||||
- m17
|
||||
- magicmini
|
||||
- miyoomini
|
||||
- my282
|
||||
- my355
|
||||
- rg35xx
|
||||
- rg35xxplus
|
||||
- rgb30
|
||||
- tg5040
|
||||
- trimuismart
|
||||
- zero28
|
||||
source_ref: "skeleton/EXTRAS/Emus/m17/SUPA.pak/launch.sh:3"
|
||||
Bios/VB:
|
||||
cores:
|
||||
- mednafen_vb
|
||||
devices:
|
||||
- m17
|
||||
- magicmini
|
||||
- miyoomini
|
||||
- my282
|
||||
- my355
|
||||
- rg35xx
|
||||
- rg35xxplus
|
||||
- rgb30
|
||||
- tg5040
|
||||
- trimuismart
|
||||
- zero28
|
||||
source_ref: "skeleton/EXTRAS/Emus/m17/VB.pak/launch.sh:3"
|
||||
core_repositories:
|
||||
fake08:
|
||||
- "https://github.com/jtothebell/fake-08"
|
||||
fceumm:
|
||||
- "https://github.com/libretro/libretro-fceumm"
|
||||
gambatte:
|
||||
- "https://github.com/libretro/gambatte-libretro"
|
||||
gpsp:
|
||||
- "https://github.com/libretro/gpsp"
|
||||
mednafen_pce_fast:
|
||||
- "https://github.com/libretro/beetle-pce-fast-libretro"
|
||||
mednafen_supafaust:
|
||||
- "https://github.com/libretro/supafaust"
|
||||
mednafen_vb:
|
||||
- "https://github.com/libretro/beetle-vb-libretro"
|
||||
mgba:
|
||||
- "https://github.com/libretro/mgba"
|
||||
pcsx_rearmed:
|
||||
- "https://github.com/libretro/pcsx_rearmed"
|
||||
picodrive:
|
||||
- "https://github.com/irixxxx/picodrive"
|
||||
pokemini:
|
||||
- "https://github.com/libretro/PokeMini"
|
||||
race:
|
||||
- "https://github.com/libretro/race"
|
||||
snes9x2005_plus:
|
||||
- "https://github.com/libretro/snes9x2005"
|
||||
systems: {}
|
||||
@@ -0,0 +1,426 @@
|
||||
platform: MyMinUI
|
||||
version: '2026-08-09'
|
||||
homepage: "https://github.com/Turro75/MyMinUI"
|
||||
source: "https://github.com/Turro75/MyMinUI/tree/72ed4f3dd80d96646c11bf9363242565320d9566"
|
||||
base_destination: ''
|
||||
hash_type: sha1
|
||||
verification_mode: existence
|
||||
system_directory_ref: "workspace/all/minarch/minarch.c:3532"
|
||||
cores:
|
||||
- a5200
|
||||
- dosbox_pure
|
||||
- fake08
|
||||
- fbneo
|
||||
- fceumm
|
||||
- fuse
|
||||
- gambatte
|
||||
- gpsp
|
||||
- mame2003_plus
|
||||
- mednafen_ngp
|
||||
- mednafen_pce_fast
|
||||
- mednafen_supafaust
|
||||
- mednafen_vb
|
||||
- mednafen_wswan
|
||||
- mgba
|
||||
- neocd
|
||||
- pcsx_rearmed
|
||||
- picodrive
|
||||
- pokemini
|
||||
- prboom
|
||||
- prosystem
|
||||
- puae2021
|
||||
- snes9x2005_plus
|
||||
- stella2014
|
||||
- tyrquake
|
||||
core_system_dirs:
|
||||
Bios/A2600:
|
||||
cores:
|
||||
- stella2014
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/A2600.pak/launch.sh:3"
|
||||
Bios/A5200:
|
||||
cores:
|
||||
- a5200
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/A5200.pak/launch.sh:3"
|
||||
Bios/A7800:
|
||||
cores:
|
||||
- prosystem
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/A7800.pak/launch.sh:3"
|
||||
Bios/DOOM:
|
||||
cores:
|
||||
- prboom
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/DOOM.pak/launch.sh:3"
|
||||
Bios/DOS:
|
||||
cores:
|
||||
- dosbox_pure
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/DOS.pak/launch.sh:3"
|
||||
Bios/FBN:
|
||||
cores:
|
||||
- fbneo
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/FBN.pak/launch.sh:3"
|
||||
Bios/FC:
|
||||
cores:
|
||||
- fceumm
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/FC.pak/launch.sh:3"
|
||||
Bios/GB:
|
||||
cores:
|
||||
- gambatte
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/GB.pak/launch.sh:3"
|
||||
Bios/GBA:
|
||||
cores:
|
||||
- gpsp
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/GBA.pak/launch.sh:3"
|
||||
Bios/GBC:
|
||||
cores:
|
||||
- gambatte
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/GBC.pak/launch.sh:3"
|
||||
Bios/GG:
|
||||
cores:
|
||||
- picodrive
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/GG.pak/launch.sh:3"
|
||||
Bios/MAME:
|
||||
cores:
|
||||
- mame2003_plus
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/MAME.pak/launch.sh:3"
|
||||
Bios/MD:
|
||||
cores:
|
||||
- picodrive
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/MD.pak/launch.sh:3"
|
||||
Bios/MGBA:
|
||||
cores:
|
||||
- mgba
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/MGBA.pak/launch.sh:3"
|
||||
Bios/NG:
|
||||
cores:
|
||||
- fbneo
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/NG.pak/launch.sh:3"
|
||||
Bios/NGCD:
|
||||
cores:
|
||||
- neocd
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/NGCD.pak/launch.sh:3"
|
||||
Bios/NGP:
|
||||
cores:
|
||||
- mednafen_ngp
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/NGP.pak/launch.sh:3"
|
||||
Bios/NGPC:
|
||||
cores:
|
||||
- mednafen_ngp
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/NGPC.pak/launch.sh:3"
|
||||
Bios/P8:
|
||||
cores:
|
||||
- fake08
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/P8.pak/launch.sh:3"
|
||||
Bios/PCE:
|
||||
cores:
|
||||
- mednafen_pce_fast
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/PCE.pak/launch.sh:3"
|
||||
Bios/PKM:
|
||||
cores:
|
||||
- pokemini
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/PKM.pak/launch.sh:3"
|
||||
Bios/PS:
|
||||
cores:
|
||||
- pcsx_rearmed
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/PS.pak/launch.sh:3"
|
||||
Bios/PUAE:
|
||||
cores:
|
||||
- puae2021
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/PUAE.pak/launch.sh:3"
|
||||
Bios/QUAKE:
|
||||
cores:
|
||||
- tyrquake
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/QUAKE.pak/launch.sh:3"
|
||||
Bios/SFC:
|
||||
cores:
|
||||
- snes9x2005_plus
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/SFC.pak/launch.sh:3"
|
||||
Bios/SGB:
|
||||
cores:
|
||||
- mgba
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/SGB.pak/launch.sh:3"
|
||||
Bios/SMS:
|
||||
cores:
|
||||
- picodrive
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/SMS.pak/launch.sh:3"
|
||||
Bios/SUPA:
|
||||
cores:
|
||||
- mednafen_supafaust
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/SUPA.pak/launch.sh:3"
|
||||
Bios/VB:
|
||||
cores:
|
||||
- mednafen_vb
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/VB.pak/launch.sh:3"
|
||||
Bios/WS:
|
||||
cores:
|
||||
- mednafen_wswan
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/WS.pak/launch.sh:3"
|
||||
Bios/ZXS:
|
||||
cores:
|
||||
- fuse
|
||||
devices:
|
||||
- h700
|
||||
- m21
|
||||
- miyoomini
|
||||
- my282
|
||||
- r36s
|
||||
- rg35xx
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/ZXS.pak/launch.sh:3"
|
||||
core_repositories:
|
||||
a5200:
|
||||
- "https://github.com/libretro/a5200"
|
||||
dosbox_pure:
|
||||
- "https://github.com/libretro/dosbox-pure"
|
||||
fake08:
|
||||
- "https://github.com/jtothebell/fake-08"
|
||||
fbneo:
|
||||
- "https://github.com/libretro/fbneo"
|
||||
fceumm:
|
||||
- "https://github.com/libretro/libretro-fceumm"
|
||||
fuse:
|
||||
- "https://github.com/libretro/fuse-libretro"
|
||||
gambatte:
|
||||
- "https://github.com/libretro/gambatte-libretro"
|
||||
gpsp:
|
||||
- "https://github.com/libretro/gpsp"
|
||||
mame2003_plus:
|
||||
- "https://github.com/libretro/mame2003-plus-libretro"
|
||||
mednafen_ngp:
|
||||
- "https://github.com/libretro/beetle-ngp-libretro"
|
||||
mednafen_pce_fast:
|
||||
- "https://github.com/libretro/beetle-pce-fast-libretro"
|
||||
mednafen_supafaust:
|
||||
- "https://github.com/libretro/supafaust"
|
||||
mednafen_vb:
|
||||
- "https://github.com/libretro/beetle-vb-libretro"
|
||||
mednafen_wswan:
|
||||
- "https://github.com/libretro/beetle-wswan-libretro"
|
||||
mgba:
|
||||
- "https://github.com/libretro/mgba"
|
||||
neocd:
|
||||
- "https://github.com/libretro/neocd_libretro"
|
||||
pcsx_rearmed:
|
||||
- "https://github.com/libretro/pcsx_rearmed"
|
||||
picodrive:
|
||||
- "https://github.com/irixxxx/picodrive"
|
||||
pokemini:
|
||||
- "https://github.com/libretro/PokeMini"
|
||||
prboom:
|
||||
- "https://github.com/libretro/libretro-prboom"
|
||||
prosystem:
|
||||
- "https://github.com/libretro/prosystem-libretro"
|
||||
puae2021:
|
||||
- "https://github.com/libretro/libretro-uae"
|
||||
snes9x2005_plus:
|
||||
- "https://github.com/libretro/snes9x2005"
|
||||
stella2014:
|
||||
- "https://github.com/libretro/stella2014-libretro"
|
||||
tyrquake:
|
||||
- "https://github.com/libretro/tyrquake"
|
||||
systems: {}
|
||||
@@ -0,0 +1,399 @@
|
||||
platform: NextUI
|
||||
version: '2026-10-20'
|
||||
homepage: "https://nextui.loveretro.games"
|
||||
source: "https://github.com/LoveRetro/NextUI/tree/5291344b3aceae5fa197437a70824afb41e2d338"
|
||||
base_destination: ''
|
||||
hash_type: sha1
|
||||
verification_mode: existence
|
||||
system_directory_ref: "workspace/all/minarch/ma_core.c:76"
|
||||
cores:
|
||||
- a5200
|
||||
- bluemsx
|
||||
- cap32
|
||||
- fake08
|
||||
- fbneo
|
||||
- fceumm
|
||||
- gambatte
|
||||
- gearcoleco
|
||||
- gpsp
|
||||
- handy
|
||||
- mednafen_pce_fast
|
||||
- mednafen_supafaust
|
||||
- mednafen_vb
|
||||
- mgba
|
||||
- pcsx_rearmed
|
||||
- picodrive
|
||||
- pokemini
|
||||
- prboom
|
||||
- prosystem
|
||||
- puae2021
|
||||
- race
|
||||
- snes9x
|
||||
- stella2014
|
||||
- vice_x128
|
||||
- vice_x64
|
||||
- vice_xpet
|
||||
- vice_xplus4
|
||||
- vice_xvic
|
||||
core_system_dirs:
|
||||
Bios/32X:
|
||||
cores:
|
||||
- picodrive
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/32X.pak/launch.sh:3"
|
||||
Bios/A2600:
|
||||
cores:
|
||||
- stella2014
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/A2600.pak/launch.sh:3"
|
||||
Bios/A5200:
|
||||
cores:
|
||||
- a5200
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/A5200.pak/launch.sh:3"
|
||||
Bios/A7800:
|
||||
cores:
|
||||
- prosystem
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/A7800.pak/launch.sh:3"
|
||||
Bios/C128:
|
||||
cores:
|
||||
- vice_x128
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/C128.pak/launch.sh:3"
|
||||
Bios/C64:
|
||||
cores:
|
||||
- vice_x64
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/C64.pak/launch.sh:3"
|
||||
Bios/COLECO:
|
||||
cores:
|
||||
- gearcoleco
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/COLECO.pak/launch.sh:3"
|
||||
Bios/CPC:
|
||||
cores:
|
||||
- cap32
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/CPC.pak/launch.sh:3"
|
||||
Bios/FBN:
|
||||
cores:
|
||||
- fbneo
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/FBN.pak/launch.sh:3"
|
||||
Bios/FC:
|
||||
cores:
|
||||
- fceumm
|
||||
devices:
|
||||
- desktop
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/SYSTEM/desktop/paks/Emus/FC.pak/launch.sh:3"
|
||||
Bios/FDS:
|
||||
cores:
|
||||
- fceumm
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/FDS.pak/launch.sh:3"
|
||||
Bios/GB:
|
||||
cores:
|
||||
- gambatte
|
||||
devices:
|
||||
- desktop
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/SYSTEM/desktop/paks/Emus/GB.pak/launch.sh:3"
|
||||
Bios/GBA:
|
||||
cores:
|
||||
- gpsp
|
||||
devices:
|
||||
- desktop
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/SYSTEM/desktop/paks/Emus/GBA.pak/launch.sh:3"
|
||||
Bios/GBC:
|
||||
cores:
|
||||
- gambatte
|
||||
devices:
|
||||
- desktop
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/SYSTEM/desktop/paks/Emus/GBC.pak/launch.sh:3"
|
||||
Bios/GG:
|
||||
cores:
|
||||
- picodrive
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/GG.pak/launch.sh:3"
|
||||
Bios/LYNX:
|
||||
cores:
|
||||
- handy
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/LYNX.pak/launch.sh:3"
|
||||
Bios/MD:
|
||||
cores:
|
||||
- picodrive
|
||||
devices:
|
||||
- desktop
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/SYSTEM/desktop/paks/Emus/MD.pak/launch.sh:3"
|
||||
Bios/MGBA:
|
||||
cores:
|
||||
- mgba
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/MGBA.pak/launch.sh:3"
|
||||
Bios/MSX:
|
||||
cores:
|
||||
- bluemsx
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/MSX.pak/launch.sh:3"
|
||||
Bios/NGP:
|
||||
cores:
|
||||
- race
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/NGP.pak/launch.sh:3"
|
||||
Bios/NGPC:
|
||||
cores:
|
||||
- race
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/NGPC.pak/launch.sh:3"
|
||||
Bios/P8:
|
||||
cores:
|
||||
- fake08
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/P8.pak/launch.sh:3"
|
||||
Bios/PCE:
|
||||
cores:
|
||||
- mednafen_pce_fast
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/PCE.pak/launch.sh:3"
|
||||
Bios/PET:
|
||||
cores:
|
||||
- vice_xpet
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/PET.pak/launch.sh:3"
|
||||
Bios/PKM:
|
||||
cores:
|
||||
- pokemini
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/PKM.pak/launch.sh:3"
|
||||
Bios/PLUS4:
|
||||
cores:
|
||||
- vice_xplus4
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/PLUS4.pak/launch.sh:3"
|
||||
Bios/PRBOOM:
|
||||
cores:
|
||||
- prboom
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/PRBOOM.pak/launch.sh:7"
|
||||
Bios/PS:
|
||||
cores:
|
||||
- pcsx_rearmed
|
||||
devices:
|
||||
- desktop
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/SYSTEM/desktop/paks/Emus/PS.pak/launch.sh:3"
|
||||
Bios/PUAE:
|
||||
cores:
|
||||
- puae2021
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/PUAE.pak/launch.sh:3"
|
||||
Bios/SEGACD:
|
||||
cores:
|
||||
- picodrive
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/SEGACD.pak/launch.sh:3"
|
||||
Bios/SFC:
|
||||
cores:
|
||||
- snes9x
|
||||
devices:
|
||||
- desktop
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/SYSTEM/desktop/paks/Emus/SFC.pak/launch.sh:3"
|
||||
Bios/SG1000:
|
||||
cores:
|
||||
- picodrive
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/SG1000.pak/launch.sh:3"
|
||||
Bios/SGB:
|
||||
cores:
|
||||
- mgba
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/SGB.pak/launch.sh:3"
|
||||
Bios/SMS:
|
||||
cores:
|
||||
- picodrive
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/SMS.pak/launch.sh:3"
|
||||
Bios/SUPA:
|
||||
cores:
|
||||
- mednafen_supafaust
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/SUPA.pak/launch.sh:3"
|
||||
Bios/VB:
|
||||
cores:
|
||||
- mednafen_vb
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/VB.pak/launch.sh:3"
|
||||
Bios/VIC:
|
||||
cores:
|
||||
- vice_xvic
|
||||
devices:
|
||||
- h700
|
||||
- tg5040
|
||||
- tg5050
|
||||
source_ref: "skeleton/EXTRAS/Emus/h700/VIC.pak/launch.sh:3"
|
||||
core_repositories:
|
||||
a5200:
|
||||
- "https://github.com/libretro/a5200"
|
||||
bluemsx:
|
||||
- "https://github.com/libretro/blueMSX-libretro"
|
||||
cap32:
|
||||
- "https://github.com/libretro/libretro-cap32"
|
||||
fake08:
|
||||
- "https://github.com/jtothebell/fake-08"
|
||||
fbneo:
|
||||
- "https://github.com/libretro/FBNeo"
|
||||
fceumm:
|
||||
- "https://github.com/libretro/libretro-fceumm"
|
||||
gambatte:
|
||||
- "https://github.com/libretro/gambatte-libretro"
|
||||
gearcoleco:
|
||||
- "https://github.com/drhelius/Gearcoleco"
|
||||
gpsp:
|
||||
- "https://github.com/libretro/gpsp"
|
||||
handy:
|
||||
- "https://github.com/libretro/libretro-handy"
|
||||
mednafen_pce_fast:
|
||||
- "https://github.com/libretro/beetle-pce-fast-libretro"
|
||||
mednafen_supafaust:
|
||||
- "https://github.com/libretro/supafaust"
|
||||
mednafen_vb:
|
||||
- "https://github.com/libretro/beetle-vb-libretro"
|
||||
mgba:
|
||||
- "https://github.com/libretro/mgba"
|
||||
pcsx_rearmed:
|
||||
- "https://github.com/libretro/pcsx_rearmed"
|
||||
picodrive:
|
||||
- "https://github.com/irixxxx/picodrive"
|
||||
pokemini:
|
||||
- "https://github.com/libretro/PokeMini"
|
||||
prboom:
|
||||
- "https://github.com/libretro/libretro-prboom"
|
||||
prosystem:
|
||||
- "https://github.com/libretro/prosystem-libretro"
|
||||
puae2021:
|
||||
- "https://github.com/libretro/libretro-uae"
|
||||
race:
|
||||
- "https://github.com/libretro/race"
|
||||
snes9x:
|
||||
- "https://github.com/libretro/snes9x"
|
||||
stella2014:
|
||||
- "https://github.com/libretro/stella2014-libretro"
|
||||
vice_x128:
|
||||
- "https://github.com/libretro/vice-libretro"
|
||||
vice_x64:
|
||||
- "https://github.com/libretro/vice-libretro"
|
||||
vice_xpet:
|
||||
- "https://github.com/libretro/vice-libretro"
|
||||
vice_xplus4:
|
||||
- "https://github.com/libretro/vice-libretro"
|
||||
vice_xvic:
|
||||
- "https://github.com/libretro/vice-libretro"
|
||||
systems: {}
|
||||
@@ -1157,6 +1157,7 @@ def generate_pack(
|
||||
standalone_copies=(
|
||||
_pack_registry.get(platform_name, {}).get("install", {}) or {}
|
||||
).get("standalone_copies", []),
|
||||
launch_folders=len(config.get("core_system_dirs") or {}),
|
||||
)
|
||||
_write_generated_member(zf, "README.txt", readme_text)
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ sys.path.insert(0, os.path.dirname(__file__))
|
||||
from common import (
|
||||
compute_composition,
|
||||
count_catalog_matched,
|
||||
declared_root,
|
||||
github_token,
|
||||
list_registered_platforms,
|
||||
load_database,
|
||||
@@ -167,6 +168,19 @@ def extract_targets(platforms_dir: str) -> list[tuple[str, str]]:
|
||||
return sorted(rows, key=lambda row: row[0].casefold())
|
||||
|
||||
|
||||
def own_root(config: dict) -> str:
|
||||
"""The folder a pack written from an empty base_destination starts with.
|
||||
|
||||
RetroDECK writes bios/ in front of its files, the MinUI family hands its
|
||||
cores folders below Bios/.
|
||||
"""
|
||||
root = declared_root(config)
|
||||
if root:
|
||||
return root
|
||||
tops = {directory.split("/", 1)[0] for directory in config.get("core_system_dirs") or {}}
|
||||
return tops.pop() if len(tops) == 1 else "bios"
|
||||
|
||||
|
||||
def extract_notes(platforms_dir: str) -> list[str]:
|
||||
"""A pack whose entries carry their own root extracts above the BIOS folder."""
|
||||
folders = dict(extract_targets(platforms_dir))
|
||||
@@ -176,8 +190,8 @@ def extract_notes(platforms_dir: str) -> list[str]:
|
||||
display = config.get("platform", key)
|
||||
if config.get("base_destination") == "" and display in folders:
|
||||
notes.append(
|
||||
f"The {display} pack already carries its own `bios/` folder, so it"
|
||||
f" extracts into `{folders[display]}` rather than into the BIOS folder."
|
||||
f"The {display} pack already carries its own `{own_root(config)}/` folder,"
|
||||
f" so it extracts into `{folders[display]}` rather than into the BIOS folder."
|
||||
)
|
||||
return notes
|
||||
|
||||
@@ -470,7 +484,9 @@ def generate_readme(db: dict, platforms_dir: str) -> str:
|
||||
" a checksum, the fingerprint of a file's contents, which catches a"
|
||||
" corrupt or unexpected copy. RetroArch, Lakka and RetroPie check"
|
||||
" that the file is present under its name without reading its"
|
||||
" contents (`path_is_valid` in RetroArch's `core_info.c`): the"
|
||||
" contents (`path_is_valid` in RetroArch's `core_info.c`), and"
|
||||
" MinUI, NextUI and MyMinUI hand each core its folder without"
|
||||
" opening a file in it: the"
|
||||
" Coverage table says which applies to you. Independently of that,"
|
||||
" the collection records five fingerprints per file, and wherever"
|
||||
" an emulator's code states an expected size or hash, that value is"
|
||||
|
||||
@@ -2158,7 +2158,9 @@ def generate_platform_page(
|
||||
f"| Contributed by | [@{username}](https://github.com/{username}):"
|
||||
f" {contribution}{pr_link} |"
|
||||
)
|
||||
lines.extend(["", *_platform_install_block(name, display)])
|
||||
detect = ((registry or {}).get(name, {}).get("install") or {}).get("detect") or []
|
||||
on_card = any(rule.get("method") == "card_marker" for rule in detect)
|
||||
lines.extend(["", *_platform_install_block(name, display, on_card)])
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
@@ -3565,9 +3567,35 @@ def _install_block(name: str, profile: dict) -> list[str]:
|
||||
]
|
||||
|
||||
|
||||
def _platform_install_block(name: str, display: str) -> list[str]:
|
||||
def _platform_install_block(name: str, display: str, on_card: bool = False) -> list[str]:
|
||||
"""The platform's pack, installed file by file: what the ZIP holds, with
|
||||
resume, verification and only what is missing."""
|
||||
resume, verification and only what is missing.
|
||||
|
||||
``on_card``: the files go on the frontend's SD card, installed from the
|
||||
computer it is mounted on, whose root is named.
|
||||
"""
|
||||
if on_card:
|
||||
return [
|
||||
'???+ tip "Install with one command"',
|
||||
"",
|
||||
f" With the {display} SD card mounted, the installer downloads the files "
|
||||
"of the pack that are missing or differ from it into `Bios/<TAG>/` on "
|
||||
"the card, checks each one, and resumes where it stopped. `--dest` "
|
||||
"names the card's root.",
|
||||
"",
|
||||
" Linux, macOS:",
|
||||
"",
|
||||
" ```sh",
|
||||
f" {install_command('sh', f'--platform {name} --dest /path/to/card')}",
|
||||
" ```",
|
||||
"",
|
||||
" Windows (PowerShell, E: being the card):",
|
||||
"",
|
||||
" ```powershell",
|
||||
" " + install_command("powershell", f"--platform {name} --dest E:\\"),
|
||||
" ```",
|
||||
"",
|
||||
]
|
||||
return [
|
||||
'???+ tip "Install with one command"',
|
||||
"",
|
||||
|
||||
+60
-7
@@ -12,9 +12,46 @@ def _detect_systems(detect: list[dict]) -> list[str]:
|
||||
"""The operating systems the registry's detection rules cover, in order."""
|
||||
order = ("linux", "darwin", "windows")
|
||||
seen = {str(rule.get("os", "")) for rule in detect}
|
||||
if "any" in seen:
|
||||
return list(order)
|
||||
return [name for name in order if name in seen]
|
||||
|
||||
|
||||
def _card_section(platform_name: str, display: str) -> str:
|
||||
"""The one-line install for a frontend whose files live on its SD card.
|
||||
|
||||
The installer runs on the computer the card is mounted on, and the card
|
||||
root is named: no folder on that computer is one the frontend reads.
|
||||
"""
|
||||
return (
|
||||
" Option A: Automatic (recommended)\n"
|
||||
" ---------------------------------\n"
|
||||
" With the SD card mounted on a computer, name its root with --dest.\n"
|
||||
" Linux and macOS: run this in a terminal:\n\n"
|
||||
f" curl -fsSL {INSTALL_SH} | sh -s -- --platform {platform_name} --dest /path/to/card\n\n"
|
||||
" Windows: run this in PowerShell (E: being the card):\n\n"
|
||||
f" &([scriptblock]::Create((irm {INSTALL_PS1}))) --platform {platform_name} --dest E:\\\n\n"
|
||||
f" The installer writes each file where {display} hands it to the core\n"
|
||||
" and checks every file.\n\n"
|
||||
)
|
||||
|
||||
|
||||
def _card_manual(display: str) -> str:
|
||||
"""Where the files of a per-tag Bios/ layout go, copied by hand."""
|
||||
return (
|
||||
" Option B: Manual\n"
|
||||
" ----------------\n"
|
||||
" 1. Put the SD card in your PC\n"
|
||||
" 2. Extract all files from this archive directly into the root of the\n"
|
||||
" card: the archive holds the Bios/ folder, with one subfolder per\n"
|
||||
" launch tag (Bios/PS/, Bios/GBA/, ...)\n"
|
||||
" 3. Overwrite if asked\n\n"
|
||||
f" {display} hands each core the folder named after the tag in\n"
|
||||
" parentheses of its Roms folder (\"Sony PlayStation (PS)\" reads\n"
|
||||
" Bios/PS/). A Roms folder can be renamed as long as the tag stays.\n\n"
|
||||
)
|
||||
|
||||
|
||||
def _automatic_section(
|
||||
display: str, detect: list[dict], standalone_copies: list[dict] | None,
|
||||
) -> str:
|
||||
@@ -234,24 +271,40 @@ def _build_readme(
|
||||
system_filter: list[str] | None = None,
|
||||
detect: list[dict] | None = None,
|
||||
standalone_copies: list[dict] | None = None,
|
||||
launch_folders: int = 0,
|
||||
) -> str:
|
||||
"""Build a personalized step-by-step README for each platform pack."""
|
||||
"""Build a personalized step-by-step README for each platform pack.
|
||||
|
||||
``launch_folders`` counts the folders a platform that hands each core
|
||||
its own system directory gives them, where it names no system.
|
||||
"""
|
||||
narrowings = narrowings or []
|
||||
sep = "=" * 50
|
||||
scope = (
|
||||
f"{launch_folders} launch folders" if launch_folders and not num_systems
|
||||
else f"{num_systems} systems"
|
||||
)
|
||||
header = (
|
||||
f"{sep}\n"
|
||||
f" RETROBIOS - {platform_display} BIOS Pack\n"
|
||||
f" {total_files} files for {num_systems} systems\n"
|
||||
f" {total_files} files for {scope}\n"
|
||||
f"{sep}\n\n"
|
||||
)
|
||||
|
||||
detect = list(detect or [])
|
||||
bios_paths = [str(rule.get("bios_path", "")) for rule in detect]
|
||||
guide = (
|
||||
"INSTALLATION GUIDE\n\n"
|
||||
+ _automatic_section(platform_display, detect, standalone_copies)
|
||||
+ MANUAL_GUIDES.get(platform_name, _generic_manual(bios_paths))
|
||||
)
|
||||
if any(rule.get("method") == "card_marker" for rule in detect):
|
||||
guide = (
|
||||
"INSTALLATION GUIDE\n\n"
|
||||
+ _card_section(platform_name, platform_display)
|
||||
+ _card_manual(platform_display)
|
||||
)
|
||||
else:
|
||||
guide = (
|
||||
"INSTALLATION GUIDE\n\n"
|
||||
+ _automatic_section(platform_display, detect, standalone_copies)
|
||||
+ MANUAL_GUIDES.get(platform_name, _generic_manual(bios_paths))
|
||||
)
|
||||
|
||||
if regions:
|
||||
region_help = (
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Scraper for MinUI and the frontends built on it (NextUI, MyMinUI).
|
||||
|
||||
Source: the frontend's repository at a pinned commit
|
||||
Format: one emulator pak per launch tag, ``skeleton/SYSTEM/<device>/paks/Emus/
|
||||
<TAG>.pak/launch.sh`` (base install) or ``skeleton/EXTRAS/Emus/<device>/
|
||||
<TAG>.pak/launch.sh`` (extras bundle), whose script names the libretro core
|
||||
in ``EMU_EXE`` and starts it under minarch.
|
||||
|
||||
None of the three publishes a BIOS list in code: the frontend hands every
|
||||
core the folder ``<SD>/Bios/<TAG>/`` as its libretro system directory
|
||||
(``core.bios_dir``, returned for RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY) and
|
||||
never opens or hashes a BIOS file itself. What the code does state is which
|
||||
core each tag runs, and that tag decides the folder. So the platform file
|
||||
carries no file entry: it maps each folder to the cores launched with it,
|
||||
and the files are the ones the core profiles document, placed below that
|
||||
folder.
|
||||
|
||||
Paks that start a standalone binary (NDS.pak runs DraStic, P8-NATIVE.pak
|
||||
the PICO-8 executable) set no ``EMU_EXE`` and are not libretro launches:
|
||||
they are counted and left out.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import urllib.parse
|
||||
from collections.abc import Callable, Mapping
|
||||
from dataclasses import dataclass
|
||||
|
||||
try:
|
||||
from .base_scraper import (
|
||||
BaseScraper,
|
||||
BiosRequirement,
|
||||
fetch_branch_head,
|
||||
fetch_bytes,
|
||||
github_headers,
|
||||
)
|
||||
except ImportError:
|
||||
from base_scraper import (
|
||||
BaseScraper,
|
||||
BiosRequirement,
|
||||
fetch_branch_head,
|
||||
fetch_bytes,
|
||||
github_headers,
|
||||
)
|
||||
|
||||
PLATFORM_NAME = "minui"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Variant:
|
||||
"""One frontend of the family: where its code lives and what it is called."""
|
||||
|
||||
key: str
|
||||
name: str
|
||||
repo: str
|
||||
branch: str
|
||||
homepage: str
|
||||
# The file that sets core.bios_dir.
|
||||
minarch_source: str
|
||||
|
||||
|
||||
VARIANTS: dict[str, Variant] = {
|
||||
"minui": Variant(
|
||||
key="minui",
|
||||
name="MinUI",
|
||||
repo="shauninman/MinUI",
|
||||
branch="main",
|
||||
homepage="https://github.com/shauninman/MinUI",
|
||||
minarch_source="workspace/all/minarch/minarch.c",
|
||||
),
|
||||
"nextui": Variant(
|
||||
key="nextui",
|
||||
name="NextUI",
|
||||
repo="LoveRetro/NextUI",
|
||||
branch="main",
|
||||
homepage="https://nextui.loveretro.games",
|
||||
minarch_source="workspace/all/minarch/ma_core.c",
|
||||
),
|
||||
"myminui": Variant(
|
||||
key="myminui",
|
||||
name="MyMinUI",
|
||||
repo="Turro75/MyMinUI",
|
||||
branch="main",
|
||||
homepage="https://github.com/Turro75/MyMinUI",
|
||||
minarch_source="workspace/all/minarch/minarch.c",
|
||||
),
|
||||
}
|
||||
|
||||
# The folder minarch hands a core: SDCARD_PATH "/Bios/" and the launch tag.
|
||||
BIOS_ROOT = "Bios"
|
||||
_BIOS_DIR = re.compile(
|
||||
r'sprintf\(\s*\(char\s*\*\)\s*core\.bios_dir\s*,\s*SDCARD_PATH\s*"/Bios/%s"\s*,\s*core\.tag\s*\)'
|
||||
)
|
||||
_PAK = re.compile(
|
||||
r"^skeleton/(?:SYSTEM/(?P<base>[^/]+)/paks/Emus|EXTRAS/Emus/(?P<extra>[^/]+))"
|
||||
r"/(?P<tag>[^/]+)\.pak/launch\.sh$"
|
||||
)
|
||||
_EMU_EXE = re.compile(r"^[ \t]*EMU_EXE=([A-Za-z0-9_.-]+)[ \t]*$", re.MULTILINE)
|
||||
# Both ways a pak starts its core: minarch on the core's .so directly, or
|
||||
# MyMinUI's launch_rom.sh, which runs minarch (or RetroArch with
|
||||
# system_directory set to the same folder) on ${EMU_EXE}_libretro.so.
|
||||
_STARTS_CORE = re.compile(
|
||||
r'^[ \t]*(?:nice\s+-?\d+\s+)?minarch\.elf\s+"\$CORES_PATH/\$\{EMU_EXE\}_libretro\.so"'
|
||||
r"|^[ \t]*launch_rom\.sh\s",
|
||||
re.MULTILINE,
|
||||
)
|
||||
# Each device builds its cores from workspace/<device>/cores/makefile through
|
||||
# the template in workspace/all/cores/makefile: `<X>_REPO ?=
|
||||
# https://github.com/libretro/<X>` and the library `<X>_libretro.so` unless
|
||||
# `<X>_CORE` names another.
|
||||
_RECIPE = re.compile(r"^workspace/(?P<device>[^/]+)/cores/makefile$")
|
||||
_CORES_LIST = re.compile(r"^CORES\s*\+?=(.*)$", re.MULTILINE)
|
||||
_CORE_VAR = re.compile(r"^(\S+?)_(REPO|CORE)\s*\??=\s*(\S+)", re.MULTILINE)
|
||||
# A library built from a project other than the libretro core of that name:
|
||||
# MyMinUI builds dosbox-pure into dosbox_libretro.so. The core is the project
|
||||
# the bytes come from.
|
||||
BUILT_AS = {
|
||||
"https://github.com/libretro/dosbox-pure": "dosbox_pure",
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Pak:
|
||||
"""One emulator pak as its launch script states it."""
|
||||
|
||||
tag: str
|
||||
device: str
|
||||
core: str
|
||||
path: str
|
||||
line: int
|
||||
|
||||
|
||||
def read_pak(path: str, text: str) -> Pak | None:
|
||||
"""The libretro launch a pak's script makes, or None for a standalone one.
|
||||
|
||||
A script that names a core but starts it in a way not read here raises:
|
||||
the shape of the paks changed and the scrape must say so.
|
||||
"""
|
||||
match = _PAK.match(path)
|
||||
if not match:
|
||||
return None
|
||||
exe = _EMU_EXE.search(text)
|
||||
if not exe:
|
||||
return None
|
||||
if not _STARTS_CORE.search(text):
|
||||
raise ValueError(f"{path}: EMU_EXE={exe.group(1)} but no minarch launch")
|
||||
line = text.count("\n", 0, exe.start()) + 1
|
||||
return Pak(
|
||||
tag=match.group("tag"),
|
||||
device=match.group("base") or match.group("extra"),
|
||||
core=exe.group(1),
|
||||
path=path,
|
||||
line=line,
|
||||
)
|
||||
|
||||
|
||||
def normalize_repository(url: str) -> str:
|
||||
"""A repository URL without the .git suffix or a trailing slash."""
|
||||
url = url.strip().rstrip("/")
|
||||
return url[: -len(".git")] if url.endswith(".git") else url
|
||||
|
||||
|
||||
def read_recipes(text: str) -> dict[str, str]:
|
||||
"""Library file name to source repository, for one device's core makefile."""
|
||||
text = "\n".join(line.split("#", 1)[0] for line in text.splitlines())
|
||||
names = [name for match in _CORES_LIST.finditer(text) for name in match.group(1).split()]
|
||||
values: dict[tuple[str, str], str] = {
|
||||
(match.group(1), match.group(2)): match.group(3) for match in _CORE_VAR.finditer(text)
|
||||
}
|
||||
return {
|
||||
values.get((name, "CORE"), f"{name}_libretro.so"): normalize_repository(
|
||||
values.get((name, "REPO"), f"https://github.com/libretro/{name}")
|
||||
)
|
||||
for name in names
|
||||
}
|
||||
|
||||
|
||||
def bios_dir_ref(source: str, text: str) -> str:
|
||||
"""``file:line`` of the statement that sets the per-tag system directory."""
|
||||
match = _BIOS_DIR.search(text)
|
||||
if not match:
|
||||
raise ValueError(f"{source}: core.bios_dir is no longer SDCARD_PATH/Bios/<tag>")
|
||||
return f"{source}:{text.count(chr(10), 0, match.start()) + 1}"
|
||||
|
||||
|
||||
def core_identity(library: str, repositories: Mapping[str, set[str]]) -> str:
|
||||
"""The core a pak's library is, by the project it is built from."""
|
||||
stem = library.removesuffix("_libretro.so")
|
||||
built = sorted({BUILT_AS[r] for r in repositories.get(library, set()) if r in BUILT_AS})
|
||||
if len(built) > 1:
|
||||
raise ValueError(f"{library} is built from projects of {', '.join(built)}")
|
||||
return built[0] if built else stem
|
||||
|
||||
|
||||
def core_system_dirs(
|
||||
paks: list[Pak], repositories: Mapping[str, set[str]] | None = None
|
||||
) -> dict[str, dict]:
|
||||
"""Each Bios/<TAG> folder with the cores its paks run and where they ship."""
|
||||
repositories = repositories or {}
|
||||
by_dir: dict[str, dict] = {}
|
||||
for pak in sorted(paks, key=lambda p: (p.tag, p.device, p.path)):
|
||||
entry = by_dir.setdefault(
|
||||
f"{BIOS_ROOT}/{pak.tag}",
|
||||
{"cores": [], "devices": [], "source_ref": f"{pak.path}:{pak.line}"},
|
||||
)
|
||||
core = core_identity(f"{pak.core}_libretro.so", repositories)
|
||||
if core not in entry["cores"]:
|
||||
entry["cores"].append(core)
|
||||
if pak.device not in entry["devices"]:
|
||||
entry["devices"].append(pak.device)
|
||||
for entry in by_dir.values():
|
||||
entry["cores"].sort()
|
||||
entry["devices"].sort()
|
||||
return dict(sorted(by_dir.items()))
|
||||
|
||||
|
||||
def platform_config(
|
||||
variant: Variant, revision: str, date: str, files: Mapping[str, str]
|
||||
) -> dict:
|
||||
"""The platform file for one frontend, from its sources at ``revision``.
|
||||
|
||||
``files`` maps repository paths to their text: every pak launch script,
|
||||
every device's core makefile and the minarch source that sets the
|
||||
system directory.
|
||||
"""
|
||||
if variant.minarch_source not in files:
|
||||
raise ValueError(f"{variant.repo}@{revision}: no {variant.minarch_source}")
|
||||
reference = bios_dir_ref(variant.minarch_source, files[variant.minarch_source])
|
||||
paks = [pak for path, text in sorted(files.items()) if (pak := read_pak(path, text))]
|
||||
if not paks:
|
||||
raise ValueError(f"{variant.repo}@{revision}: no pak starts a libretro core")
|
||||
repositories: dict[str, set[str]] = {}
|
||||
for path, text in files.items():
|
||||
if _RECIPE.match(path):
|
||||
for library, repository in read_recipes(text).items():
|
||||
repositories.setdefault(library, set()).add(repository)
|
||||
dirs = core_system_dirs(paks, repositories)
|
||||
built_from = {
|
||||
core_identity(f"{pak.core}_libretro.so", repositories): sorted(
|
||||
repositories.get(f"{pak.core}_libretro.so", set())
|
||||
)
|
||||
for pak in paks
|
||||
}
|
||||
return {
|
||||
"platform": variant.name,
|
||||
"version": date,
|
||||
"homepage": variant.homepage,
|
||||
"source": f"https://github.com/{variant.repo}/tree/{revision}",
|
||||
# The card root: every core reads below Bios/<TAG>/ on it.
|
||||
"base_destination": "",
|
||||
"hash_type": "sha1",
|
||||
# minarch hands the folder and never reads or hashes a file in it.
|
||||
"verification_mode": "existence",
|
||||
"system_directory_ref": reference,
|
||||
"cores": sorted({core for entry in dirs.values() for core in entry["cores"]}),
|
||||
"core_system_dirs": dirs,
|
||||
# Where each core's library is built from, read from the device
|
||||
# makefiles. A pak may ship a library no makefile builds.
|
||||
"core_repositories": {core: repos for core, repos in sorted(built_from.items()) if repos},
|
||||
"systems": {},
|
||||
}
|
||||
|
||||
|
||||
class Scraper(BaseScraper):
|
||||
"""Pak scraper for one frontend of the MinUI family."""
|
||||
|
||||
lists_files = False
|
||||
variant: Variant = VARIANTS["minui"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
url: str | None = None,
|
||||
fetch: Callable[[str, dict[str, str] | None], bytes] | None = None,
|
||||
) -> None:
|
||||
self._fetch = fetch or (lambda u, h=None: fetch_bytes(u, headers=h))
|
||||
self.revision = ""
|
||||
if url is None:
|
||||
self.revision = fetch_branch_head(self.variant.repo, self.variant.branch)
|
||||
url = f"https://github.com/{self.variant.repo}/tree/{self.revision}"
|
||||
super().__init__(url=url)
|
||||
self._config: dict | None = None
|
||||
|
||||
def _api(self, path: str) -> dict:
|
||||
url = f"https://api.github.com/repos/{self.variant.repo}/{path}"
|
||||
try:
|
||||
return json.loads(self._fetch(url, github_headers()))
|
||||
except (ConnectionError, json.JSONDecodeError) as exc:
|
||||
raise RuntimeError(f"cannot read {url}: {exc}") from exc
|
||||
|
||||
def _raw(self, path: str) -> str:
|
||||
url = (
|
||||
f"https://raw.githubusercontent.com/{self.variant.repo}/{self.revision}/"
|
||||
f"{urllib.parse.quote(path)}"
|
||||
)
|
||||
return self._fetch(url, None).decode("utf-8", errors="replace")
|
||||
|
||||
def config(self) -> dict:
|
||||
"""The platform file, read once."""
|
||||
if self._config is not None:
|
||||
return self._config
|
||||
if not self.revision:
|
||||
self.revision = self.url.rstrip("/").rsplit("/", 1)[-1]
|
||||
tree = self._api(f"git/trees/{self.revision}?recursive=1")
|
||||
if tree.get("truncated"):
|
||||
raise RuntimeError(f"{self.variant.repo}@{self.revision}: tree listing truncated")
|
||||
paths = [
|
||||
item["path"] for item in tree.get("tree", [])
|
||||
if item.get("type") == "blob"
|
||||
and (
|
||||
_PAK.match(item["path"])
|
||||
or _RECIPE.match(item["path"])
|
||||
or item["path"] == self.variant.minarch_source
|
||||
)
|
||||
]
|
||||
commit = self._api(f"commits/{self.revision}")
|
||||
date = commit.get("commit", {}).get("committer", {}).get("date", "")[:10]
|
||||
if not date:
|
||||
raise RuntimeError(f"{self.variant.repo}@{self.revision}: commit has no date")
|
||||
files = {path: self._raw(path) for path in paths}
|
||||
self._config = platform_config(self.variant, self.revision, date, files)
|
||||
return self._config
|
||||
|
||||
def fetch_requirements(self) -> list[BiosRequirement]:
|
||||
"""None: the frontend names no BIOS file in code, its cores do."""
|
||||
self.config()
|
||||
return []
|
||||
|
||||
def validate_format(self, raw_data: str) -> bool:
|
||||
return bool(self.config().get("core_system_dirs"))
|
||||
|
||||
def generate_platform_yaml(self) -> dict:
|
||||
return self.config()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
try:
|
||||
from .base_scraper import scraper_cli
|
||||
except ImportError:
|
||||
from base_scraper import scraper_cli
|
||||
|
||||
scraper_cli(Scraper, "Scrape the MinUI pak list")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Scraper for MyMinUI's pak list: the MinUI scraper on MyMinUI's repository."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
try:
|
||||
from . import minui_scraper
|
||||
except ImportError:
|
||||
import minui_scraper
|
||||
|
||||
PLATFORM_NAME = "myminui"
|
||||
|
||||
|
||||
class Scraper(minui_scraper.Scraper):
|
||||
"""Pak scraper for MyMinUI."""
|
||||
|
||||
variant = minui_scraper.VARIANTS["myminui"]
|
||||
|
||||
|
||||
def main() -> None:
|
||||
try:
|
||||
from .base_scraper import scraper_cli
|
||||
except ImportError:
|
||||
from base_scraper import scraper_cli
|
||||
|
||||
scraper_cli(Scraper, "Scrape the MyMinUI pak list")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Scraper for NextUI's pak list: the MinUI scraper on NextUI's repository."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
try:
|
||||
from . import minui_scraper
|
||||
except ImportError:
|
||||
import minui_scraper
|
||||
|
||||
PLATFORM_NAME = "nextui"
|
||||
|
||||
|
||||
class Scraper(minui_scraper.Scraper):
|
||||
"""Pak scraper for NextUI."""
|
||||
|
||||
variant = minui_scraper.VARIANTS["nextui"]
|
||||
|
||||
|
||||
def main() -> None:
|
||||
try:
|
||||
from .base_scraper import scraper_cli
|
||||
except ImportError:
|
||||
from base_scraper import scraper_cli
|
||||
|
||||
scraper_cli(Scraper, "Scrape the NextUI pak list")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+24
-1
@@ -56,6 +56,17 @@ from exporter.baseline import ( # noqa: E402
|
||||
|
||||
UPSTREAM_CACHE = REPO_ROOT / ".cache" / "upstream-native"
|
||||
|
||||
|
||||
def _without_native_list() -> set[str]:
|
||||
"""Registered platforms whose code names no BIOS file to hand back."""
|
||||
import yaml # noqa: PLC0415
|
||||
|
||||
registry = yaml.safe_load((REPO_ROOT / "platforms" / "_registry.yml").read_text())
|
||||
return {
|
||||
name for name, entry in registry["platforms"].items()
|
||||
if entry.get("native_list") == "none"
|
||||
}
|
||||
|
||||
TRUTH = {
|
||||
"systems": {
|
||||
"sony-playstation": {
|
||||
@@ -552,7 +563,19 @@ class ExporterRegistry(unittest.TestCase):
|
||||
str(REPO_ROOT / "platforms"), include_archived=True
|
||||
)
|
||||
)
|
||||
self.assertEqual(registered - set(exporters), set())
|
||||
self.assertEqual(registered - set(exporters) - _without_native_list(), set())
|
||||
|
||||
def test_a_platform_without_a_native_list_declares_no_file(self):
|
||||
"""`native_list: none` says the platform's code names no BIOS file
|
||||
(MinUI hands each core a folder and opens nothing in it). A platform
|
||||
whose file declares one has a list to hand back."""
|
||||
for name in _without_native_list():
|
||||
config = load_platform_config(name, str(REPO_ROOT / "platforms"))
|
||||
with self.subTest(platform=name):
|
||||
self.assertNotIn(name, discover_exporters())
|
||||
self.assertFalse(any(
|
||||
system.get("files") for system in (config.get("systems") or {}).values()
|
||||
))
|
||||
|
||||
def test_no_exporter_is_registered_for_an_unregistered_platform(self):
|
||||
"""A dead exporter is one nobody will notice has stopped working."""
|
||||
|
||||
@@ -0,0 +1,415 @@
|
||||
"""MinUI, NextUI and MyMinUI hand each core a system directory of its own.
|
||||
|
||||
minarch sets ``core.bios_dir`` to SDCARD_PATH "/Bios/<TAG>", the tag being
|
||||
the pak's folder name and the one in parentheses of the Roms folder, and
|
||||
returns it for RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY. A core run from
|
||||
GB.pak reads gb_bios.bin from Bios/GB/, the same core run from GBC.pak from
|
||||
Bios/GBC/. The frontends name no BIOS file, so the platform file maps each
|
||||
folder to the cores its paks start, and the files come from the core
|
||||
profiles, placed below that folder.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
import zipfile
|
||||
import zlib
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
||||
sys.path.insert(0, str(REPO_ROOT))
|
||||
|
||||
from common import load_emulator_profiles, load_platform_config # noqa: E402
|
||||
from scraper import minui_scraper # noqa: E402
|
||||
|
||||
MINARCH = (
|
||||
"void Core_open(const char* core_path, const char* tag_name) {\n"
|
||||
'\tsprintf((char*)core.saves_dir, SDCARD_PATH "/Saves/%s", core.tag);\n'
|
||||
'\tsprintf((char*)core.bios_dir, SDCARD_PATH "/Bios/%s", core.tag);\n'
|
||||
"}\n"
|
||||
)
|
||||
BASE_PAK = (
|
||||
"#!/bin/sh\n\n"
|
||||
"EMU_EXE=gambatte\n\n"
|
||||
"###############################\n\n"
|
||||
'EMU_TAG=$(basename "$(dirname "$0")" .pak)\n'
|
||||
'mkdir -p "$BIOS_PATH/$EMU_TAG"\n'
|
||||
'nice -20 minarch.elf "$CORES_PATH/${EMU_EXE}_libretro.so" "$ROM" &> "$LOGS_PATH/$EMU_TAG.txt"\n'
|
||||
)
|
||||
# MyMinUI's shape: the pak hands its folder to launch_rom.sh.
|
||||
EXTRA_PAK = (
|
||||
"#!/bin/sh\n\n"
|
||||
"EMU_EXE=dosbox\n\n"
|
||||
"RUN=minarch\nRUN2=minarch\n"
|
||||
'CURDIR="$(dirname "$0")"\n'
|
||||
'launch_rom.sh "$1" "$2" "$3" "$4" ${RUN} ${RUN2} "$CURDIR" "$EMU_EXE" $CPU_OC\n'
|
||||
)
|
||||
STANDALONE_PAK = (
|
||||
"#!/bin/sh\n"
|
||||
'if [ -f /mnt/SDCARD/Bios/NDS/drastic ]; then\n'
|
||||
' /mnt/SDCARD/Bios/NDS/drastic "$1"\n'
|
||||
"fi\n"
|
||||
)
|
||||
RECIPE = (
|
||||
"CORES = gambatte dosbox # mesen\n"
|
||||
"dosbox_REPO = https://github.com/libretro/dosbox-pure.git\n"
|
||||
"dosbox_CORE = dosbox_libretro.so\n"
|
||||
)
|
||||
FIXTURE = {
|
||||
"workspace/all/minarch/minarch.c": MINARCH,
|
||||
"skeleton/SYSTEM/miyoomini/paks/Emus/GB.pak/launch.sh": BASE_PAK,
|
||||
"skeleton/SYSTEM/tg5040/paks/Emus/GB.pak/launch.sh": BASE_PAK,
|
||||
"skeleton/EXTRAS/Emus/tg5040/DOS.pak/launch.sh": EXTRA_PAK,
|
||||
"skeleton/EXTRAS/Emus/miyoomini/NDS.pak/launch.sh": STANDALONE_PAK,
|
||||
"workspace/tg5040/cores/makefile": RECIPE,
|
||||
}
|
||||
|
||||
|
||||
class ScraperReadsThePaks(unittest.TestCase):
|
||||
"""Two paks, one per bundle, and a standalone one left out."""
|
||||
|
||||
def config(self, files: dict[str, str] | None = None) -> dict:
|
||||
return minui_scraper.platform_config(
|
||||
minui_scraper.VARIANTS["minui"], "f" * 40, "2026-01-01", files or FIXTURE
|
||||
)
|
||||
|
||||
def test_each_tag_is_a_folder_below_bios(self):
|
||||
dirs = self.config()["core_system_dirs"]
|
||||
self.assertEqual(sorted(dirs), ["Bios/DOS", "Bios/GB"])
|
||||
self.assertEqual(dirs["Bios/GB"]["cores"], ["gambatte"])
|
||||
self.assertEqual(dirs["Bios/GB"]["devices"], ["miyoomini", "tg5040"])
|
||||
self.assertEqual(
|
||||
dirs["Bios/GB"]["source_ref"],
|
||||
"skeleton/SYSTEM/miyoomini/paks/Emus/GB.pak/launch.sh:3",
|
||||
)
|
||||
|
||||
def test_the_core_is_the_project_the_library_is_built_from(self):
|
||||
"""MyMinUI builds dosbox-pure into dosbox_libretro.so: the pak's
|
||||
EMU_EXE=dosbox is DOSBox Pure, whose profile reads other files than
|
||||
the dosbox core's."""
|
||||
config = self.config()
|
||||
self.assertEqual(config["core_system_dirs"]["Bios/DOS"]["cores"], ["dosbox_pure"])
|
||||
self.assertEqual(
|
||||
config["core_repositories"]["dosbox_pure"],
|
||||
["https://github.com/libretro/dosbox-pure"],
|
||||
)
|
||||
self.assertEqual(config["cores"], ["dosbox_pure", "gambatte"])
|
||||
|
||||
def test_the_folder_rule_is_read_from_minarch(self):
|
||||
config = self.config()
|
||||
self.assertEqual(config["system_directory_ref"], "workspace/all/minarch/minarch.c:3")
|
||||
self.assertEqual(config["base_destination"], "")
|
||||
self.assertEqual(config["verification_mode"], "existence")
|
||||
|
||||
def test_a_standalone_pak_is_not_a_core(self):
|
||||
dirs = self.config()["core_system_dirs"]
|
||||
self.assertNotIn("Bios/NDS", dirs)
|
||||
|
||||
def test_a_named_core_started_another_way_raises(self):
|
||||
files = {**FIXTURE, "skeleton/SYSTEM/miyoomini/paks/Emus/GB.pak/launch.sh":
|
||||
"EMU_EXE=gambatte\nretroarch -L core.so\n"}
|
||||
with self.assertRaises(ValueError):
|
||||
self.config(files)
|
||||
|
||||
def test_a_moved_system_directory_raises(self):
|
||||
files = {**FIXTURE, "workspace/all/minarch/minarch.c":
|
||||
'sprintf((char*)core.bios_dir, SDCARD_PATH "/BIOS");\n'}
|
||||
with self.assertRaises(ValueError):
|
||||
self.config(files)
|
||||
|
||||
def test_the_scraper_reads_the_pinned_revision(self):
|
||||
"""Tree, commit and file reads all name the same commit."""
|
||||
revision = "a" * 40
|
||||
asked: list[str] = []
|
||||
|
||||
def fetch(url: str, _headers: dict[str, str] | None = None) -> bytes:
|
||||
asked.append(url)
|
||||
if url.endswith(f"git/trees/{revision}?recursive=1"):
|
||||
tree = [{"path": p, "type": "blob"} for p in FIXTURE]
|
||||
tree.append({"path": "skeleton/BASE/README.txt", "type": "blob"})
|
||||
return json.dumps({"tree": tree, "truncated": False}).encode()
|
||||
if url.endswith(f"commits/{revision}"):
|
||||
return json.dumps(
|
||||
{"commit": {"committer": {"date": "2026-02-03T04:05:06Z"}}}
|
||||
).encode()
|
||||
prefix = f"https://raw.githubusercontent.com/shauninman/MinUI/{revision}/"
|
||||
self.assertTrue(url.startswith(prefix), url)
|
||||
return FIXTURE[url[len(prefix):]].encode()
|
||||
|
||||
scraper = minui_scraper.Scraper(
|
||||
url=f"https://github.com/shauninman/MinUI/tree/{revision}", fetch=fetch
|
||||
)
|
||||
config = scraper.generate_platform_yaml()
|
||||
self.assertEqual(config["version"], "2026-02-03")
|
||||
self.assertEqual(config["source"], f"https://github.com/shauninman/MinUI/tree/{revision}")
|
||||
self.assertNotIn(
|
||||
f"https://raw.githubusercontent.com/shauninman/MinUI/{revision}/skeleton/BASE/README.txt",
|
||||
asked,
|
||||
)
|
||||
|
||||
|
||||
def _crc(data: bytes) -> str:
|
||||
return format(zlib.crc32(data) & 0xFFFFFFFF, "08x")
|
||||
|
||||
|
||||
class PackPlacesCoreFilesUnderTheTag(unittest.TestCase):
|
||||
"""The pack, the report and the manifest put a core's files in each
|
||||
folder the platform hands it, and nowhere else."""
|
||||
|
||||
def setUp(self):
|
||||
from common import _emulator_profiles_cache
|
||||
|
||||
_emulator_profiles_cache.clear()
|
||||
self.root = tempfile.mkdtemp(dir=REPO_ROOT / "tmp" / "tests")
|
||||
self.bios = os.path.join(self.root, "bios")
|
||||
self.platforms = os.path.join(self.root, "platforms")
|
||||
self.emulators = os.path.join(self.root, "emulators")
|
||||
for folder in (self.bios, self.platforms, self.emulators):
|
||||
os.makedirs(folder)
|
||||
self.records: dict[str, dict] = {}
|
||||
self._file("Nintendo/Game Boy/gb_bios.bin", b"DMG BOOT ROM")
|
||||
# Two palettes of one name: the core's path, below its folder,
|
||||
# must still choose its own.
|
||||
self._file("Other/gambatte/palettes/default.pal", b"GAMBATTE PALETTE")
|
||||
self._file("Other/elsewhere/default.pal", b"ANOTHER PALETTE")
|
||||
self.member = b"ARCADE BIOS ROM"
|
||||
buffer = os.path.join(self.root, "arc.zip")
|
||||
with zipfile.ZipFile(buffer, "w") as archive:
|
||||
archive.writestr("rom_a.bin", self.member)
|
||||
self._file("Arcade/arc.zip", Path(buffer).read_bytes())
|
||||
self._file("Other/unlisted/unlisted.bin", b"NOT HANDED")
|
||||
self.db = self._database()
|
||||
|
||||
platform = {
|
||||
"platform": "MinUI Test",
|
||||
"base_destination": "",
|
||||
"verification_mode": "existence",
|
||||
"cores": ["gambatte", "arcadecore", "unlisted"],
|
||||
"core_system_dirs": {
|
||||
"Bios/GB": {"cores": ["gambatte"], "source_ref": "GB.pak/launch.sh:3"},
|
||||
"Bios/GBC": {"cores": ["gambatte"], "source_ref": "GBC.pak/launch.sh:3"},
|
||||
"Bios/ARC": {"cores": ["arcade_lib"], "source_ref": "ARC.pak/launch.sh:3"},
|
||||
},
|
||||
"systems": {},
|
||||
}
|
||||
Path(self.platforms, "test_minui.yml").write_text(yaml.safe_dump(platform))
|
||||
profiles = {
|
||||
"gambatte": {
|
||||
"emulator": "Gambatte", "type": "libretro", "systems": ["nintendo-gb"],
|
||||
"files": [
|
||||
{"name": "gb_bios.bin", "required": False},
|
||||
{"name": "default.pal", "path": "palettes/default.pal", "required": False},
|
||||
],
|
||||
},
|
||||
"arcadecore": {
|
||||
"emulator": "Arcade", "type": "libretro", "systems": ["arcade"],
|
||||
"cores": ["arcade_lib"], "archive_prefix": "arcadecore",
|
||||
"files": [
|
||||
{"name": "rom_a.bin", "archive": "arc.zip", "required": True,
|
||||
"crc32": _crc(self.member)},
|
||||
],
|
||||
},
|
||||
"unlisted": {
|
||||
"emulator": "Unlisted", "type": "libretro", "systems": ["other"],
|
||||
"files": [{"name": "unlisted.bin", "required": True}],
|
||||
},
|
||||
}
|
||||
for key, profile in profiles.items():
|
||||
Path(self.emulators, f"{key}.yml").write_text(yaml.safe_dump(profile))
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.root)
|
||||
|
||||
def _file(self, relative: str, data: bytes) -> None:
|
||||
path = os.path.join(self.bios, relative)
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
Path(path).write_bytes(data)
|
||||
self.records[relative] = {
|
||||
"path": path,
|
||||
"name": os.path.basename(relative),
|
||||
"sha1": hashlib.sha1(data).hexdigest(),
|
||||
"md5": hashlib.md5(data).hexdigest(),
|
||||
"crc32": _crc(data),
|
||||
"size": len(data),
|
||||
}
|
||||
|
||||
def _database(self) -> dict:
|
||||
files, by_name, by_md5, by_crc32, by_suffix = {}, {}, {}, {}, {}
|
||||
for relative, record in self.records.items():
|
||||
sha1 = record["sha1"]
|
||||
files[sha1] = {k: v for k, v in record.items() if k != "sha1"}
|
||||
by_name.setdefault(record["name"], []).append(sha1)
|
||||
by_md5[record["md5"]] = sha1
|
||||
by_crc32[record["crc32"]] = sha1
|
||||
parts = relative.split("/")
|
||||
for start in range(len(parts) - 1):
|
||||
by_suffix.setdefault("/".join(parts[start:]), []).append(sha1)
|
||||
return {"files": files, "indexes": {
|
||||
"by_name": by_name, "by_md5": by_md5, "by_crc32": by_crc32,
|
||||
"by_path_suffix": by_suffix,
|
||||
}}
|
||||
|
||||
def _pack(self) -> dict[str, bytes]:
|
||||
from generate_pack import generate_pack
|
||||
|
||||
output = os.path.join(self.root, "dist")
|
||||
os.makedirs(output)
|
||||
zip_path = generate_pack(
|
||||
"test_minui", self.platforms, self.db, self.bios, output,
|
||||
emulators_dir=self.emulators, offline=True,
|
||||
)
|
||||
self.assertIsNotNone(zip_path)
|
||||
with zipfile.ZipFile(zip_path) as archive:
|
||||
return {
|
||||
name: archive.read(name) for name in archive.namelist()
|
||||
if name not in ("README.txt", "manifest.json")
|
||||
}
|
||||
|
||||
def test_a_core_file_lands_in_every_folder_of_the_core(self):
|
||||
packed = self._pack()
|
||||
self.assertEqual(packed["Bios/GB/gb_bios.bin"], b"DMG BOOT ROM")
|
||||
self.assertEqual(packed["Bios/GBC/gb_bios.bin"], b"DMG BOOT ROM")
|
||||
|
||||
def test_the_core_path_still_chooses_the_copy(self):
|
||||
packed = self._pack()
|
||||
self.assertEqual(packed["Bios/GB/palettes/default.pal"], b"GAMBATTE PALETTE")
|
||||
|
||||
def test_an_archive_and_its_core_subfolder_move_with_the_folder(self):
|
||||
packed = self._pack()
|
||||
self.assertIn("Bios/ARC/arc.zip", packed)
|
||||
self.assertIn("Bios/ARC/arcadecore/arc.zip", packed)
|
||||
|
||||
def test_nothing_outside_a_handed_folder(self):
|
||||
"""A core no pak starts gets no folder, so none of its files."""
|
||||
packed = self._pack()
|
||||
outside = [
|
||||
name for name in packed
|
||||
if "/".join(name.split("/")[:2]) not in ("Bios/GB", "Bios/GBC", "Bios/ARC")
|
||||
]
|
||||
self.assertEqual(outside, [])
|
||||
|
||||
def test_the_same_cores_in_other_folders_make_another_pack(self):
|
||||
from common import group_identical_platforms
|
||||
|
||||
twin = yaml.safe_load(Path(self.platforms, "test_minui.yml").read_text())
|
||||
twin["core_system_dirs"]["Bios/GB"]["cores"] = ["arcade_lib"]
|
||||
Path(self.platforms, "test_twin.yml").write_text(yaml.safe_dump(twin))
|
||||
groups = group_identical_platforms(["test_minui", "test_twin"], self.platforms)
|
||||
self.assertEqual(len(groups), 2)
|
||||
|
||||
def test_the_report_and_the_manifest_agree_with_the_pack(self):
|
||||
from generate_pack import generate_manifest
|
||||
from verify import verify_platform
|
||||
|
||||
packed = set(self._pack())
|
||||
config = load_platform_config("test_minui", self.platforms)
|
||||
profiles = load_emulator_profiles(self.emulators)
|
||||
report = verify_platform(config, self.db, self.emulators, profiles)
|
||||
self.assertEqual(set(report["pack_destinations"]), packed)
|
||||
manifest = generate_manifest(
|
||||
"test_minui", self.platforms, self.db, self.bios,
|
||||
os.path.join(self.platforms, "_registry.yml"), emulators_dir=self.emulators,
|
||||
emu_profiles=profiles, offline=True,
|
||||
)
|
||||
self.assertEqual({entry["dest"] for entry in manifest["files"]}, packed)
|
||||
|
||||
|
||||
class PublishedPlatformsHoldTheRule(unittest.TestCase):
|
||||
"""The committed files of the family, read as the tools read them."""
|
||||
|
||||
FAMILY = ("minui", "nextui", "myminui")
|
||||
|
||||
def test_every_core_resolves_to_a_profile_of_its_project(self):
|
||||
"""A core named in core_system_dirs is the profile built from the
|
||||
repository the platform's recipe names: a library renamed from
|
||||
another project (dosbox-pure as dosbox) would hand the wrong
|
||||
profile's files to the folder."""
|
||||
from common import resolve_platform_cores
|
||||
|
||||
profiles = load_emulator_profiles(str(REPO_ROOT / "emulators"))
|
||||
|
||||
def norm(url: str) -> str:
|
||||
url = str(url).strip().rstrip("/").lower()
|
||||
url = url.split("/tree/", 1)[0]
|
||||
return url[: -len(".git")] if url.endswith(".git") else url
|
||||
|
||||
for platform in self.FAMILY:
|
||||
config = load_platform_config(platform, str(REPO_ROOT / "platforms"))
|
||||
resolved = resolve_platform_cores(config, profiles)
|
||||
for core, repositories in config["core_repositories"].items():
|
||||
matches = [
|
||||
key for key in resolved
|
||||
if (key == core or core in [str(c) for c in profiles[key].get("cores") or []])
|
||||
and profiles[key].get("type") not in ("alias", "launcher")
|
||||
]
|
||||
with self.subTest(platform=platform, core=core):
|
||||
self.assertTrue(matches, f"{core} resolves to no profile")
|
||||
for key in matches:
|
||||
declared = []
|
||||
for field in ("source", "upstream"):
|
||||
value = profiles[key].get(field)
|
||||
declared += list(value.values()) if isinstance(value, dict) else [value]
|
||||
self.assertTrue(
|
||||
{norm(r) for r in repositories} & {norm(d) for d in declared if d},
|
||||
f"{key} documents {declared}, {platform} builds {repositories}",
|
||||
)
|
||||
|
||||
def test_the_registry_detects_the_card_the_installer_reads(self):
|
||||
import install
|
||||
|
||||
registry = yaml.safe_load((REPO_ROOT / "platforms" / "_registry.yml").read_text())
|
||||
rules = {
|
||||
(name, rule["file"], rule["first_line_prefix"])
|
||||
for name, entry in registry["platforms"].items()
|
||||
for rule in (entry.get("install") or {}).get("detect", [])
|
||||
if rule["method"] == "card_marker"
|
||||
}
|
||||
self.assertEqual(set(install._CARD_RULES), rules)
|
||||
self.assertEqual({name for name, _f, _p in rules}, set(self.FAMILY))
|
||||
|
||||
|
||||
class InstallerReadsTheCard(unittest.TestCase):
|
||||
"""install.py runs on the computer the card is mounted on."""
|
||||
|
||||
def setUp(self):
|
||||
self.root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp" / "tests"))
|
||||
(self.root / ".system").mkdir()
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.root)
|
||||
|
||||
def test_the_release_name_names_the_frontend(self):
|
||||
import install
|
||||
|
||||
for first_line, expected in (
|
||||
("MinUI-20251127-0", "minui"),
|
||||
("MyMinUI-20260809b-0", "myminui"),
|
||||
("NextUI-20261020-1", "nextui"),
|
||||
("Onion-4.3", None),
|
||||
):
|
||||
(self.root / ".system" / "version.txt").write_text(f"{first_line}\nabc123\n")
|
||||
with self.subTest(first_line=first_line):
|
||||
self.assertEqual(install.card_platform(self.root), expected)
|
||||
|
||||
def test_a_folder_inside_the_card_is_refused(self):
|
||||
"""Bios/ given for the card would install into Bios/Bios/<TAG>/."""
|
||||
import install
|
||||
|
||||
(self.root / ".system" / "version.txt").write_text("MinUI-20251127-0\n")
|
||||
(self.root / "Bios").mkdir()
|
||||
with self.assertRaises(SystemExit):
|
||||
install._check_card_root("minui", self.root / "Bios")
|
||||
install._check_card_root("minui", self.root)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user