mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-04-13 12:22:33 -05:00
feat: re-profile 22 emulators, refactor validation to common.py
batch re-profiled nekop2 through pokemini. mupen64plus renamed to mupen64plus_next. new profiles: nes, mupen64plus_next. validation functions (_build_validation_index, check_file_validation) consolidated in common.py — single source of truth for verify.py and generate_pack.py. pipeline 100% consistent on all 6 platforms.
This commit is contained in:
@@ -1,30 +1,111 @@
|
||||
# LRPS2 — Sony PlayStation 2 (libretro)
|
||||
# ref: libretro/lrps2, docs.libretro.com/library/lrps2
|
||||
# Same BIOS as PCSX2 standalone, placed in pcsx2/bios/ subfolder.
|
||||
# Also needs GameIndex.yaml for game compatibility database.
|
||||
#
|
||||
# doc vs source: docs say "no specific filename required" for BIOS, any
|
||||
# valid 4 MB PS2 BIOS dump works. Docs do not list GameIndex.yaml as a
|
||||
# BIOS file, but the source code loads it from pcsx2/resources/ and it
|
||||
# is needed for game-specific patches and compatibility fixes.
|
||||
# SCPH-10000 not recommended per docs (lower compatibility).
|
||||
# LRPS2 - libretro PlayStation 2 core
|
||||
# Source: https://github.com/libretro/ps2
|
||||
# Upstream: https://github.com/PCSX2/pcsx2
|
||||
|
||||
emulator: LRPS2
|
||||
type: libretro
|
||||
core_classification: community_fork
|
||||
source: "https://github.com/libretro/ps2"
|
||||
upstream: "https://github.com/PCSX2/pcsx2"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "Git"
|
||||
display_name: "Sony - PlayStation 2 (LRPS2)"
|
||||
cores: [lrps2]
|
||||
source: "https://github.com/libretro/lrps2"
|
||||
systems:
|
||||
- sony-playstation-2
|
||||
systems: [sony-playstation-2]
|
||||
|
||||
bios_directory: "pcsx2/bios/"
|
||||
resources_directory: "pcsx2/resources/"
|
||||
|
||||
notes: |
|
||||
Libretro port of PCSX2. Uses the same BIOS files as standalone PCSX2.
|
||||
BIOS must NOT be zipped. Any valid PS2 BIOS works (SCPH-10000 not
|
||||
recommended due to lower compatibility). BIOS goes in pcsx2/bios/.
|
||||
GameIndex.yaml goes in pcsx2/resources/ and is needed for proper
|
||||
game-specific settings (patches, fixes).
|
||||
Hard fork of PCSX2 ported to libretro. BIOS detection is filename-agnostic: the core
|
||||
scans pcsx2/bios/ for any file between 4-8 MB with a valid romdir structure containing
|
||||
RESET and ROMVER entries. The ROMVER entry determines region and version.
|
||||
Companion files (.rom1, .rom2, .nvm, .mec) derive their paths from the selected BIOS.
|
||||
DEV9 (network adapter) and USB are stubbed in the libretro port.
|
||||
GameIndex.yaml in pcsx2/resources/ provides per-game patches for compatibility.
|
||||
|
||||
files:
|
||||
- name: GameIndex.yaml
|
||||
system: sony-playstation-2
|
||||
- name: "<bios>.bin"
|
||||
path: "pcsx2/bios/"
|
||||
required: true
|
||||
note: "game compatibility database, not a BIOS — pcsx2/resources/GameIndex.yaml"
|
||||
source_ref: "pcsx2/GameIndex.yaml"
|
||||
description: "PS2 BIOS binary"
|
||||
min_size: 4194304
|
||||
max_size: 8388608
|
||||
validation: [size]
|
||||
source_ref: "pcsx2/ps2/BiosTools.cpp:230-254,266-322"
|
||||
note: >
|
||||
Scans pcsx2/bios/ for any file between 4 MB and 8 MB. Validates via romdir
|
||||
structure parsing (RESET + ROMVER entries). User selects BIOS via core option
|
||||
pcsx2_bios. Falls back to first valid BIOS found if configured BIOS is missing.
|
||||
|
||||
- name: "<bios>.rom1"
|
||||
path: "pcsx2/bios/"
|
||||
required: false
|
||||
max_size: 4194304
|
||||
description: "DVD player ROM"
|
||||
source_ref: "pcsx2/ps2/BiosTools.cpp:189-210,313"
|
||||
note: >
|
||||
DVD player ROM. Tries <biospath>.rom1 (appended) then <biosbase>.rom1
|
||||
(extension replaced). Silently skipped if not found.
|
||||
|
||||
- name: "<bios>.rom2"
|
||||
path: "pcsx2/bios/"
|
||||
required: false
|
||||
max_size: 4194304
|
||||
description: "Chinese ROM extension"
|
||||
source_ref: "pcsx2/ps2/BiosTools.cpp:189-210,314"
|
||||
note: >
|
||||
Chinese region ROM extension. Same naming convention as rom1.
|
||||
Only present on Chinese region consoles.
|
||||
|
||||
- name: "<bios>.nvm"
|
||||
path: "pcsx2/bios/"
|
||||
required: false
|
||||
hle_fallback: true
|
||||
size: 1024
|
||||
description: "NVRAM / EEPROM data"
|
||||
source_ref: "pcsx2/CDVD/CDVD.cpp:155-198"
|
||||
note: >
|
||||
Console EEPROM data (language, timezone, region, iLink ID, OSD settings).
|
||||
Path derived from BIOS path with extension replaced to .nvm.
|
||||
Auto-created with region-appropriate defaults if missing or invalid.
|
||||
|
||||
- name: "<bios>.mec"
|
||||
path: "pcsx2/bios/"
|
||||
required: false
|
||||
hle_fallback: true
|
||||
size: 4
|
||||
description: "Mechacon version"
|
||||
source_ref: "pcsx2/CDVD/CDVD.cpp:186-197"
|
||||
note: >
|
||||
Mechacon (disc drive controller) version as u32.
|
||||
Auto-created with default 0x00020603 if missing.
|
||||
|
||||
- name: "GameIndex.yaml"
|
||||
path: "pcsx2/resources/GameIndex.yaml"
|
||||
required: false
|
||||
description: "game compatibility database"
|
||||
source_ref: "pcsx2/GameDatabase.cpp:48,880"
|
||||
note: >
|
||||
YAML database of per-game patches, settings overrides, and compatibility fixes.
|
||||
OSD warning shown if missing. Some games may not boot or have issues without it.
|
||||
|
||||
- name: "cheats_ws.zip"
|
||||
path: "pcsx2/resources/cheats_ws.zip"
|
||||
required: false
|
||||
description: "widescreen patches archive"
|
||||
source_ref: "pcsx2/VMManager.cpp:340-353"
|
||||
note: >
|
||||
ZIP archive of per-game widescreen (16:9) patches in pnach format.
|
||||
Only loaded when widescreen patches are enabled via core options.
|
||||
Fallback if no patches found in pcsx2/cheats_ws/ folder.
|
||||
|
||||
- name: "cheats_ni.zip"
|
||||
path: "pcsx2/resources/cheats_ni.zip"
|
||||
required: false
|
||||
description: "no-interlacing patches archive"
|
||||
source_ref: "pcsx2/VMManager.cpp:375-388"
|
||||
note: >
|
||||
ZIP archive of per-game no-interlacing patches in pnach format.
|
||||
Only loaded when no-interlacing patches are enabled via core options.
|
||||
Fallback if no patches found in pcsx2/cheats_ni/ folder.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
emulator: Mupen64Plus-Next
|
||||
emulator: "Mupen64Plus-Next"
|
||||
type: libretro
|
||||
core_classification: enhanced_fork
|
||||
source: "https://github.com/libretro/mupen64plus-libretro-nx"
|
||||
upstream: "https://github.com/mupen64plus/mupen64plus-core"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "2.8"
|
||||
core_version: "2.6.0"
|
||||
display_name: "Nintendo - Nintendo 64 (Mupen64Plus-Next)"
|
||||
systems: [nintendo-64, nintendo-64dd]
|
||||
cores: [mupen64plus_next, mupen64plus_next_develop, mupen64plus_next_gles3, mupen64plus_next_gles2]
|
||||
@@ -14,8 +14,16 @@ files:
|
||||
path: "Mupen64plus/IPL.n64"
|
||||
size: 4194304
|
||||
required: false
|
||||
note: "64DD IPL ROM. Only needed for N64 Disk Drive games (.ndd) via subsystem API"
|
||||
source_ref: "mupen64plus-core/src/main/main.c:954-979"
|
||||
description: "64DD IPL ROM"
|
||||
note: "Only needed for N64 Disk Drive games (.ndd) via subsystem API. Accepts Z64, N64, and V64 byte-swap formats."
|
||||
source_ref: "mupen64plus-core/src/main/main.c:940-1024"
|
||||
|
||||
- name: "font.ttf"
|
||||
path: "Mupen64plus/font.ttf"
|
||||
required: false
|
||||
description: "TrueType font for on-screen display text"
|
||||
note: "Fallback font for OSD text rendering (FPS counter, messages). System fonts tried first. No impact on emulation."
|
||||
source_ref: "GLideN64/src/TextDrawer.cpp:165-170"
|
||||
|
||||
notes:
|
||||
hle_available: true
|
||||
@@ -29,3 +37,6 @@ notes:
|
||||
transferpak_note: >
|
||||
Transfer Pak support (GB/GBC games on N64) handled via subsystem API.
|
||||
No additional firmware files needed.
|
||||
embedded_data: >
|
||||
mupen64plus.ini (ROM database) and GLideN64.custom.ini (per-game GPU
|
||||
settings) are embedded as compiled headers and auto-generated at init.
|
||||
@@ -1,8 +1,149 @@
|
||||
emulator: "nekop2"
|
||||
type: alias
|
||||
alias_of: "np2kai"
|
||||
profiled_date: "2026-03-18"
|
||||
emulator: nekop2
|
||||
type: libretro
|
||||
core_classification: community_fork
|
||||
source: "https://github.com/libretro/libretro-meowPC98"
|
||||
upstream: "https://np2.yui.ne.jp (dead; source preserved in libretro repo)"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "0.86"
|
||||
display_name: "NEC - PC-98 (Neko Project II)"
|
||||
note: "This core uses the same BIOS/firmware as np2kai. See emulators/np2kai.yml for details."
|
||||
files: []
|
||||
cores: [nekop2]
|
||||
systems: [pc-98]
|
||||
|
||||
# Neko Project II (NP2) by Yui, ported to libretro by meepingsnesroms.
|
||||
# PC-9801/9821 emulator (80286/IA-32 CPU). All files load from <system>/np2/.
|
||||
# The libretro shim sets np2cfg.biospath to "<system>/np2/" and
|
||||
# file_setcd() to the same path (libretro/libretro.c:856-864,196).
|
||||
#
|
||||
# BIOS_SIMULATE is hardcoded (bios/bios.c:26), so the core always has
|
||||
# a built-in BIOS simulator and ITF ROM is never loaded from disk.
|
||||
# Font data falls back to built-in 8x8 tables. Sound ROM falls back
|
||||
# to a 9-byte stub. IDE/SCSI/SASI all have built-in stubs.
|
||||
#
|
||||
# This is NOT the same core as np2kai. Different repo, different
|
||||
# feature set (no fmgen, no IDE/PCI/GPIB ROM loading, no itf.rom).
|
||||
# BIOS path is np2/, not np2kai/.
|
||||
#
|
||||
# .info declares firmware_count=0, which is misleading — the core
|
||||
# loads multiple BIOS/font/sound files but all have built-in fallbacks.
|
||||
|
||||
files:
|
||||
# -- Main BIOS ROM --
|
||||
# Loaded in bios/bios.c:232-235 via getbiospath(). 96 KB (0x18000)
|
||||
# mapped at 0xe8000. Without this, the built-in BIOS simulator
|
||||
# (nosyscode + BIOS_SIMULATE) is used.
|
||||
- name: "bios.rom"
|
||||
path: "np2/bios.rom"
|
||||
required: false
|
||||
hle_fallback: true
|
||||
note: >
|
||||
PC-9801 system BIOS ROM (96 KB). The core boots without it using
|
||||
the built-in BIOS simulator, but some software requires the real BIOS.
|
||||
source_ref: "bios/bios.c:232-235, common/strres.c:53"
|
||||
|
||||
# -- PC-9821 extension BIOS --
|
||||
# Loaded in bios/bios.c:262-266 via getbiospath(). 8 KB (0x2000)
|
||||
# at 0xd8000. Only compiled when SUPPORT_PC9821 is defined
|
||||
# (libretro/compiler.h:169).
|
||||
- name: "bios9821.rom"
|
||||
path: "np2/bios9821.rom"
|
||||
required: false
|
||||
note: >
|
||||
PC-9821 extension BIOS ROM (8 KB). For PC-9821 mode support.
|
||||
Mapped at 0xd8000.
|
||||
source_ref: "bios/bios.c:262-266, libretro/compiler.h:169"
|
||||
|
||||
# -- Font file --
|
||||
# Set in libretro.c:862 as "<system>/np2/font.bmp".
|
||||
# font_load() in font/font.c:113 determines type by extension:
|
||||
# .bmp = PC98 format, FONT.ROM = V98 format.
|
||||
# Without any font file, built-in fontdata_8 provides 8x8 ASCII only.
|
||||
- name: "font.bmp"
|
||||
path: "np2/font.bmp"
|
||||
required: false
|
||||
hle_fallback: true
|
||||
aliases: ["FONT.ROM"]
|
||||
note: >
|
||||
PC-98 font bitmap (288 KB). Required for correct Japanese kanji display.
|
||||
Without this, only basic ASCII renders using built-in 8x8 data.
|
||||
FONT.ROM (V98 format) is also accepted.
|
||||
source_ref: "libretro/libretro.c:862, font/font.c:113, font/fontdata.c:12"
|
||||
|
||||
# -- Sound BIOS ROM --
|
||||
# Loaded by soundrom_load() via loadsoundrom() in sound/soundrom.c:21-55.
|
||||
# Filename built as "sound" + optional board name + ".rom".
|
||||
# Board-specific variants: sound26.rom (PC-9801-26K), sound86.rom (86),
|
||||
# sound118.rom (118), soundSPB.rom (Speak Board), sound14.rom (14).
|
||||
# The code tries the board-specific name first, falls back to sound.rom.
|
||||
# 16 KB (0x4000). Fallback: 9-byte defsoundrom stub.
|
||||
- name: "sound.rom"
|
||||
path: "np2/sound.rom"
|
||||
required: false
|
||||
hle_fallback: true
|
||||
note: >
|
||||
FM sound board BIOS ROM (16 KB). The core tries board-specific
|
||||
variants first (sound26.rom, sound86.rom, sound118.rom, soundSPB.rom,
|
||||
sound14.rom) then falls back to sound.rom.
|
||||
source_ref: "sound/soundrom.c:15-16,21-55,65-78"
|
||||
|
||||
# -- YM2608 OPNA rhythm samples --
|
||||
# Loaded by rhythm_load() in sound/rhythmc.c:60-71 via getbiospath().
|
||||
# No fmgen engine in this core — only lowercase filenames are used.
|
||||
- name: "2608_bd.wav"
|
||||
path: "np2/2608_bd.wav"
|
||||
required: false
|
||||
note: "YM2608 OPNA rhythm sample: bass drum"
|
||||
source_ref: "sound/rhythmc.c:11,60-71"
|
||||
|
||||
- name: "2608_sd.wav"
|
||||
path: "np2/2608_sd.wav"
|
||||
required: false
|
||||
note: "YM2608 OPNA rhythm sample: snare drum"
|
||||
source_ref: "sound/rhythmc.c:12"
|
||||
|
||||
- name: "2608_top.wav"
|
||||
path: "np2/2608_top.wav"
|
||||
required: false
|
||||
note: "YM2608 OPNA rhythm sample: top cymbal"
|
||||
source_ref: "sound/rhythmc.c:13"
|
||||
|
||||
- name: "2608_hh.wav"
|
||||
path: "np2/2608_hh.wav"
|
||||
required: false
|
||||
note: "YM2608 OPNA rhythm sample: hi-hat"
|
||||
source_ref: "sound/rhythmc.c:14"
|
||||
|
||||
- name: "2608_tom.wav"
|
||||
path: "np2/2608_tom.wav"
|
||||
required: false
|
||||
note: "YM2608 OPNA rhythm sample: tom"
|
||||
source_ref: "sound/rhythmc.c:15"
|
||||
|
||||
- name: "2608_rim.wav"
|
||||
path: "np2/2608_rim.wav"
|
||||
required: false
|
||||
note: "YM2608 OPNA rhythm sample: rim shot"
|
||||
source_ref: "sound/rhythmc.c:16"
|
||||
|
||||
# -- SCSI controller BIOS --
|
||||
# Loaded in cbus/scsiio.c:219 via file_open_rb_c(). 16 KB (0x4000).
|
||||
# Falls back to built-in scsibios[] stub.
|
||||
- name: "scsi.rom"
|
||||
path: "np2/scsi.rom"
|
||||
required: false
|
||||
hle_fallback: true
|
||||
note: >
|
||||
SCSI controller BIOS ROM (16 KB). The core includes a built-in
|
||||
SCSI BIOS stub as fallback.
|
||||
source_ref: "cbus/scsiio.c:208-233"
|
||||
|
||||
# -- SASI controller BIOS --
|
||||
# Loaded in cbus/sasiio.c:453 via file_open_rb_c(). 4 KB (0x1000).
|
||||
# Falls back to built-in sasibios[] stub.
|
||||
- name: "sasi.rom"
|
||||
path: "np2/sasi.rom"
|
||||
required: false
|
||||
hle_fallback: true
|
||||
note: >
|
||||
SASI controller BIOS ROM (4 KB). The core includes a built-in
|
||||
SASI BIOS stub as fallback.
|
||||
source_ref: "cbus/sasiio.c:442-465"
|
||||
|
||||
17
emulators/nes.yml
Normal file
17
emulators/nes.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
emulator: nes
|
||||
type: libretro
|
||||
core_classification: community_fork
|
||||
source: "https://github.com/rz5/nes"
|
||||
upstream: "https://git.9front.org/plan9front/plan9front (sys/src/games/nes/)"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "1.0"
|
||||
display_name: "Nintendo - NES / Famicom (nes)"
|
||||
cores: [nes]
|
||||
systems: [nintendo-nes]
|
||||
files: []
|
||||
notes: >
|
||||
Port of aiju's NES emulator from 9front (Plan 9) to libretro by rz5.
|
||||
Minimal emulator supporting mappers 0 (NROM), 1 (MMC1), 2 (UxROM),
|
||||
3 (CNROM), 4 (MMC3), 7 (AxROM). Palette hardcoded in ppu.c.
|
||||
No BIOS or external files required. NES hardware boots directly
|
||||
from cartridge ROM. No FDS support (only .nes extension).
|
||||
@@ -1,40 +1,36 @@
|
||||
emulator: Nestopia UE
|
||||
type: libretro
|
||||
core_classification: community_fork
|
||||
source: "https://github.com/libretro/nestopia"
|
||||
upstream: "https://gitlab.com/jgemu/nestopia"
|
||||
logo: "https://raw.githubusercontent.com/0ldsk00l/nestopia/master/icons/svg/nestopia.svg"
|
||||
profiled_date: "2026-03-18"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "1.53.1"
|
||||
display_name: "Nintendo - NES / Famicom (Nestopia)"
|
||||
cores: [nestopia]
|
||||
systems: [nintendo-nes, nintendo-fds]
|
||||
|
||||
notes: |
|
||||
Nestopia UE (Undead Edition) is a cycle-accurate NES/Famicom emulator.
|
||||
NES cartridge games need no BIOS. Famicom Disk System games require
|
||||
the FDS BIOS ROM (disksys.rom, 8 KB) loaded from the system directory.
|
||||
The core validates the BIOS via CRC32 against two known dumps:
|
||||
standard Famicom (0x5E607DCF) and Twin Famicom (0x4DF24A6C).
|
||||
An unknown BIOS triggers a warning but still loads.
|
||||
Nestopia UE is a cycle-accurate NES/Famicom emulator. The libretro port
|
||||
tracks the upstream Nestopia JG project. NES cartridge games need no BIOS.
|
||||
Famicom Disk System games require disksys.rom (8 KB FDS BIOS) in the
|
||||
system directory. The core validates the BIOS via CRC32 against two known
|
||||
dumps: standard Famicom (0x5E607DCF) and Twin Famicom (0x4DF24A6C). An
|
||||
unknown BIOS triggers a warning but still loads.
|
||||
|
||||
NstDatabase.xml is an optional game database used for region autodetection,
|
||||
aspect ratio selection, and 4-player adapter recognition. A copy is baked
|
||||
into the core binary (libretro/nstdatabase.hpp) and used as fallback when
|
||||
no external file is found. Placing a newer NstDatabase.xml in the system
|
||||
directory overrides the built-in copy.
|
||||
NstDatabase.xml is an optional game database for region autodetection,
|
||||
mapper selection, and 4-player adapter recognition. A copy is baked into
|
||||
the core binary (libretro/nstdatabase.hpp) as fallback. The upstream
|
||||
Nestopia JG requires this file; the libretro port makes it optional.
|
||||
|
||||
An optional custom palette file (custom.pal, 192 bytes, 64 RGB triplets)
|
||||
overrides the built-in palettes when the nestopia_palette core option is
|
||||
set to Custom. The core ships with 10+ built-in palettes (Royaltea,
|
||||
Smooth FBx, etc.) so the external file is rarely needed.
|
||||
An optional custom palette file (custom.pal, 64 RGB triplets) overrides
|
||||
built-in palettes when the nestopia_palette core option is set to Custom.
|
||||
The libretro port embeds all named palettes (Royaltea, Smooth FBx, etc.)
|
||||
that the upstream loads from external .pal files.
|
||||
|
||||
Audio samples for specific games can be placed in system/nestopia/samples/
|
||||
as numbered .wav files (e.g., 00.wav, 01.wav). This is used for Famicom
|
||||
expansion audio in a few titles.
|
||||
|
||||
Game Genie is handled as cheat code decoding (software), not via a ROM file.
|
||||
|
||||
File path construction: libretro/libretro.cpp retro_load_game() joins the
|
||||
system directory (RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY) with each
|
||||
filename directly - no subdirectories.
|
||||
Five Famicom games use external ADPCM audio samples placed in
|
||||
system/nestopia/samples/{game}/ as numbered .wav files. These originate
|
||||
from MAME sample ZIPs. Games function without them but miss some audio.
|
||||
|
||||
files:
|
||||
# --- Famicom Disk System BIOS (required for FDS games) ---
|
||||
@@ -43,6 +39,7 @@ files:
|
||||
description: "FDS BIOS ROM"
|
||||
required: true
|
||||
size: 8192
|
||||
validation: [crc32]
|
||||
md5: "ca30b50f880eb660a320674ed365ef7a"
|
||||
sha1: "57fe1bdee955bb48d357e463ccbf129496930b62"
|
||||
source_ref: "libretro/libretro.cpp:1608-1634 (FDS load), source/core/NstFds.cpp:61-131 (Bios class, CRC32 validation)"
|
||||
@@ -54,11 +51,8 @@ files:
|
||||
description: "Nestopia game database for region and mapper autodetection"
|
||||
required: false
|
||||
hle_fallback: true
|
||||
size: 1022369
|
||||
md5: "0ee6cbdc6f5c96ce9c8aa5edb59066f4"
|
||||
sha1: ~
|
||||
source_ref: "libretro/libretro.cpp:1561-1586 (database load), libretro/nstdatabase.hpp (baked-in fallback)"
|
||||
notes: "XML database matching games by SHA1+CRC32. Used for region, mapper, mirroring, and 4-player adapter detection. Built-in fallback exists so this file is optional. Hash is for the upstream copy shipped with the core repo."
|
||||
notes: "XML database matching games by SHA1+CRC32. Used for region, mapper, mirroring, and 4-player adapter detection. Built-in fallback exists so this file is optional."
|
||||
|
||||
# --- Custom palette (optional, core option nestopia_palette = Custom) ---
|
||||
- name: "custom.pal"
|
||||
@@ -67,7 +61,41 @@ files:
|
||||
required: false
|
||||
hle_fallback: true
|
||||
size: 192
|
||||
md5: ~
|
||||
sha1: ~
|
||||
source_ref: "libretro/libretro.cpp:1540-1559 (palette load)"
|
||||
notes: "64 RGB triplets (64 x 3 bytes = 192 bytes). Only loaded when nestopia_palette core option is set to Custom. Falls back to built-in Royaltea palette if not found. Multiple valid palettes exist so no single canonical hash."
|
||||
notes: "64 RGB triplets (64 x 3 bytes = 192 bytes). Loaded unconditionally at startup; applied only when nestopia_palette core option is set to Custom. Falls back to built-in Royaltea palette. Multiple valid palettes exist so no canonical hash."
|
||||
|
||||
# --- ADPCM audio samples (optional, game-specific) ---
|
||||
- name: "nestopia/samples/moepro/"
|
||||
system: nintendo-nes
|
||||
description: "Moero Pro Yakyuu audio samples (16 .wav files, 00.wav-15.wav)"
|
||||
required: false
|
||||
category: game_data
|
||||
source_ref: "libretro/libretro.cpp:142-180 (load_wav), libretro/libretro.cpp:247 (LOAD_SAMPLE_MOERO_PRO_YAKYUU callback), source/core/NstSoundPlayer.hpp:53 (16 samples)"
|
||||
|
||||
- name: "nestopia/samples/moepro88/"
|
||||
system: nintendo-nes
|
||||
description: "Moero Pro Yakyuu '88 audio samples (20 .wav files, 00.wav-19.wav)"
|
||||
required: false
|
||||
category: game_data
|
||||
source_ref: "libretro/libretro.cpp:142-180 (load_wav), libretro/libretro.cpp:249 (LOAD_SAMPLE_MOERO_PRO_YAKYUU_88 callback), source/core/NstSoundPlayer.hpp:54 (20 samples)"
|
||||
|
||||
- name: "nestopia/samples/mptennis/"
|
||||
system: nintendo-nes
|
||||
description: "Moero Pro Tennis audio samples (19 .wav files, 00.wav-18.wav)"
|
||||
required: false
|
||||
category: game_data
|
||||
source_ref: "libretro/libretro.cpp:142-180 (load_wav), libretro/libretro.cpp:251 (LOAD_SAMPLE_MOERO_PRO_TENNIS callback), source/core/NstSoundPlayer.hpp:55 (19 samples)"
|
||||
|
||||
- name: "nestopia/samples/terao/"
|
||||
system: nintendo-nes
|
||||
description: "Terao no Dosukoi Oozumou audio samples (6 .wav files, 00.wav-05.wav)"
|
||||
required: false
|
||||
category: game_data
|
||||
source_ref: "libretro/libretro.cpp:142-180 (load_wav), libretro/libretro.cpp:253 (LOAD_SAMPLE_TERAO_NO_DOSUKOI_OOZUMOU callback), source/core/NstSoundPlayer.hpp:56 (6 samples)"
|
||||
|
||||
- name: "nestopia/samples/ftaerobi/"
|
||||
system: nintendo-nes
|
||||
description: "Aerobics Studio audio samples (8 .wav files, 00.wav-07.wav)"
|
||||
required: false
|
||||
category: game_data
|
||||
source_ref: "libretro/libretro.cpp:142-180 (load_wav), libretro/libretro.cpp:255 (LOAD_SAMPLE_AEROBICS_STUDIO callback), source/core/NstSoundPlayer.hpp:57 (8 samples)"
|
||||
|
||||
@@ -1,173 +1,204 @@
|
||||
emulator: NP2kai
|
||||
type: libretro
|
||||
core_classification: enhanced_fork
|
||||
source: "https://github.com/libretro/NP2kai"
|
||||
profiled_date: "2026-03-18"
|
||||
upstream: "https://github.com/AZO234/NP2kai"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "0.86"
|
||||
display_name: "NEC - PC-98 (Neko Project II Kai)"
|
||||
cores: [np2kai]
|
||||
systems: [pc-98]
|
||||
|
||||
# NP2kai is a PC-9801/9821 emulator (Neko Project II kai).
|
||||
# All BIOS/font/sound files are loaded from <system>/np2kai/ subdirectory.
|
||||
# The core sets np2cfg.biospath to "<system>/np2kai/" in retro_load_game()
|
||||
# NP2kai is a PC-9801/9821 emulator by AZO234, enhanced fork of Neko Project II
|
||||
# by Yui (original upstream dead: np2.yui.ne.jp). All files load from
|
||||
# <system>/np2kai/ subdirectory, set in retro_load_game()
|
||||
# (sdl/libretro/libretro.c:1800-1815). All getbiospath() calls resolve
|
||||
# relative to that directory.
|
||||
#
|
||||
# The core has a built-in BIOS simulator (BIOS_SIMULATE) that can boot
|
||||
# without a real BIOS ROM, but a real bios.rom provides better compatibility.
|
||||
# Font data is auto-generated from built-in tables if font.bmp is missing,
|
||||
# but Japanese kanji display requires the real font file.
|
||||
# BIOS_SIMULATE is unconditionally #define'd (bios/bios.c:78), so the core
|
||||
# always has a built-in BIOS simulator. itf.rom loading (bios.c:569) is dead
|
||||
# code behind #else of this define and is never executed.
|
||||
#
|
||||
# .info declares firmware_count=11 but is incomplete: misses IDE, SCSI, SASI,
|
||||
# PCI, GPIB ROMs and key.txt. Lists itf.rom which is dead code (phantom).
|
||||
#
|
||||
# The fmgen YM2608 rhythm engine (fmgen_opna.cpp:1413-1443) loads WAV files
|
||||
# with uppercase extension (.WAV) but the built-in rhythm engine (rhythmc.c)
|
||||
# uses lowercase (.wav). Both paths resolve from np2kai/.
|
||||
# with uppercase names (2608_BD.WAV). The built-in rhythm engine (rhythmc.c)
|
||||
# uses lowercase (2608_bd.wav). Both resolve from np2kai/.
|
||||
# The fmgen engine also accepts "2608_RYM.WAV" as fallback for the rim sample.
|
||||
#
|
||||
# Sound ROM has board-specific variants tried before the generic fallback:
|
||||
# sound26.rom (26K), sound86.rom (86), sound118.rom (118), soundSPB.rom
|
||||
# (Speak Board), soundMO.rom (MO), sound14.rom (14). Built as "sound" +
|
||||
# board name + ".rom" in soundrom.c:21-33.
|
||||
|
||||
files:
|
||||
# -- Main BIOS ROM --
|
||||
# Loaded in bios/bios.c:430-440. 96 KB (0x18000) mapped at 0xe8000.
|
||||
# Without this, the built-in BIOS simulator is used (less compatible).
|
||||
# Loaded in bios/bios.c:430-436 via getbiospath(). 96 KB (0x18000) mapped
|
||||
# at 0xe8000. Only loaded when np2cfg.usebios is true (core option).
|
||||
# Without this, CopyMemory copies nosyscode[] built-in simulator.
|
||||
- name: "bios.rom"
|
||||
path: "np2kai/bios.rom"
|
||||
size: 98304
|
||||
required: false
|
||||
hle_fallback: true
|
||||
note: >
|
||||
PC-9801 system BIOS ROM (96 KB). Provides full hardware compatibility.
|
||||
The core can boot without it using the built-in BIOS simulator, but
|
||||
some software may not work correctly. Loaded at address 0xe8000.
|
||||
source_ref: "bios/bios.c:430-440, common/strres.c:60"
|
||||
|
||||
# -- ITF ROM --
|
||||
# Initial Test Firmware, loaded at ITF_ADRS (0xf8000), 32 KB.
|
||||
# Only loaded when BIOS_SIMULATE is not defined (bios/bios.c:569-574).
|
||||
# In the libretro build, BIOS_SIMULATE is typically enabled, so this is
|
||||
# only needed for non-simulated builds.
|
||||
- name: "itf.rom"
|
||||
path: "np2kai/itf.rom"
|
||||
required: false
|
||||
hle_fallback: true
|
||||
note: >
|
||||
Initial Test Firmware ROM (32 KB). Used for hardware initialization
|
||||
and memory check at boot. Only loaded when the built-in ITF simulator
|
||||
is disabled. Most libretro builds include the simulator.
|
||||
source_ref: "bios/bios.c:569-574"
|
||||
PC-9801 system BIOS ROM (96 KB). The core boots without it using the
|
||||
built-in BIOS simulator, but some software requires the real BIOS.
|
||||
source_ref: "bios/bios.c:430-436, common/strres.c:60"
|
||||
|
||||
# -- Font file --
|
||||
# Set explicitly in libretro.c:1813 as "<system>/np2kai/font.bmp".
|
||||
# The core also supports FONT.ROM / font.rom (V98 format) via font_load().
|
||||
# Without any font file, built-in 8x8 bitmap data is used but kanji
|
||||
# characters will not display correctly.
|
||||
# Path set in libretro.c:1813 as "<system>/np2kai/font.bmp".
|
||||
# font_load() in font/font.c:125 detects type by extension:
|
||||
# .bmp/.BMP = PC98 format (fontpc98.c), FONT.ROM = V98 format (fontv98.c).
|
||||
# Without any font file, fontdata_8 provides 8x8 ASCII only.
|
||||
- name: "font.bmp"
|
||||
path: "np2kai/font.bmp"
|
||||
required: false
|
||||
hle_fallback: true
|
||||
aliases: ["FONT.ROM", "font.rom", "FONT.BMP"]
|
||||
note: >
|
||||
PC-98 font bitmap (288 KB). Required for correct Japanese kanji display.
|
||||
Without this file, only basic ASCII characters render correctly using
|
||||
built-in data.
|
||||
source_ref: "sdl/libretro/libretro.c:1813, font/fontdata.c:11-14"
|
||||
PC-98 font bitmap. Required for correct Japanese kanji display.
|
||||
Without this, only basic ASCII renders using built-in 8x8 data.
|
||||
FONT.ROM (V98 format) is also accepted.
|
||||
source_ref: "sdl/libretro/libretro.c:1813, font/font.c:86-125, font/fontdata.c:11"
|
||||
|
||||
# -- Sound BIOS ROM --
|
||||
# Loaded by soundrom_load() as "sound.rom" (soundrom.c:15-16, 28-32).
|
||||
# The filename is composed as "sound" + optional board name + ".rom".
|
||||
# 16 KB ROM for the FM sound board.
|
||||
# Loaded by soundrom_load() in soundrom.c:93-106 via loadsoundrom().
|
||||
# Filename composed as "sound" + optional board name + ".rom" (soundrom.c:27-32).
|
||||
# Board-specific variants tried first, then sound.rom as fallback.
|
||||
# 16 KB (0x4000). Falls back to 9-byte defsoundrom[] stub.
|
||||
- name: "sound.rom"
|
||||
path: "np2kai/sound.rom"
|
||||
size: 16384
|
||||
required: false
|
||||
hle_fallback: true
|
||||
note: >
|
||||
FM sound board BIOS ROM (16 KB). Used by the PC-9801-26K/86/118
|
||||
sound boards. The core falls back to a minimal built-in default
|
||||
(9-byte stub) if this file is missing.
|
||||
FM sound board BIOS ROM (16 KB). Generic fallback for all sound boards.
|
||||
Board-specific variants are tried first: sound26.rom (PC-9801-26K),
|
||||
sound86.rom (86), sound118.rom (118), soundSPB.rom (Speak Board),
|
||||
soundMO.rom (MO), sound14.rom (14).
|
||||
source_ref: "sound/soundrom.c:15-16,21-55,93-106"
|
||||
|
||||
# -- YM2608 OPNA rhythm samples --
|
||||
# Loaded by both the built-in rhythm engine (rhythmc.c:60-71) and the
|
||||
# fmgen engine (fmgen_opna.cpp:1413-1443). Required for YM2608 OPNA
|
||||
# rhythm sound channel (bass drum, snare, etc).
|
||||
# The fmgen engine tries uppercase .WAV, the built-in engine uses .wav.
|
||||
# Place lowercase versions - the filesystem handles case on most platforms.
|
||||
# Loaded by the built-in rhythm engine (rhythmc.c:60-71) using lowercase
|
||||
# filenames, and by the fmgen engine (fmgen_opna.cpp:1413-1443) using
|
||||
# uppercase (2608_BD.WAV etc). Both resolve via getbiospath().
|
||||
- name: "2608_bd.wav"
|
||||
path: "np2kai/2608_bd.wav"
|
||||
required: false
|
||||
aliases: ["2608_BD.WAV"]
|
||||
note: "YM2608 OPNA rhythm sample: bass drum"
|
||||
source_ref: "sound/rhythmc.c:11, sound/fmgen/fmgen_opna.cpp:1431-1433"
|
||||
source_ref: "sound/rhythmc.c:11,60-71, sound/fmgen/fmgen_opna.cpp:1431"
|
||||
|
||||
- name: "2608_sd.wav"
|
||||
path: "np2kai/2608_sd.wav"
|
||||
required: false
|
||||
aliases: ["2608_SD.WAV"]
|
||||
note: "YM2608 OPNA rhythm sample: snare drum"
|
||||
source_ref: "sound/rhythmc.c:12"
|
||||
source_ref: "sound/rhythmc.c:12, sound/fmgen/fmgen_opna.cpp:1432"
|
||||
|
||||
- name: "2608_top.wav"
|
||||
path: "np2kai/2608_top.wav"
|
||||
required: false
|
||||
aliases: ["2608_TOP.WAV"]
|
||||
note: "YM2608 OPNA rhythm sample: top cymbal"
|
||||
source_ref: "sound/rhythmc.c:13"
|
||||
source_ref: "sound/rhythmc.c:13, sound/fmgen/fmgen_opna.cpp:1432"
|
||||
|
||||
- name: "2608_hh.wav"
|
||||
path: "np2kai/2608_hh.wav"
|
||||
required: false
|
||||
aliases: ["2608_HH.WAV"]
|
||||
note: "YM2608 OPNA rhythm sample: hi-hat"
|
||||
source_ref: "sound/rhythmc.c:14"
|
||||
source_ref: "sound/rhythmc.c:14, sound/fmgen/fmgen_opna.cpp:1432"
|
||||
|
||||
- name: "2608_tom.wav"
|
||||
path: "np2kai/2608_tom.wav"
|
||||
required: false
|
||||
aliases: ["2608_TOM.WAV"]
|
||||
note: "YM2608 OPNA rhythm sample: tom"
|
||||
source_ref: "sound/rhythmc.c:15"
|
||||
source_ref: "sound/rhythmc.c:15, sound/fmgen/fmgen_opna.cpp:1432"
|
||||
|
||||
- name: "2608_rim.wav"
|
||||
path: "np2kai/2608_rim.wav"
|
||||
required: false
|
||||
aliases: ["2608_RYM.WAV"]
|
||||
note: "YM2608 OPNA rhythm sample: rim shot. fmgen also accepts 2608_RYM.WAV"
|
||||
source_ref: "sound/rhythmc.c:16, sound/fmgen/fmgen_opna.cpp:1413-1443"
|
||||
aliases: ["2608_RIM.WAV", "2608_RYM.WAV"]
|
||||
note: "YM2608 OPNA rhythm sample: rim shot. fmgen also accepts 2608_RYM.WAV."
|
||||
source_ref: "sound/rhythmc.c:16, sound/fmgen/fmgen_opna.cpp:1431-1441"
|
||||
|
||||
# -- IDE BIOS ROM --
|
||||
# Loaded by ideio.c:1913-1931. Tried in order: ide.rom, d8000.rom,
|
||||
# bank3.bin, bios9821.rom. Only loaded when IDE BIOS is enabled in
|
||||
# core options (np2cfg.idebios) and a real BIOS ROM is also present.
|
||||
# bank3.bin, bios9821.rom. Only loaded when np2cfg.idebios is enabled
|
||||
# and a real main BIOS ROM is present. Falls back to simulated IDE BIOS.
|
||||
- name: "ide.rom"
|
||||
path: "np2kai/ide.rom"
|
||||
size: 8192
|
||||
required: false
|
||||
hle_fallback: true
|
||||
aliases: ["d8000.rom", "bank3.bin", "bios9821.rom"]
|
||||
note: >
|
||||
IDE controller BIOS ROM (8 KB). Required for real IDE BIOS emulation
|
||||
(HDD boot from IDE). Without this, a simulated IDE BIOS is used.
|
||||
source_ref: "cbus/ideio.c:1913-1931"
|
||||
IDE controller BIOS ROM (8 KB). For IDE HDD boot support.
|
||||
Without this, a simulated IDE BIOS is used.
|
||||
source_ref: "cbus/ideio.c:1913-1941"
|
||||
|
||||
# -- SCSI BIOS ROM --
|
||||
# Loaded by scsiio.c:219-231. Falls back to built-in scsibios[] stub.
|
||||
# Loaded by scsiio.c:219. 16 KB (0x4000). Falls back to scsibios[] stub.
|
||||
- name: "scsi.rom"
|
||||
path: "np2kai/scsi.rom"
|
||||
size: 16384
|
||||
required: false
|
||||
hle_fallback: true
|
||||
note: >
|
||||
SCSI controller BIOS ROM (16 KB). For PC-98 SCSI HDD support.
|
||||
The core includes a built-in SCSI BIOS stub as fallback.
|
||||
source_ref: "cbus/scsiio.c:219-231"
|
||||
SCSI controller BIOS ROM (16 KB). The core includes a built-in
|
||||
SCSI BIOS stub as fallback.
|
||||
source_ref: "cbus/scsiio.c:214-231"
|
||||
|
||||
# -- SASI BIOS ROM --
|
||||
# Loaded by sasiio.c:455. 4 KB (0x1000). Falls back to sasibios[] stub.
|
||||
- name: "sasi.rom"
|
||||
path: "np2kai/sasi.rom"
|
||||
size: 4096
|
||||
required: false
|
||||
hle_fallback: true
|
||||
note: >
|
||||
SASI controller BIOS ROM (4 KB). The core includes a built-in
|
||||
SASI BIOS stub as fallback.
|
||||
source_ref: "cbus/sasiio.c:451-467"
|
||||
|
||||
# -- PCI BIOS ROM --
|
||||
# Loaded by pcidev.c:364-382. Tries pci.rom then bank0.bin.
|
||||
# Falls back to built-in PCI BIOS simulation.
|
||||
# Loaded by pcidev.c:360-382. Tries pci.rom then bank0.bin.
|
||||
# 32 KB (0x8000). Falls back to built-in PCI BIOS simulation.
|
||||
- name: "pci.rom"
|
||||
path: "np2kai/pci.rom"
|
||||
size: 32768
|
||||
required: false
|
||||
hle_fallback: true
|
||||
aliases: ["bank0.bin"]
|
||||
note: >
|
||||
PCI BIOS ROM (32 KB). For PC-9821 PCI bus emulation.
|
||||
Without this, the built-in PCI BIOS simulator is used.
|
||||
source_ref: "io/pcidev.c:360-382"
|
||||
source_ref: "io/pcidev.c:356-382"
|
||||
|
||||
# -- GPIB BIOS ROM --
|
||||
# Loaded by gpibio.c:327-356.
|
||||
# Loaded by gpibio.c:327-356. 8 KB (0x2000). No built-in fallback:
|
||||
# if missing, GPIB is disabled entirely (gpib.enable = 0).
|
||||
- name: "gpib.rom"
|
||||
path: "np2kai/gpib.rom"
|
||||
size: 8192
|
||||
required: false
|
||||
note: >
|
||||
GP-IB interface BIOS ROM. Rarely needed, only for GP-IB peripheral
|
||||
emulation.
|
||||
source_ref: "cbus/gpibio.c:327-356"
|
||||
GP-IB interface BIOS ROM (8 KB). If missing, GP-IB emulation
|
||||
is disabled entirely.
|
||||
source_ref: "cbus/gpibio.c:320-356"
|
||||
|
||||
# -- Keyboard remapping config --
|
||||
# Loaded by keystat_initialize() in keystat.c:43 via getbiospath().
|
||||
# Plain text file mapping keyboard scancodes. If missing, default
|
||||
# built-in table is used.
|
||||
- name: "key.txt"
|
||||
path: "np2kai/key.txt"
|
||||
required: false
|
||||
note: >
|
||||
Keyboard remapping configuration (text file). User-created file
|
||||
for custom keyboard layout. The core uses built-in defaults if absent.
|
||||
source_ref: "keystat.c:43,113-148"
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
emulator: Numero
|
||||
type: libretro
|
||||
core_classification: community_fork
|
||||
source: "https://github.com/nbarkhina/numero"
|
||||
profiled_date: "2026-03-18"
|
||||
upstream: "https://github.com/sputt/wabbitemu"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "v1.0"
|
||||
display_name: "Texas Instruments TI-83 (Numero)"
|
||||
cores:
|
||||
@@ -10,38 +12,35 @@ systems:
|
||||
- ti-83
|
||||
|
||||
notes: |
|
||||
Numero is a TI-83 family calculator emulator for libretro, based on the
|
||||
Wabbitemu emulator. Supports TI-83, TI-83 Plus and TI-83 Silver Edition.
|
||||
Libretro port of Wabbitemu, a TI-83 family calculator emulator.
|
||||
Supports TI-83, TI-83 Plus and TI-83 Plus Silver Edition.
|
||||
|
||||
A calculator ROM dump is required to run. The core searches for ROM files
|
||||
in the system directory root with a fixed priority order
|
||||
(libretronew.cpp:575-594):
|
||||
A calculator ROM dump is required. The core searches sequentially in the
|
||||
system directory (libretronew.cpp:575-594): ti83se.rom first, then
|
||||
ti83plus.rom, then ti83.rom. The first found is loaded via rom_load()
|
||||
(libretronew.cpp:608). file_present_in_system() checks existence only
|
||||
via VFS open/close (libretronew.cpp:451-472). No hash or size validation.
|
||||
|
||||
1. ti83se.rom (TI-83 Silver Edition, recommended -- largest storage)
|
||||
2. ti83plus.rom (TI-83 Plus)
|
||||
3. ti83.rom (TI-83)
|
||||
Without any ROM, the core shows a blank screen listing the expected
|
||||
filenames (libretronew.cpp:1030-1042). No HLE fallback.
|
||||
|
||||
The first file found wins. file_present_in_system() checks existence only
|
||||
(libretronew.cpp:451-472), then rom_load() loads it (libretronew.cpp:608).
|
||||
Supports no-content launch (libretronew.cpp:556). Content files
|
||||
(.8xp/.8xk/.8xg) are loaded on top of the running ROM via link cable
|
||||
simulation. Auto-saves calculator state every 10 seconds
|
||||
(libretronew.cpp:1050-1063).
|
||||
|
||||
There is no fallback or built-in ROM. If none of the three files are found,
|
||||
the core starts without a BIOS and shows a blank screen with a status
|
||||
message listing the expected filenames (libretronew.cpp:1034-1041).
|
||||
|
||||
The core supports no-content launch (RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME
|
||||
is set to true, libretronew.cpp:555-556). Content files are .8xp/.8xk/.8xg
|
||||
calculator programs loaded on top of the running ROM.
|
||||
|
||||
The emulator auto-saves calculator state every 10 seconds to preserve RAM
|
||||
contents (the TI-83 has no persistent storage beyond battery-backed RAM).
|
||||
Upstream Wabbitemu supports a wider model range (TI-73 through TI-84+CSE)
|
||||
but the libretro wrapper only searches for TI-83 family filenames. The
|
||||
underlying engine (calc.cpp:rom_load) auto-detects the model from the ROM
|
||||
header.
|
||||
|
||||
files:
|
||||
- name: "ti83se.rom"
|
||||
system: ti-83
|
||||
description: "TI-83 Silver Edition ROM dump"
|
||||
description: "TI-83 Plus Silver Edition ROM dump"
|
||||
required: false
|
||||
source_ref: "libretronew.cpp:576-578"
|
||||
notes: "Checked first. Recommended by the author for largest storage capacity."
|
||||
notes: "Checked first. Recommended for largest storage capacity."
|
||||
|
||||
- name: "ti83plus.rom"
|
||||
system: ti-83
|
||||
@@ -55,28 +54,4 @@ files:
|
||||
description: "TI-83 ROM dump"
|
||||
required: false
|
||||
source_ref: "libretronew.cpp:591-593"
|
||||
notes: "Checked last. Original TI-83 (non-Plus) ROM."
|
||||
|
||||
platform_details:
|
||||
bios_search:
|
||||
source_ref: "libretronew.cpp:451-472, 575-608"
|
||||
notes: |
|
||||
file_present_in_system() opens a read handle via libretro VFS to
|
||||
test file existence. The search is sequential: ti83se.rom first,
|
||||
then ti83plus.rom, then ti83.rom. Only one ROM is loaded.
|
||||
|
||||
rom_loading:
|
||||
source_ref: "src/Interface/calc.cpp (rom_load)"
|
||||
notes: |
|
||||
rom_load() detects the calculator model from the ROM image header
|
||||
and initializes the appropriate hardware (83, 83+, 83SE). Each
|
||||
model gets its own progress/save directory via setProgressDir().
|
||||
|
||||
content_loading:
|
||||
formats: "8xp, 8xk, 8xg"
|
||||
source_ref: "libretronew.cpp:483, 769"
|
||||
notes: |
|
||||
Calculator programs (.8xp), apps (.8xk) and groups (.8xg) are
|
||||
loaded into the running calculator via the sendfile mechanism,
|
||||
simulating the link cable transfer. Multiple programs can be
|
||||
loaded sequentially onto the same ROM session.
|
||||
notes: "Checked last. Original TI-83 (non-Plus)."
|
||||
|
||||
@@ -1,45 +1,38 @@
|
||||
emulator: NXEngine
|
||||
type: libretro
|
||||
core_classification: game_engine
|
||||
source: "https://github.com/libretro/nxengine-libretro"
|
||||
profiled_date: "2026-03-18"
|
||||
upstream: "https://github.com/Rox64/NXEngine"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "1.0.0.6"
|
||||
display_name: "Cave Story (NXEngine)"
|
||||
cores: [nxengine]
|
||||
systems: [cave-story]
|
||||
|
||||
notes: |
|
||||
NXEngine is an open-source reimplementation of the Cave Story (Doukutsu
|
||||
Monogatari) engine by Studio Pixel. It is not an emulator but a source port
|
||||
that loads the original freeware game data directly.
|
||||
Monogatari) engine by Studio Pixel, authored by Caitlin Shaw. It is not
|
||||
an emulator but a source port that loads the original freeware game data.
|
||||
|
||||
The core requires the original freeware Cave Story distribution placed in
|
||||
{system_dir}/nxengine/. When launched without a content file, it looks for
|
||||
Doukutsu.exe at that path (libretro.cpp:254-259). When launched with a .exe
|
||||
content file, it uses the parent directory of that file instead
|
||||
(libretro.cpp:244).
|
||||
The core requires the freeware Cave Story distribution placed in
|
||||
{system_dir}/nxengine/. When launched without content, it looks for
|
||||
Doukutsu.exe there (libretro.cpp:254-259). When launched with a .exe
|
||||
content file, it uses the parent directory of that file (libretro.cpp:237).
|
||||
|
||||
At startup (main.cpp:66-90), the core opens Doukutsu.exe and extracts from
|
||||
it: ORG music files (extractorg.c), PXT sound effects (extractpxt.c), stage
|
||||
tile attributes (extractstages.c), bitmap graphics, and the wavetable
|
||||
(cachefiles.c:484). These are kept in memory, not written to disk.
|
||||
At startup (main.cpp:75-90), the core opens Doukutsu.exe and extracts:
|
||||
ORG music (extractorg.c), PXT sound effects (extractpxt.c), stage tile
|
||||
attributes (extractstages.c), 18 credit BMPs + pixel.bmp (endpic/),
|
||||
and the wavetable (cachefiles.c:464-485). All kept in memory.
|
||||
|
||||
The data/ directory must exist alongside Doukutsu.exe with the full game
|
||||
asset tree: 30 root-level files (sprites, backgrounds, script tables),
|
||||
36 NPC sprite sheets in data/Npc/, and 333 stage files in data/Stage/
|
||||
(maps .pxm, tile attributes .pxa, entity lists .pxe, scripts .tsc, and
|
||||
tileset images via Prt*.pbm). The core verifies data/ exists by checking
|
||||
for data/npc.tbl (main.cpp:47-63).
|
||||
The data/ directory (399 files from the freeware release) is loaded into
|
||||
an in-memory cache at init (cachefiles.c:38-439, 497-540). All subsequent
|
||||
file access goes through this cache. The core verifies data/ by checking
|
||||
for data/npc.tbl (main.cpp:47-58).
|
||||
|
||||
The core also reads endpic/ bitmaps for ending sequences, extracted from
|
||||
Doukutsu.exe at runtime (cachefiles.c:460-484).
|
||||
The font is compiled-in (bitmap_font.h). The upstream standalone version
|
||||
uses font.ttf via SDL_ttf.
|
||||
|
||||
All required files ship in the datafiles/ directory of the libretro repo
|
||||
itself, since Cave Story is freeware. No separate BIOS or firmware is
|
||||
needed. The entire freeware distribution is the "system" requirement.
|
||||
|
||||
sprites.sif has a compiled-in fallback (sprites_sif.h) and is the only
|
||||
data file that can be missing without a fatal error (cachefiles.c:515-521).
|
||||
|
||||
Valid content extension: .exe (retro_get_system_info, libretro.cpp:96).
|
||||
Valid content extension: .exe (libretro.cpp:109).
|
||||
|
||||
files:
|
||||
- name: "Doukutsu.exe"
|
||||
@@ -51,20 +44,37 @@ files:
|
||||
md5: "38695d3d69d7a0ada8178072dad4c58b"
|
||||
sha1: "bb2d0441e073da9c584f23c2ad8c7ab8aac293bf"
|
||||
source_ref: "main.cpp:77-78 (opened for extraction), libretro.cpp:258 (existence check)"
|
||||
notes: "Placed in system/nxengine/. The core extracts ORG music, PXT sounds, stage tile attributes, BMP graphics, and wavetable.dat from this binary at each launch."
|
||||
|
||||
- name: "data/npc.tbl"
|
||||
category: game_data
|
||||
system: cave-story
|
||||
description: "NPC attribute table (entity behavior flags, HP, damage, display rect offsets)"
|
||||
required: true
|
||||
source_ref: "main.cpp:50 (existence check for data/ directory validation)"
|
||||
notes: "Located in system/nxengine/data/. The core uses this file to verify the data directory is present."
|
||||
notes: "Placed in system/nxengine/. The core extracts ORG music, PXT sounds, stage tile attributes, BMP graphics, and wavetable from this binary at each launch."
|
||||
|
||||
- name: "data/"
|
||||
category: game_data
|
||||
system: cave-story
|
||||
description: "Full game asset directory tree (399 files: sprites, NPC sheets, stage maps, scripts, backgrounds)"
|
||||
required: true
|
||||
source_ref: "cachefiles.c:38-480 (complete file list loaded at init)"
|
||||
source_ref: "cachefiles.c:38-439 (filenames[] array loaded at init)"
|
||||
notes: "Must contain root assets (Arms.pbm, MyChar.pbm, etc.), Npc/ (36 sprite sheets), and Stage/ (333 map/script/tileset files). All files from the original freeware release."
|
||||
|
||||
- name: "data/npc.tbl"
|
||||
category: game_data
|
||||
system: cave-story
|
||||
description: "NPC attribute table (entity behavior flags, HP, damage, display rect offsets)"
|
||||
required: true
|
||||
source_ref: "main.cpp:50 (existence check for data/ directory), ai/ai.cpp:56-59 (loaded for NPC properties)"
|
||||
notes: "Located in system/nxengine/data/. Used to validate data directory presence and to load NPC behavior attributes."
|
||||
|
||||
- name: "data/sprites.sif"
|
||||
category: game_data
|
||||
system: cave-story
|
||||
description: "Sprite information file (sprite positions, sizes, animation data)"
|
||||
required: false
|
||||
hle_fallback: true
|
||||
source_ref: "cachefiles.c:101 (in filenames[]), cachefiles.c:515-521 (compiled-in fallback from sprites_sif.h)"
|
||||
notes: "Not shipped in the freeware distribution. If missing, the core uses a compiled-in copy (sprites_sif.h)."
|
||||
|
||||
- name: "tilekey.dat"
|
||||
category: game_data
|
||||
system: cave-story
|
||||
description: "Tile attribute lookup table (maps tile codes to collision/behavior attributes)"
|
||||
required: false
|
||||
source_ref: "map.cpp:290-303 (loaded at init, hardcoded default if missing)"
|
||||
notes: "Not part of the freeware distribution. Generated by the standalone NXEngine extraction tool. The libretro core has hardcoded defaults in map.cpp:30."
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
emulator: O2EM
|
||||
type: libretro
|
||||
core_classification: community_fork
|
||||
core: o2em_libretro
|
||||
cores: [o2em_libretro]
|
||||
source: "https://github.com/libretro/libretro-o2em"
|
||||
profiled_date: "2026-03-18"
|
||||
upstream: "https://sourceforge.net/projects/o2em/"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "1.18"
|
||||
display_name: "Magnavox - Odyssey2 / Philips Videopac+ (O2EM)"
|
||||
systems:
|
||||
@@ -10,28 +13,24 @@ systems:
|
||||
- videopac
|
||||
|
||||
notes: |
|
||||
O2EM is the libretro port of the Odyssey2/Videopac emulator.
|
||||
The core requires exactly one BIOS ROM selected via the o2em_bios core option.
|
||||
Default is o2rom.bin (Odyssey2 NTSC). The user can switch to c52.bin, g7400.bin
|
||||
or jopac.bin for European/French Videopac variants.
|
||||
Libretro port of O2EM, an Odyssey2/Videopac emulator by Daniel Boris
|
||||
and André de la Rocha. Port by Arlindo M. de Oliveira.
|
||||
|
||||
BIOS files are loaded from the system directory into rom_table[0] (1024 bytes).
|
||||
The core identifies the BIOS variant by CRC32 and sets the vpp flag accordingly
|
||||
(vpp=1 enables Videopac+ enhanced graphics for g7400.bin and jopac.bin).
|
||||
One BIOS ROM selected via o2em_bios core option (default o2rom.bin).
|
||||
Loaded into rom_table[0] (1024 bytes), identified by CRC32.
|
||||
vpp=1 for Videopac+ variants (g7400.bin, jopac.bin).
|
||||
Core fails to load without the selected BIOS.
|
||||
|
||||
The core will not start without a valid BIOS file present.
|
||||
Any single BIOS from the list below is sufficient for its region/hardware.
|
||||
Voice module ("The Voice" speech synthesis add-on) emulation via WAV
|
||||
samples in system/voice/. 9 banks × 128 samples, named {bank}{sample}.WAV
|
||||
(e.g. E480.WAV). Compiled with HAVE_VOICE=1 on most platforms. Optional:
|
||||
core works without samples, speech is silently skipped.
|
||||
|
||||
All BIOS files are exactly 1 KB (1024 bytes).
|
||||
|
||||
BIOS detection: libretro.c load_bios() lines 146-212, CRC32 switch.
|
||||
BIOS selection: libretro_core_options.h o2em_bios option lines 52-64.
|
||||
Core option handling: libretro.c lines 1107-1134.
|
||||
BIOS loading: libretro.c load_bios() lines 145-212.
|
||||
BIOS selection: libretro_core_options.h lines 52-64.
|
||||
Voice loading: voice.c init_voice() lines 38-79, called from libretro.c:1025-1032.
|
||||
|
||||
files:
|
||||
# -------------------------------------------------------
|
||||
# Magnavox Odyssey2 (NTSC) - G7000 model
|
||||
# -------------------------------------------------------
|
||||
- name: "o2rom.bin"
|
||||
system: odyssey2
|
||||
region: [north-america]
|
||||
@@ -41,12 +40,9 @@ files:
|
||||
sha1: "b2e1955d957a475de2411770452eff4ea19f4cee"
|
||||
crc32: "8016a315"
|
||||
validation: [size, crc32]
|
||||
note: "Magnavox Odyssey2 BIOS (G7000 NTSC). Default BIOS, vpp=0."
|
||||
note: "Odyssey2 BIOS (G7000 NTSC). Default, vpp=0."
|
||||
source_ref: "libretro.c:182-186"
|
||||
|
||||
# -------------------------------------------------------
|
||||
# Philips Videopac G7000 (European)
|
||||
# -------------------------------------------------------
|
||||
- name: "c52.bin"
|
||||
system: videopac
|
||||
region: [europe]
|
||||
@@ -56,12 +52,9 @@ files:
|
||||
sha1: "a6120aed50831c9c0d95dbdf707820f601d9452e"
|
||||
crc32: "a318e8d6"
|
||||
validation: [size, crc32]
|
||||
note: "Philips Videopac G7000 European BIOS. vpp=0, auto-sets PAL region."
|
||||
note: "Videopac G7000 European BIOS. vpp=0, auto-sets PAL."
|
||||
source_ref: "libretro.c:192-197"
|
||||
|
||||
# -------------------------------------------------------
|
||||
# Philips Videopac+ G7400 (European)
|
||||
# -------------------------------------------------------
|
||||
- name: "g7400.bin"
|
||||
system: videopac
|
||||
region: [europe]
|
||||
@@ -71,12 +64,9 @@ files:
|
||||
sha1: "5130243429b40b01a14e1304d0394b8459a6fbae"
|
||||
crc32: "e20a9f41"
|
||||
validation: [size, crc32]
|
||||
note: "Philips Videopac+ G7400 European BIOS. vpp=1, enables enhanced graphics."
|
||||
note: "Videopac+ G7400 European BIOS. vpp=1."
|
||||
source_ref: "libretro.c:187-191"
|
||||
|
||||
# -------------------------------------------------------
|
||||
# Philips Videopac+ G7400 (French) - JoPac
|
||||
# -------------------------------------------------------
|
||||
- name: "jopac.bin"
|
||||
system: videopac
|
||||
region: [france]
|
||||
@@ -86,20 +76,11 @@ files:
|
||||
sha1: "54b8d2c1317628de51a85fc1c424423a986775e4"
|
||||
crc32: "11647ca5"
|
||||
validation: [size, crc32]
|
||||
note: "Philips Videopac+ G7400 French BIOS (JoPac). vpp=1, enables enhanced graphics."
|
||||
note: "Videopac+ G7400 French BIOS (JoPac). vpp=1."
|
||||
source_ref: "libretro.c:198-203"
|
||||
|
||||
platform_details:
|
||||
odyssey2:
|
||||
bios_size: 1024
|
||||
bios_selection: "core option o2em_bios, default o2rom.bin"
|
||||
detection_method: "CRC32 of 1024-byte ROM"
|
||||
hle_available: false
|
||||
source_ref: "libretro.c:146-212, libretro_core_options.h:52-64"
|
||||
videopac:
|
||||
bios_size: 1024
|
||||
bios_selection: "core option o2em_bios, choose c52/g7400/jopac"
|
||||
detection_method: "CRC32 of 1024-byte ROM"
|
||||
vpp_flag: "g7400.bin and jopac.bin set vpp=1 for enhanced graphics"
|
||||
hle_available: false
|
||||
source_ref: "libretro.c:146-212, libretro_core_options.h:52-64"
|
||||
- name: "voice/"
|
||||
required: false
|
||||
category: game_data
|
||||
note: "The Voice speech synthesis WAV samples. 9 banks (E4, E8-EF) × 128 samples."
|
||||
source_ref: "voice.c:38-79, libretro.c:1025-1032"
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
emulator: Oberon
|
||||
type: libretro
|
||||
core_classification: community_fork
|
||||
source: "https://github.com/libretro/oberon-risc-emu"
|
||||
profiled_date: "2026-03-18"
|
||||
upstream: "https://github.com/pdewacht/oberon-risc-emu"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "2020-07-01"
|
||||
display_name: "Project Oberon RISC"
|
||||
display_name: "Oberon RISC Emulator"
|
||||
cores:
|
||||
- oberon
|
||||
@@ -11,33 +12,15 @@ systems: [oberon]
|
||||
|
||||
# Project Oberon RISC emulator by Peter De Wachter.
|
||||
# Emulates the Oberon RISC processor designed by Niklaus Wirth.
|
||||
# The bootloader (512 words) is compiled into the binary from risc-boot.inc,
|
||||
# loaded into ROM at 0xFFFFF800 on startup (risc.c:75-77, risc_new).
|
||||
#
|
||||
# Content: .dsk disk images containing the full Oberon operating system.
|
||||
# The disk image is loaded via retro_load_game(game->path) and attached
|
||||
# as SPI disk (Libretro/libretro.c:209-214). No files are read from
|
||||
# the RetroArch system directory.
|
||||
#
|
||||
# Reference disk images ship in the repo under DiskImage/:
|
||||
# Oberon-2020-08-18.dsk (990208 bytes, latest)
|
||||
# Oberon-2019-01-21.dsk (988160 bytes)
|
||||
# Oberon-2016-08-02.dsk (989184 bytes)
|
||||
# Oberon-2016-04-18.dsk (989184 bytes)
|
||||
# These are game content, not system files.
|
||||
# Bootloader (512 words) compiled into the binary from risc-boot.inc,
|
||||
# loaded into ROM at 0xFFFFF800 on startup (risc.c:75-77,93).
|
||||
# No retro_get_system_directory() call — no files from system dir.
|
||||
# Content: .dsk disk images loaded via retro_load_game (libretro.c:209-214).
|
||||
# Upstream and libretro core emulation code (risc.c, disk.c) are identical.
|
||||
|
||||
files: []
|
||||
|
||||
notes:
|
||||
architecture: >
|
||||
Custom 32-bit RISC CPU (25 MHz emulated) with 1 MB RAM (expandable to 32 MB).
|
||||
Monochrome 1-bit framebuffer. Keyboard input via PS/2 scancodes.
|
||||
SPI bus for SD card (disk image) access. Serial port for PCLink file transfer.
|
||||
boot_process: >
|
||||
CPU starts execution at ROM address 0xFFFFF800. The embedded bootloader
|
||||
reads the boot sector from the SPI disk and loads the Oberon inner core
|
||||
(modules Kernel, FileDir, Files, Modules) into RAM, then jumps to it.
|
||||
content_format: >
|
||||
Disk images (.dsk) are raw sector images read via 512-byte SPI commands.
|
||||
The core detects filesystem-only images (magic 0x9B1EA38D at sector 0)
|
||||
and adjusts the sector offset accordingly (disk.c:57-58).
|
||||
Bootloader embedded in ROM reads boot sector from SPI disk, loads
|
||||
Oberon inner core into RAM. Content is raw sector .dsk images.
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
emulator: ONScripter
|
||||
type: libretro
|
||||
core_classification: community_fork
|
||||
source: "https://github.com/iyzsong/onscripter-libretro"
|
||||
profiled_date: "2026-03-18"
|
||||
upstream: "https://github.com/ogapee/onscripter"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "0.3"
|
||||
display_name: "ONScripter"
|
||||
cores:
|
||||
@@ -10,30 +12,18 @@ systems:
|
||||
- onscripter
|
||||
|
||||
notes: |
|
||||
ONScripter is a clone of NScripter, a Japanese visual novel engine by
|
||||
Naoki Takahashi. The libretro port by iyzsong wraps the upstream engine
|
||||
(ogapee/onscripter, tag 20230825) with an SDL-to-libretro shim layer.
|
||||
Clone of NScripter, a Japanese visual novel engine. The libretro port
|
||||
wraps upstream ogapee/onscripter (tag 20230825) via an SDL-to-libretro
|
||||
shim.
|
||||
|
||||
No BIOS or system files are required. The .info file declares no
|
||||
firmware entries and the core never calls
|
||||
RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY.
|
||||
No system files required. The .info declares no firmware. The core
|
||||
never calls RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY. All files
|
||||
(scripts, fonts, archives, config) are loaded from the game directory
|
||||
via archive_path (libretro.cpp:120-121, ScriptHandler.cpp:148-161).
|
||||
|
||||
Game content is loaded directly from the archive path containing the
|
||||
script file. The core opens txt, dat, ___, or ons script files via
|
||||
ONScripter::openScript() and reads all game assets (graphics, audio,
|
||||
fonts) from the same directory.
|
||||
default.ttf, registry.txt, dll.txt are per-game assets shipped with
|
||||
each visual novel, not system-level files.
|
||||
|
||||
The engine looks for a font file named default.ttf in the game
|
||||
directory. This is a per-game asset shipped with each visual novel,
|
||||
not a system-level file managed by RetroArch.
|
||||
|
||||
Dependencies (bundled at build time via Guix): freetype, libjpeg,
|
||||
libogg, libvorbis, libmad, bzip2, SDL (shimmed to libretro).
|
||||
|
||||
The core is marked is_experimental = true. It does not support
|
||||
save states (retro_serialize_size returns 0).
|
||||
|
||||
Supported script formats: .txt, .dat, .___, .ons
|
||||
needs_fullpath = true, block_extract = false.
|
||||
Experimental core. No save state support (retro_serialize_size = 0).
|
||||
|
||||
files: []
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
emulator: ONScripter Yuri
|
||||
type: game
|
||||
source: "https://github.com/libretro/libretro-onsyuri"
|
||||
profiled_date: "2026-03-18"
|
||||
core_classification: game_engine
|
||||
source: "https://github.com/YuriSizuku/OnscripterYuri"
|
||||
upstream: "https://github.com/YuriSizuku/OnscripterYuri"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "0.7.4+2"
|
||||
display_name: "ONScripter Yuri"
|
||||
cores:
|
||||
@@ -10,13 +12,10 @@ systems:
|
||||
- onscripter
|
||||
|
||||
notes: |
|
||||
ONScripter Yuri is a fork of ONScripter optimized for the libretro
|
||||
environment. Like the original ONScripter, it is a clone of the
|
||||
NScripter visual novel engine and runs .txt, .dat, and .ons script
|
||||
files.
|
||||
|
||||
No BIOS or system files required. Game content (scripts, graphics,
|
||||
audio, fonts) is loaded from the game directory. Each game ships its
|
||||
own default.ttf font file.
|
||||
Clone of the NScripter visual novel engine, forked from ONScripter-Jh.
|
||||
The libretro port lives in the upstream repo (src/onsyuri_libretro/).
|
||||
No system files required. Game scripts (0.txt, nscript.dat, etc.),
|
||||
fonts (default.ttf), and assets are loaded from the game directory.
|
||||
The core does not use RetroArch's system directory.
|
||||
|
||||
files: []
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
emulator: OpenLara
|
||||
type: libretro
|
||||
core_classification: game_engine
|
||||
source: "https://github.com/libretro/OpenLara"
|
||||
profiled_date: "2026-03-18"
|
||||
upstream: "https://github.com/XProger/OpenLara"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "v1"
|
||||
display_name: "Tomb Raider (OpenLara)"
|
||||
cores: [openlara]
|
||||
@@ -9,23 +11,14 @@ systems: [tomb-raider]
|
||||
verification: existence
|
||||
notes: >
|
||||
Open-source reimplementation of the classic Tomb Raider engine (TR1-TR4).
|
||||
Supports PC (.PHD, .TR2, .TR4), PlayStation (.PSX), and Saturn (.SAT)
|
||||
level formats. The core accepts phd|psx|tr2|sat extensions
|
||||
(retro_get_system_info sets valid_extensions, main.cpp:246).
|
||||
need_fullpath is true - the core resolves sibling game assets relative
|
||||
to the content directory. retro_load_game (main.cpp:602) derives
|
||||
contentDir from the loaded level file path and strips the level/
|
||||
subdirectory if present, then passes a relative levelpath to the engine.
|
||||
Game version is auto-detected by probing for known level files:
|
||||
DATA/GYM.PHD (TR1 PC), PSXDATA/GYM.PSX (TR1 PSX), DATA/GYM.SAT
|
||||
(TR1 Saturn), data/ASSAULT.TR2 (TR2 PC), DATA/ASSAULT.PSX (TR2 PSX),
|
||||
data/JUNGLE.TR2 (TR3 PC), DATA/JUNGLE.PSX (TR3 PSX), data/angkor1.tr4
|
||||
(TR4 PC) - see gameflow.h:1047-1071. Audio tracks are loaded from
|
||||
audio/ subdirectories as .ogg files or cdaudio.wad containers
|
||||
(gameflow.h:1248-1350). The system directory is used only to create
|
||||
a cache folder at {system_dir}/openlara/cache/ (main.cpp:177-197).
|
||||
No BIOS, firmware, or engine data files are required in the system
|
||||
directory. All required data comes from the original game files loaded
|
||||
as content.
|
||||
Accepts phd|psx|tr2|sat extensions (main.cpp:246). need_fullpath is true -
|
||||
the core resolves game assets relative to the content directory.
|
||||
retro_load_game (main.cpp:602) derives contentDir from the level file path
|
||||
and strips level/ if present. Game version auto-detected by probing for
|
||||
known level files (gameflow.h:1047-1071). Audio tracks loaded from audio/
|
||||
subdirectories as .ogg/.mp3/.wav files or cdaudio.wad (gameflow.h:1272-1356).
|
||||
System directory used only for shader cache at {system_dir}/openlara/cache/
|
||||
(main.cpp:177-197). No BIOS, firmware, or engine data files required in
|
||||
system directory. .info omits sat from supported_extensions (code declares it).
|
||||
|
||||
files: []
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
emulator: OpenTyrian
|
||||
type: libretro
|
||||
core_classification: game_engine
|
||||
source: "https://github.com/trapexit/libretro-opentyrian"
|
||||
profiled_date: "2026-03-18"
|
||||
upstream: "https://github.com/opentyrian/opentyrian"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "1.0.0.6"
|
||||
display_name: "Tyrian (OpenTyrian)"
|
||||
cores: [opentyrian]
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
emulator: Opera (4DO)
|
||||
type: libretro
|
||||
core_classification: community_fork
|
||||
core: opera_libretro
|
||||
cores: [opera]
|
||||
source: "https://github.com/libretro/opera-libretro"
|
||||
profiled_date: "2026-03-18"
|
||||
upstream: "https://sourceforge.net/projects/freedo/"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "1.0.0"
|
||||
display_name: "The 3DO Company - 3DO (Opera)"
|
||||
systems:
|
||||
@@ -27,7 +30,8 @@ notes: |
|
||||
|
||||
BIOS definitions: libopera/opera_bios.c BIOSES[] lines 3-136.
|
||||
BIOS loading: opera_lr_opts.c opera_lr_opts_set_bios() lines 239-270.
|
||||
Font loading: opera_lr_opts.c opera_lr_opts_set_font() lines 272-320.
|
||||
Font loading: opera_lr_opts.c opera_lr_opts_get_font() lines 274-293,
|
||||
opera_lr_opts_set_font() lines 297-328.
|
||||
Core option: libretro_core_options.c opera_bios / opera_font.
|
||||
|
||||
files:
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
emulator: Panda3DS
|
||||
type: libretro
|
||||
type: standalone + libretro
|
||||
core_classification: official_port
|
||||
core_name: panda3ds_libretro
|
||||
source: "https://github.com/panda3ds-emu/panda3ds"
|
||||
profiled_date: "2026-03-18"
|
||||
upstream: "https://github.com/panda3ds-emu/panda3ds"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "Git"
|
||||
display_name: "Nintendo - 3DS (Panda3DS)"
|
||||
cores: [panda3ds]
|
||||
systems: [nintendo-3ds]
|
||||
|
||||
notes: |
|
||||
Panda3DS is an HLE 3DS emulator. Most games run without any system files.
|
||||
HLE 3DS emulator. Most games run without system files.
|
||||
Encrypted ROMs need AES keys in sysdata/aes_keys.txt, and seed-encrypted
|
||||
titles (9.6+) also need sysdata/seeddb.bin.
|
||||
DSP firmware is loaded from the game itself (not from disk), with HLE/LLE/Null
|
||||
modes selectable via core option panda3ds_dsp_emulation.
|
||||
System archives (shared font, bad word list, country list, mii data) are
|
||||
compiled into the binary from citra_system_archives headers.
|
||||
The libretro core does NOT use RetroArch's system directory. It stores data
|
||||
under the save directory in "Emulator Files/sysdata/".
|
||||
The .info file declares no firmware entries, so RetroArch will not check
|
||||
for any system files.
|
||||
Experimental core: is_experimental = true in the .info file.
|
||||
DSP firmware is loaded from the game itself (HLE/LLE/Null modes via core
|
||||
option panda3ds_dsp_emulation). System archives (shared font, bad word
|
||||
list, country list, mii data) are compiled into the binary via cmrc.
|
||||
The libretro core uses the save directory, not RetroArch's system
|
||||
directory. Files go under <save_dir>/Emulator Files/sysdata/.
|
||||
The .info declares firmware_count=0. is_experimental = true.
|
||||
|
||||
files:
|
||||
- name: "aes_keys.txt"
|
||||
path: "Emulator Files/sysdata/aes_keys.txt"
|
||||
description: "AES encryption keys for decrypting encrypted ROMs"
|
||||
required: false
|
||||
source_ref: "src/emulator.cpp:229,238"
|
||||
source_ref: "src/emulator.cpp:229,237-238, src/core/crypto/aes_engine.cpp:13-92"
|
||||
notes: |
|
||||
Loaded at ROM load time from appDataRoot/sysdata/aes_keys.txt.
|
||||
In libretro mode, appDataRoot = <save_dir>/Emulator Files/.
|
||||
Contains key slot entries like "generator=XXXX", keyX/keyY values.
|
||||
Contains key slot entries (generator, keyX, keyY, normalKey).
|
||||
Only needed for encrypted .3ds/.cci/.cxi/.app files.
|
||||
Decrypted dumps work without this file.
|
||||
|
||||
@@ -38,8 +38,8 @@ files:
|
||||
path: "Emulator Files/sysdata/seeddb.bin"
|
||||
description: "Seed database for seed-encrypted games"
|
||||
required: false
|
||||
source_ref: "src/emulator.cpp:230,241-242, src/core/loader/ncch.cpp:77-93"
|
||||
source_ref: "src/emulator.cpp:230,241-242, src/core/loader/ncch.cpp:78-93"
|
||||
notes: |
|
||||
Required for titles using seed encryption (firmware 9.6+).
|
||||
Must be placed alongside aes_keys.txt in the sysdata directory.
|
||||
Without it, seed-encrypted titles will fail to load with a warning.
|
||||
Used for titles with seed encryption (firmware 9.6+).
|
||||
Placed alongside aes_keys.txt in the sysdata directory.
|
||||
Without it, seed-encrypted titles fail to load with a warning.
|
||||
|
||||
@@ -1,16 +1,33 @@
|
||||
emulator: "ParaLLEl N64"
|
||||
type: libretro
|
||||
core_classification: community_fork
|
||||
source: "https://github.com/libretro/parallel-n64"
|
||||
upstream: "https://github.com/mupen64plus/mupen64plus-core"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "2.0-rc2"
|
||||
display_name: "Nintendo - Nintendo 64 (ParaLLEl N64)"
|
||||
systems: [nintendo-64, nintendo-64dd]
|
||||
cores: [parallel_n64, parallel_n64_debug]
|
||||
|
||||
# Needs full source-verified profiling. Minimal profile based on known file paths.
|
||||
|
||||
files:
|
||||
- name: "64DD_IPL.bin"
|
||||
path: "64DD_IPL.bin"
|
||||
required: false
|
||||
note: "64DD IPL ROM. Only needed for N64 Disk Drive games"
|
||||
description: "64DD IPL ROM"
|
||||
note: "Only loaded when a 64DD disk image (.ndd) is present via subsystem API. Core fails to load disk games without it."
|
||||
source_ref: "libretro/libretro.c:576-623"
|
||||
|
||||
notes:
|
||||
hle_available: true
|
||||
hle_note: >
|
||||
PIF boot ROM is fully HLE'd (pifbootrom/pifbootrom.c:57). CIC
|
||||
challenge/response handled in software (n64_cic_nus_6105.c). No BIOS
|
||||
files needed for standard cartridge games.
|
||||
dd_note: >
|
||||
64DD IPL ROM loaded from system_dir/64DD_IPL.bin when disk_data is
|
||||
present. Loaded via fopen with no hash or size validation.
|
||||
divergence_note: >
|
||||
Fork of mupen64plus-core with significant divergence. Includes legacy
|
||||
GPU plugins (Glide64, glN64, Rice, angrylion) and ParaLLEl-RDP/RSP.
|
||||
Uses flat path 64DD_IPL.bin unlike mupen64plus-next which uses
|
||||
Mupen64plus/IPL.n64 subdirectory.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
emulator: "parallel_n64_debug"
|
||||
type: alias
|
||||
alias_of: "mupen64plus"
|
||||
profiled_date: "2026-03-18"
|
||||
alias_of: "parallel_n64"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "2.0-rc2"
|
||||
display_name: "Nintendo - Nintendo 64 (ParaLLEl) (Debug)"
|
||||
note: "This core uses the same BIOS/firmware as mupen64plus. See emulators/mupen64plus.yml for details."
|
||||
note: "Debug build of parallel_n64. Same codebase and firmware requirements."
|
||||
files: []
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
emulator: Pascal Pong
|
||||
type: game
|
||||
source: "https://github.com/libretro/libretro-pong"
|
||||
profiled_date: "2026-03-18"
|
||||
core_classification: pure_libretro
|
||||
source: "https://github.com/libretro/pascal-pong-libretro"
|
||||
upstream: "https://github.com/libretro/pascal-pong-libretro"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "v1.0"
|
||||
display_name: "PascalPong"
|
||||
cores: [pascal_pong]
|
||||
systems: []
|
||||
files: []
|
||||
notes: >
|
||||
Pong clone written in Pascal as a libretro core demonstration.
|
||||
Self-contained with all rendering done programmatically.
|
||||
No content file, BIOS, or system directory files required.
|
||||
Pong clone written in Pascal for libretro. Self-contained,
|
||||
all assets embedded in the binary. No external files loaded.
|
||||
|
||||
@@ -1,17 +1,45 @@
|
||||
emulator: PCem
|
||||
type: libretro
|
||||
core_classification: community_fork
|
||||
source: "https://github.com/libretro/libretro-pcem"
|
||||
profiled_date: "2026-03-18"
|
||||
core_version: "SVN"
|
||||
upstream: "https://github.com/sarah-walker-pcem/pcem"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "v11 (PCem-mooch, based on PCem v10.1)"
|
||||
display_name: "PC (PCem)"
|
||||
systems: [ibm-pc, ibm-xt, ibm-at, ibm-pcjr, ibm-ps1, tandy-1000]
|
||||
cores: [pcem]
|
||||
systems:
|
||||
- ibm-pc
|
||||
- ibm-xt
|
||||
- ibm-at
|
||||
- ibm-pcjr
|
||||
- ibm-ps1
|
||||
- tandy-1000
|
||||
|
||||
# PCem (PC Emulator) emulates IBM PC compatibles from 8088 through Pentium.
|
||||
# All ROMs are loaded relative to the core's system directory via romfopen()
|
||||
# which prepends pcempath (= RetroArch system dir) to the path.
|
||||
# Each machine model requires its own BIOS ROM set under roms/<machine>/.
|
||||
# Video card ROMs are separate and only needed for the selected GPU.
|
||||
# The font ROM (mda.rom) is always loaded at startup.
|
||||
notes:
|
||||
rom_structure: >
|
||||
All ROM files are loaded relative to the RetroArch system directory via
|
||||
romfopen(). Machine BIOS ROMs go in roms/<machine_name>/ subdirectories.
|
||||
Video card ROMs go directly in roms/ (or roms/<card>/ for some).
|
||||
Which ROMs are needed depends on the selected machine model and video card.
|
||||
minimum_requirement: >
|
||||
At minimum, mda.rom (font) and one machine BIOS set are required.
|
||||
The video card ROM for the selected GPU is also needed unless the machine
|
||||
has built-in video (PCjr, Tandy, Amstrad, Acer 386SX, PS/1).
|
||||
libretro_version: >
|
||||
The libretro port is PCem-mooch v11, a community fork based on PCem v10.1.
|
||||
Upstream PCem has evolved to v17 with many more machines and devices.
|
||||
nvr_files: >
|
||||
NVR files are CMOS/NVRAM templates providing pre-configured BIOS settings.
|
||||
Shipped with the PCem distribution. Without them the BIOS starts with blank
|
||||
CMOS (user must manually configure). The emulator also writes these back on
|
||||
shutdown.
|
||||
runtime_state: >
|
||||
The emulator creates additional state files during operation: ATI EEPROM
|
||||
(ati18800.nvr, ati28800.nvr, mach64.nvr), Intel flash (dmi.bin, escd.bin
|
||||
per Pentium machine). These are emulator-generated and not included here.
|
||||
info_divergence: >
|
||||
The .info declares zero firmware (no firmware_count field). The core
|
||||
actually loads 89+ ROM files plus NVR/EEPROM templates.
|
||||
|
||||
files:
|
||||
# ========================================================
|
||||
@@ -150,14 +178,18 @@ files:
|
||||
- name: "Tandy 1000 SL/2 BIOS low"
|
||||
path: "roms/tandy1000sl2/8079047.hu1"
|
||||
required: false
|
||||
note: "Tandy 1000 SL/2 BIOS low chip. Paired with 8079048.hu2."
|
||||
source_ref: "src/mem.c:180"
|
||||
note: >
|
||||
Tandy 1000 SL/2 BIOS low chip. Paired with 8079048.hu2.
|
||||
Also loaded by tandy_rom.c for full bank-switched ROM (512 KB).
|
||||
source_ref: "src/mem.c:180, src/tandy_rom.c:58"
|
||||
|
||||
- name: "Tandy 1000 SL/2 BIOS high"
|
||||
path: "roms/tandy1000sl2/8079048.hu2"
|
||||
required: false
|
||||
note: "Tandy 1000 SL/2 BIOS high chip. Paired with 8079047.hu1."
|
||||
source_ref: "src/mem.c:181"
|
||||
note: >
|
||||
Tandy 1000 SL/2 BIOS high chip. Paired with 8079047.hu1.
|
||||
Also loaded by tandy_rom.c for full bank-switched ROM (512 KB).
|
||||
source_ref: "src/mem.c:181, src/tandy_rom.c:59"
|
||||
|
||||
# ========================================================
|
||||
# MACHINE BIOS ROMs - Amstrad / Sinclair
|
||||
@@ -360,7 +392,7 @@ files:
|
||||
- name: "Acer 386SX BIOS"
|
||||
path: "roms/acer386/acer386.bin"
|
||||
required: false
|
||||
note: "Acer 386SX/25N BIOS (64 KB). Also requires oti067.bin video ROM."
|
||||
note: "Acer 386SX/25N BIOS (64 KB)."
|
||||
source_ref: "src/mem.c:353-356"
|
||||
|
||||
- name: "Acer 386SX OTI-067 video BIOS"
|
||||
@@ -634,8 +666,8 @@ files:
|
||||
path: "roms/mach64gx/bios.bin"
|
||||
required: false
|
||||
note: >
|
||||
ATI Graphics Pro Turbo (Mach64 GX) video BIOS. Also used by the
|
||||
Cirrus Logic CL-GD5429 init path (likely a copy/paste in the source).
|
||||
ATI Graphics Pro Turbo (Mach64 GX) video BIOS. Also loaded by the
|
||||
Cirrus Logic init path (copy/paste in source).
|
||||
source_ref: "src/vid_ati_mach64.c:2341, src/vid_cirrus.c:2324"
|
||||
|
||||
- name: "Cirrus Logic CL-GD5429 BIOS"
|
||||
@@ -696,16 +728,224 @@ files:
|
||||
note: "NE2000 ISA Ethernet boot ROM (64 KB)."
|
||||
source_ref: "src/ne2000.c:1701"
|
||||
|
||||
notes:
|
||||
rom_structure: >
|
||||
All ROM files are loaded relative to the RetroArch system directory.
|
||||
Machine BIOS ROMs go in roms/<machine_name>/ subdirectories.
|
||||
Video card ROMs go directly in roms/ (or roms/<card>/ for some).
|
||||
Which ROMs are needed depends on the selected machine model and video card.
|
||||
minimum_requirement: >
|
||||
At minimum, the mda.rom font and one machine BIOS set are required.
|
||||
The video card ROM for the selected GPU is also needed unless the machine
|
||||
has built-in video (PCjr, Tandy, Amstrad, Acer 386SX, PS/1).
|
||||
libretro_version: >
|
||||
Based on PCem v10.1. The libretro port is quite old and may not match
|
||||
current standalone PCem/86Box ROM paths.
|
||||
# ========================================================
|
||||
# NVR/CMOS TEMPLATES (shipped with PCem distribution)
|
||||
# Pre-configured BIOS settings. The emulator loads these on
|
||||
# startup and writes back on shutdown. Without them the BIOS
|
||||
# starts with blank CMOS settings.
|
||||
# ========================================================
|
||||
- name: "NVR: Amstrad PC1512"
|
||||
path: "pc1512.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for Amstrad PC1512."
|
||||
source_ref: "src/nvr.c:390"
|
||||
|
||||
- name: "NVR: Amstrad PC1640"
|
||||
path: "pc1640.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for Amstrad PC1640."
|
||||
source_ref: "src/nvr.c:391"
|
||||
|
||||
- name: "NVR: Sinclair PC200"
|
||||
path: "pc200.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for Sinclair PC200."
|
||||
source_ref: "src/nvr.c:392"
|
||||
|
||||
- name: "NVR: Amstrad PC2086"
|
||||
path: "pc2086.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for Amstrad PC2086."
|
||||
source_ref: "src/nvr.c:393"
|
||||
|
||||
- name: "NVR: Amstrad PC3086"
|
||||
path: "pc3086.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for Amstrad PC3086."
|
||||
source_ref: "src/nvr.c:394"
|
||||
|
||||
- name: "NVR: IBM AT"
|
||||
path: "at.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for IBM AT."
|
||||
source_ref: "src/nvr.c:395"
|
||||
|
||||
- name: "NVR: IBM PS/1 model 2011"
|
||||
path: "ibmps1_2011.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for IBM PS/1 model 2011."
|
||||
source_ref: "src/nvr.c:396"
|
||||
|
||||
- name: "NVR: Commodore PC 30 III"
|
||||
path: "cmdpc30.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for Commodore PC 30 III."
|
||||
source_ref: "src/nvr.c:397"
|
||||
|
||||
- name: "NVR: AMI 286"
|
||||
path: "ami286.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for AMI 286 clone."
|
||||
source_ref: "src/nvr.c:398"
|
||||
|
||||
- name: "NVR: Dell System 200"
|
||||
path: "dell200.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for Dell System 200."
|
||||
source_ref: "src/nvr.c:399"
|
||||
|
||||
- name: "NVR: IBM AT 386"
|
||||
path: "at386.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for IBM AT 386."
|
||||
source_ref: "src/nvr.c:400"
|
||||
|
||||
- name: "NVR: Compaq Deskpro 386"
|
||||
path: "deskpro386.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for Compaq Deskpro 386."
|
||||
source_ref: "src/nvr.c:401"
|
||||
|
||||
- name: "NVR: Acer 386SX"
|
||||
path: "acer386.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for Acer 386SX/25N."
|
||||
source_ref: "src/nvr.c:402"
|
||||
|
||||
- name: "NVR: Amstrad MegaPC"
|
||||
path: "megapc.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for Amstrad MegaPC."
|
||||
source_ref: "src/nvr.c:403"
|
||||
|
||||
- name: "NVR: AMI 386"
|
||||
path: "ami386.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for AMI 386 clone."
|
||||
source_ref: "src/nvr.c:404"
|
||||
|
||||
- name: "NVR: AMI 486"
|
||||
path: "ami486.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for AMI 486 clone."
|
||||
source_ref: "src/nvr.c:405"
|
||||
|
||||
- name: "NVR: AMI WinBIOS 486"
|
||||
path: "win486.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for AMI WinBIOS 486."
|
||||
source_ref: "src/nvr.c:406"
|
||||
|
||||
- name: "NVR: HOT-433 PCI 486"
|
||||
path: "hot-433.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for HOT-433 PCI 486."
|
||||
source_ref: "src/nvr.c:407"
|
||||
|
||||
- name: "NVR: Award SiS 496"
|
||||
path: "sis496.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for Award SiS 496/497."
|
||||
source_ref: "src/nvr.c:408"
|
||||
|
||||
- name: "NVR: Award 430VX"
|
||||
path: "430vx.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for Award 430VX PCI."
|
||||
source_ref: "src/nvr.c:409"
|
||||
|
||||
- name: "NVR: Intel Premiere/PCI (Batman)"
|
||||
path: "revenge.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for Intel Premiere/PCI."
|
||||
source_ref: "src/nvr.c:410"
|
||||
|
||||
- name: "NVR: Intel Advanced/EV (Endeavor)"
|
||||
path: "endeavor.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for Intel Advanced/EV."
|
||||
source_ref: "src/nvr.c:411"
|
||||
|
||||
- name: "NVR: Phoenix 386"
|
||||
path: "px386.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for Phoenix 386 clone."
|
||||
source_ref: "src/nvr.c:412"
|
||||
|
||||
- name: "NVR: DTK 386"
|
||||
path: "dtk386.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for DTK 386SX clone."
|
||||
source_ref: "src/nvr.c:413"
|
||||
|
||||
- name: "NVR: DTK 486"
|
||||
path: "dtk486.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for DTK 486."
|
||||
source_ref: "src/nvr.c:414"
|
||||
|
||||
- name: "NVR: Rise R418"
|
||||
path: "r418.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for Rise Computer R418."
|
||||
source_ref: "src/nvr.c:415"
|
||||
|
||||
- name: "NVR: Intel Premiere/PCI II (Plato)"
|
||||
path: "plato.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for Intel Premiere/PCI II."
|
||||
source_ref: "src/nvr.c:416"
|
||||
|
||||
- name: "NVR: PC Partner MB500N"
|
||||
path: "mb500n.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for PC Partner MB500N."
|
||||
source_ref: "src/nvr.c:417"
|
||||
|
||||
- name: "NVR: Acer M3A"
|
||||
path: "acerm3a.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for Acer M3A."
|
||||
source_ref: "src/nvr.c:418"
|
||||
|
||||
- name: "NVR: Acer V35N"
|
||||
path: "acerv35n.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for Acer V35N."
|
||||
source_ref: "src/nvr.c:419"
|
||||
|
||||
- name: "NVR: ASUS P/I-P55T2P4"
|
||||
path: "p55t2p4.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for ASUS P/I-P55T2P4."
|
||||
source_ref: "src/nvr.c:420"
|
||||
|
||||
- name: "NVR: Epox P55-VA"
|
||||
path: "p55va.nvr"
|
||||
required: false
|
||||
note: "CMOS/NVRAM template for Epox P55-VA."
|
||||
source_ref: "src/nvr.c:421"
|
||||
|
||||
# ========================================================
|
||||
# EEPROM TEMPLATES (shipped with upstream PCem)
|
||||
# Persistent settings for Tandy EEPROM and AdLib Gold.
|
||||
# Loaded on startup, saved on shutdown. Without them the
|
||||
# hardware starts with blank/default configuration.
|
||||
# ========================================================
|
||||
- name: "Tandy 1000 HX EEPROM"
|
||||
path: "tandy1000hx.bin"
|
||||
required: false
|
||||
note: "Tandy 1000 HX EEPROM state (128 bytes). Initializes to zeros if absent."
|
||||
source_ref: "src/tandy_eeprom.c:128"
|
||||
|
||||
- name: "Tandy 1000 SL/2 EEPROM"
|
||||
path: "tandy1000sl2.bin"
|
||||
required: false
|
||||
note: "Tandy 1000 SL/2 EEPROM state (128 bytes). Initializes to zeros if absent."
|
||||
source_ref: "src/tandy_eeprom.c:131"
|
||||
|
||||
- name: "AdLib Gold EEPROM"
|
||||
path: "nvr/adgold.bin"
|
||||
required: false
|
||||
note: "AdLib Gold sound card EEPROM state (24 bytes). Initializes to defaults if absent."
|
||||
source_ref: "src/sound_adlibgold.c:789"
|
||||
|
||||
@@ -1,8 +1,46 @@
|
||||
emulator: "pcsx1"
|
||||
type: alias
|
||||
alias_of: "pcsx_rearmed"
|
||||
profiled_date: "2026-03-18"
|
||||
core_version: "r21"
|
||||
emulator: PCSX1
|
||||
type: libretro
|
||||
core_classification: community_fork
|
||||
source: "https://github.com/libretro/pcsx1-libretro"
|
||||
upstream: "https://github.com/notaz/pcsx_rearmed"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "r22"
|
||||
display_name: "Sony - PlayStation (PCSX1)"
|
||||
note: "This core uses the same BIOS/firmware as pcsx_rearmed. See emulators/pcsx_rearmed.yml for details."
|
||||
files: []
|
||||
cores: [pcsx1]
|
||||
systems: [sony-playstation]
|
||||
notes: >
|
||||
Never-completed fork of PCSX-ReARMed targeting general PC hardware. Experimental, abandoned since 2018.
|
||||
HLE BIOS built-in: if no BIOS file found, core warns and falls back to HLE automatically.
|
||||
BIOS search order: scph1001.bin, scph5501.bin, scph7001.bin by exact filename, then scans system
|
||||
directory for any file matching "scph*" (case-insensitive) with size == 512 KB.
|
||||
No region detection, no hash validation. First valid BIOS found is used regardless of region.
|
||||
.info declares scph5500/5501/5502 but code explicitly searches scph1001/5501/7001 first.
|
||||
|
||||
files:
|
||||
- name: "scph1001.bin"
|
||||
description: "SCPH-1001 (v2.2 12-04-95 A)"
|
||||
required: false
|
||||
hle_fallback: true
|
||||
size: 524288
|
||||
validation: [size]
|
||||
source_ref: "frontend/libretro.c:1229 (bios[0]), :1181 (size check)"
|
||||
|
||||
- name: "scph5501.bin"
|
||||
description: "SCPH-5501 (v3.0 11-18-96 A)"
|
||||
required: false
|
||||
hle_fallback: true
|
||||
size: 524288
|
||||
validation: [size]
|
||||
source_ref: "frontend/libretro.c:1229 (bios[1]), :1181 (size check)"
|
||||
|
||||
- name: "scph7001.bin"
|
||||
description: "SCPH-7001 (v4.1 12-16-97 A)"
|
||||
required: false
|
||||
hle_fallback: true
|
||||
size: 524288
|
||||
validation: [size]
|
||||
source_ref: "frontend/libretro.c:1229 (bios[2]), :1181 (size check)"
|
||||
|
||||
# Dynamic scan fallback: after the 3 named files, find_any_bios() scans the
|
||||
# system directory for any file starting with "scph" (case-insensitive) that
|
||||
# is exactly 512 KB. This covers scph5500.bin, scph5502.bin, scph3000.bin, etc.
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
emulator: PCSX-ReARMed
|
||||
type: libretro
|
||||
core_classification: embedded_hle
|
||||
source: "https://github.com/libretro/pcsx_rearmed"
|
||||
profiled_date: "2026-03-18"
|
||||
core_version: "r24"
|
||||
upstream: "https://github.com/notaz/pcsx_rearmed"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "r25"
|
||||
display_name: "Sony - PlayStation (PCSX ReARMed)"
|
||||
cores: [pcsx_rearmed, pcsx_rearmed_neon, pcsx_rearmed_interpreter]
|
||||
systems: [sony-playstation]
|
||||
bios_size: 524288 # 512 KB (0x80000); also accepts 4 MB psxonpsp combo (reads first 512 KB)
|
||||
verification: crc32
|
||||
notes: >
|
||||
HLE BIOS built-in: core option pcsx_rearmed_bios = "HLE" bypasses real BIOS entirely.
|
||||
Default is "auto" which searches system dir for listed filenames, then scans all files.
|
||||
@@ -17,7 +17,6 @@ notes: >
|
||||
Files named "unirom" (case-insensitive) are explicitly skipped.
|
||||
Region fallback: if the matching-region BIOS is missing, any available region BIOS is used over HLE.
|
||||
Three region slots: US (index 0), JP (index 1), EU (index 2) stored in Config.Bios[].
|
||||
All firmware is optional per libretro-core-info; HLE works but real BIOS improves compatibility.
|
||||
|
||||
files:
|
||||
# -- Region: Japan (PSX_REGION_JP = 1) --
|
||||
@@ -25,10 +24,11 @@ files:
|
||||
- name: "scph5500.bin"
|
||||
description: "SCPH-5500 (v3.0 09-09-96 J)"
|
||||
region: "NTSC-J"
|
||||
size: 524288
|
||||
required: false
|
||||
hle_fallback: true
|
||||
md5: "8dd7d5296a650fac7319bce665a6a53c"
|
||||
source_ref: "frontend/libretro.c:3710 (listed_bios[0])"
|
||||
validation: [size]
|
||||
source_ref: "frontend/libretro.c:3721-3724 (listed_bios[0])"
|
||||
notes: "Preferred JP BIOS, searched first by exact filename."
|
||||
|
||||
# -- Region: North America (PSX_REGION_US = 0) --
|
||||
@@ -36,10 +36,11 @@ files:
|
||||
- name: "scph5501.bin"
|
||||
description: "SCPH-5501 (v3.0 11-18-96 A)"
|
||||
region: "NTSC-U"
|
||||
size: 524288
|
||||
required: false
|
||||
hle_fallback: true
|
||||
md5: "490f666e1afb15b7362b406ed1cea246"
|
||||
source_ref: "frontend/libretro.c:3710 (listed_bios[1])"
|
||||
validation: [size]
|
||||
source_ref: "frontend/libretro.c:3721-3724 (listed_bios[1])"
|
||||
notes: "Preferred US BIOS, searched first by exact filename."
|
||||
|
||||
# -- Region: Europe (PSX_REGION_EU = 2) --
|
||||
@@ -47,48 +48,52 @@ files:
|
||||
- name: "scph5502.bin"
|
||||
description: "SCPH-5502 (v3.0 01-06-97 E)"
|
||||
region: "PAL"
|
||||
size: 524288
|
||||
required: false
|
||||
hle_fallback: true
|
||||
md5: "32736f17079d0b2b7024407c39bd3050"
|
||||
source_ref: "frontend/libretro.c:3710 (listed_bios[2])"
|
||||
validation: [size]
|
||||
source_ref: "frontend/libretro.c:3721-3724 (listed_bios[2])"
|
||||
notes: "Preferred EU BIOS, searched first by exact filename."
|
||||
|
||||
# -- Fallback BIOS filenames (searched in order after scph550x) --
|
||||
|
||||
- name: "psxonpsp660.bin"
|
||||
description: "PSP embedded PS1 BIOS (region-free, 512 KB or 4 MB accepted)"
|
||||
description: "PSP embedded PS1 BIOS (region detected at runtime)"
|
||||
region: "Auto"
|
||||
size: 524288
|
||||
required: false
|
||||
hle_fallback: true
|
||||
md5: "c53ca5908936d412331790f4426c6c33"
|
||||
source_ref: "frontend/libretro.c:3711 (listed_bios[3])"
|
||||
notes: "Region detected from content at runtime. 4 MB combo image accepted but only first 512 KB read."
|
||||
validation: [size]
|
||||
source_ref: "frontend/libretro.c:3721-3724 (listed_bios[3])"
|
||||
notes: "Region detected from content at runtime. Must be 512 KB in libretro context."
|
||||
|
||||
- name: "scph101.bin"
|
||||
description: "SCPH-101 (v4.4 03-24-00 A) - PSone US"
|
||||
region: "NTSC-U"
|
||||
size: 524288
|
||||
required: false
|
||||
hle_fallback: true
|
||||
md5: "6e3735ff4c7dc899ee98981c18c3666d"
|
||||
source_ref: "frontend/libretro.c:3711 (listed_bios[4])"
|
||||
notes: "PSone slim model. Searched by filename after scph550x and psxonpsp660."
|
||||
validation: [size]
|
||||
source_ref: "frontend/libretro.c:3721-3724 (listed_bios[4])"
|
||||
notes: "PSone slim model."
|
||||
|
||||
- name: "scph7001.bin"
|
||||
description: "SCPH-7001 (v4.1 12-16-97 A)"
|
||||
region: "NTSC-U"
|
||||
size: 524288
|
||||
required: false
|
||||
hle_fallback: true
|
||||
md5: "1e68c231d0896b7eadcad1d7d8e76129"
|
||||
source_ref: "frontend/libretro.c:3711 (listed_bios[5])"
|
||||
notes: "Searched by filename after psxonpsp660 and scph101."
|
||||
validation: [size]
|
||||
source_ref: "frontend/libretro.c:3721-3724 (listed_bios[5])"
|
||||
|
||||
- name: "scph1001.bin"
|
||||
description: "SCPH-1001 (v2.2 12-04-95 A)"
|
||||
region: "NTSC-U"
|
||||
size: 524288
|
||||
required: false
|
||||
hle_fallback: true
|
||||
md5: "924e392ed05558ffdb115408c263dccf"
|
||||
source_ref: "frontend/libretro.c:3711 (listed_bios[6])"
|
||||
validation: [size]
|
||||
source_ref: "frontend/libretro.c:3721-3724 (listed_bios[6])"
|
||||
notes: "Original US model. Last in the explicit filename search list."
|
||||
|
||||
# -- Dynamic scan fallback --
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
emulator: PicoDrive
|
||||
type: libretro
|
||||
core_classification: official_port
|
||||
source: "https://github.com/libretro/picodrive"
|
||||
profiled_date: "2026-03-18"
|
||||
upstream: "https://github.com/irixxxx/picodrive"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "1.99"
|
||||
display_name: "Sega - MS/GG/MD/CD/32X (PicoDrive)"
|
||||
cores: [picodrive]
|
||||
systems:
|
||||
- sega-megadrive
|
||||
- sega-genesis
|
||||
@@ -13,33 +16,48 @@ systems:
|
||||
- sega-mastersystem
|
||||
- sega-gamegear
|
||||
- sega-sg1000
|
||||
- sega-sc3000
|
||||
- sega-pico
|
||||
|
||||
notes: |
|
||||
PicoDrive is a fast Mega Drive / Genesis emulator with Mega CD, 32X, Master
|
||||
System, Game Gear, SG-1000 and Sega Pico support.
|
||||
System, Game Gear, SG-1000, SC-3000 and Sega Pico support. The libretro port
|
||||
is part of the PicoDrive source tree (platform/libretro/).
|
||||
|
||||
Mega CD / Sega CD games require a region-matching BIOS file. The core searches
|
||||
the system directory for each name in order, trying .bin then .zip extension,
|
||||
and uses the first file found. If no BIOS is found, CD games fail to load with
|
||||
PM_BAD_CD_NO_BIOS. MSU-MD games can run without BIOS.
|
||||
|
||||
BIOS filename search order (platform/libretro/libretro.c:1265-1329):
|
||||
BIOS filename search order (platform/libretro/libretro.c:1264-1328):
|
||||
US: us_scd2_9306, SegaCDBIOS9303, us_scd1_9210, bios_CD_U
|
||||
EU: eu_mcd2_9306, eu_mcd2_9303, eu_mcd1_9210, bios_CD_E
|
||||
JP: jp_mcd2_921222, jp_mcd1_9112, jp_mcd1_9111, bios_CD_J
|
||||
|
||||
32X BIOS files (m68k, master SH2, slave SH2) are fully optional. The core
|
||||
has built-in HLE that generates replacement code at startup when the external
|
||||
BIOS pointers are NULL (pico/32x/memory.c:2200 get_bios(), pico/32x/32x.c:172).
|
||||
The libretro frontend does not expose any 32X BIOS loading path. Only the
|
||||
standalone platform code references 32X_M_BIOS.BIN / 32X_S_BIOS.BIN, and
|
||||
that code is currently disabled (#if 0 in platform/common/emu.c:1529).
|
||||
32X BIOS files (m68k, master SH2, slave SH2) are fully optional. The engine
|
||||
has built-in HLE that generates replacement code at startup when the BIOS
|
||||
pointers are NULL (pico/32x/memory.c:2199 get_bios(), pico/32x/32x.c:172).
|
||||
No current frontend (libretro or standalone) loads these files; the standalone
|
||||
loading code is disabled (#if 0 in platform/common/emu.c:1529).
|
||||
|
||||
Master System, Game Gear, SG-1000: no BIOS file loaded. The core initializes
|
||||
VDP registers and RAM to simulate post-BIOS state (pico/sms.c:1080-1096).
|
||||
carthw.cfg is a cartridge hardware database loaded from the system directory.
|
||||
A built-in version is compiled into the core (pico/carthw_cfg.c). The external
|
||||
file supplements or overrides game-specific hardware detection (SVP, EEPROM
|
||||
types, special mappers, copy protection).
|
||||
|
||||
Master System, Game Gear, SG-1000, SC-3000: no BIOS file loaded. The core
|
||||
initializes VDP registers and RAM to simulate post-BIOS state
|
||||
(pico/sms.c:1080-1097).
|
||||
|
||||
files:
|
||||
# -------------------------------------------------------
|
||||
# Cartridge hardware database
|
||||
# -------------------------------------------------------
|
||||
- name: "carthw.cfg"
|
||||
required: false
|
||||
note: "Cartridge hardware database for special mapper and EEPROM detection. Built-in fallback compiled into the core."
|
||||
source_ref: "platform/libretro/libretro.c:1619, pico/cart.c:1020-1022"
|
||||
|
||||
# -------------------------------------------------------
|
||||
# Mega CD / Sega CD - US region
|
||||
# -------------------------------------------------------
|
||||
@@ -48,28 +66,28 @@ files:
|
||||
required: true
|
||||
size: 131072 # 128 KB (0x20000)
|
||||
note: "US Sega CD Model 2 BIOS (September 1993). First in US search order."
|
||||
source_ref: "platform/libretro/libretro.c:1266"
|
||||
source_ref: "platform/libretro/libretro.c:1265"
|
||||
|
||||
- name: "SegaCDBIOS9303.bin"
|
||||
system: sega-segacd
|
||||
required: false
|
||||
size: 131072
|
||||
note: "US Sega CD BIOS (March 1993). Second in US search order."
|
||||
source_ref: "platform/libretro/libretro.c:1266"
|
||||
source_ref: "platform/libretro/libretro.c:1265"
|
||||
|
||||
- name: "us_scd1_9210.bin"
|
||||
system: sega-segacd
|
||||
required: false
|
||||
size: 131072
|
||||
note: "US Sega CD Model 1 BIOS (October 1992). Third in US search order."
|
||||
source_ref: "platform/libretro/libretro.c:1266"
|
||||
source_ref: "platform/libretro/libretro.c:1265"
|
||||
|
||||
- name: "bios_CD_U.bin"
|
||||
system: sega-segacd
|
||||
required: false
|
||||
size: 131072
|
||||
note: "US Sega CD BIOS (generic name). Last in US search order."
|
||||
source_ref: "platform/libretro/libretro.c:1266"
|
||||
source_ref: "platform/libretro/libretro.c:1265"
|
||||
|
||||
# -------------------------------------------------------
|
||||
# Mega CD / Sega CD - EU region
|
||||
@@ -79,28 +97,28 @@ files:
|
||||
required: true
|
||||
size: 131072
|
||||
note: "EU Mega CD Model 2 BIOS (June 1993). First in EU search order."
|
||||
source_ref: "platform/libretro/libretro.c:1269"
|
||||
source_ref: "platform/libretro/libretro.c:1268"
|
||||
|
||||
- name: "eu_mcd2_9303.bin"
|
||||
system: sega-megacd
|
||||
required: false
|
||||
size: 131072
|
||||
note: "EU Mega CD Model 2 BIOS (March 1993). Second in EU search order."
|
||||
source_ref: "platform/libretro/libretro.c:1269"
|
||||
source_ref: "platform/libretro/libretro.c:1268"
|
||||
|
||||
- name: "eu_mcd1_9210.bin"
|
||||
system: sega-megacd
|
||||
required: false
|
||||
size: 131072
|
||||
note: "EU Mega CD Model 1 BIOS (October 1992). Third in EU search order."
|
||||
source_ref: "platform/libretro/libretro.c:1269"
|
||||
source_ref: "platform/libretro/libretro.c:1268"
|
||||
|
||||
- name: "bios_CD_E.bin"
|
||||
system: sega-megacd
|
||||
required: false
|
||||
size: 131072
|
||||
note: "EU Mega CD BIOS (generic name). Last in EU search order."
|
||||
source_ref: "platform/libretro/libretro.c:1269"
|
||||
source_ref: "platform/libretro/libretro.c:1268"
|
||||
|
||||
# -------------------------------------------------------
|
||||
# Mega CD / Sega CD - JP region
|
||||
@@ -110,31 +128,31 @@ files:
|
||||
required: true
|
||||
size: 131072
|
||||
note: "JP Mega CD Model 2 BIOS (December 1992). First in JP search order."
|
||||
source_ref: "platform/libretro/libretro.c:1272"
|
||||
source_ref: "platform/libretro/libretro.c:1271"
|
||||
|
||||
- name: "jp_mcd1_9112.bin"
|
||||
system: sega-megacd
|
||||
required: false
|
||||
size: 131072
|
||||
note: "JP Mega CD Model 1 BIOS (December 1991). Second in JP search order."
|
||||
source_ref: "platform/libretro/libretro.c:1272"
|
||||
source_ref: "platform/libretro/libretro.c:1271"
|
||||
|
||||
- name: "jp_mcd1_9111.bin"
|
||||
system: sega-megacd
|
||||
required: false
|
||||
size: 131072
|
||||
note: "JP Mega CD Model 1 BIOS (November 1991). Third in JP search order."
|
||||
source_ref: "platform/libretro/libretro.c:1272"
|
||||
source_ref: "platform/libretro/libretro.c:1271"
|
||||
|
||||
- name: "bios_CD_J.bin"
|
||||
system: sega-megacd
|
||||
required: false
|
||||
size: 131072
|
||||
note: "JP Mega CD BIOS (generic name). Last in JP search order."
|
||||
source_ref: "platform/libretro/libretro.c:1272"
|
||||
source_ref: "platform/libretro/libretro.c:1271"
|
||||
|
||||
# -------------------------------------------------------
|
||||
# Sega 32X - HLE available, not loaded by libretro frontend
|
||||
# Sega 32X - HLE available, no frontend loads these
|
||||
# -------------------------------------------------------
|
||||
- name: "32X_G_BIOS.BIN"
|
||||
system: sega-32x
|
||||
@@ -142,7 +160,7 @@ files:
|
||||
hle_fallback: true
|
||||
size: 256 # 0x100
|
||||
note: "32X 68K (Genesis-side) BIOS. HLE replacement generated when NULL."
|
||||
source_ref: "pico/32x/memory.c:2207-2243"
|
||||
source_ref: "pico/32x/memory.c:2206-2243"
|
||||
|
||||
- name: "32X_M_BIOS.BIN"
|
||||
system: sega-32x
|
||||
@@ -150,7 +168,7 @@ files:
|
||||
hle_fallback: true
|
||||
size: 2048 # 0x800
|
||||
note: "32X Master SH2 BIOS. HLE replacement generated when NULL."
|
||||
source_ref: "pico/32x/memory.c:2250-2277"
|
||||
source_ref: "pico/32x/memory.c:2249-2276"
|
||||
|
||||
- name: "32X_S_BIOS.BIN"
|
||||
system: sega-32x
|
||||
@@ -158,7 +176,7 @@ files:
|
||||
hle_fallback: true
|
||||
size: 1024 # 0x400
|
||||
note: "32X Slave SH2 BIOS. HLE replacement generated when NULL."
|
||||
source_ref: "pico/32x/memory.c:2280-2298"
|
||||
source_ref: "pico/32x/memory.c:2279-2297"
|
||||
|
||||
platform_details:
|
||||
megacd:
|
||||
@@ -166,16 +184,16 @@ platform_details:
|
||||
hle_available: false
|
||||
region_specific: true
|
||||
extensions_tried: [".bin", ".zip"]
|
||||
source_ref: "pico/pico_int.h:559, platform/libretro/libretro.c:1310-1318"
|
||||
source_ref: "pico/pico_int.h:559, platform/libretro/libretro.c:1309-1317"
|
||||
|
||||
32x:
|
||||
m68k_bios_size: 256 # 0x100
|
||||
master_sh2_bios_size: 2048 # 0x800
|
||||
slave_sh2_bios_size: 1024 # 0x400
|
||||
hle_available: true
|
||||
source_ref: "pico/pico.h:53-55, pico/pico_int.h:679-693"
|
||||
source_ref: "pico/pico.h:54-55, pico/pico_int.h:679-693"
|
||||
|
||||
sms:
|
||||
hle_available: true
|
||||
note: "No BIOS file loaded. VDP/RAM initialized to post-BIOS state."
|
||||
source_ref: "pico/sms.c:1080-1096"
|
||||
source_ref: "pico/sms.c:1080-1097"
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
emulator: PokeMini
|
||||
type: libretro
|
||||
core_classification: community_fork
|
||||
source: "https://github.com/libretro/PokeMini"
|
||||
profiled_date: "2026-03-18"
|
||||
upstream: "https://sourceforge.net/projects/pokemini/"
|
||||
profiled_date: "2026-03-24"
|
||||
core_version: "v0.60"
|
||||
display_name: "Nintendo - Pokemon Mini (PokeMini)"
|
||||
cores:
|
||||
@@ -56,8 +58,6 @@ files:
|
||||
required: false
|
||||
hle_fallback: true
|
||||
size: 4096
|
||||
md5: "1e4fb124a3a886865acb574f388c803d"
|
||||
sha1: "daad4113713ed776fbd47727762bca81ba74915f"
|
||||
source_ref: "source/PokeMini.c:189-206 (PokeMini_LoadBIOSFile), libretro/libretro.c:565 (path)"
|
||||
notes: "Mapped at $000000-$000FFF (4 KB). Read via Hardware.c:144-145. Falls back to embedded FreeBIOS if missing."
|
||||
|
||||
|
||||
@@ -79,12 +79,20 @@ def md5_composite(filepath: str | Path) -> str:
|
||||
names = sorted(n for n in zf.namelist() if not n.endswith("/"))
|
||||
h = hashlib.md5()
|
||||
for name in names:
|
||||
info = zf.getinfo(name)
|
||||
if info.file_size > 512 * 1024 * 1024:
|
||||
continue # skip oversized entries
|
||||
h.update(zf.read(name))
|
||||
result = h.hexdigest()
|
||||
_md5_composite_cache[key] = result
|
||||
return result
|
||||
|
||||
|
||||
def parse_md5_list(raw: str) -> list[str]:
|
||||
"""Parse comma-separated MD5 string into normalized lowercase list."""
|
||||
return [m.strip().lower() for m in raw.split(",") if m.strip()] if raw else []
|
||||
|
||||
|
||||
def load_platform_config(platform_name: str, platforms_dir: str = "platforms") -> dict:
|
||||
"""Load a platform config with inheritance and shared group resolution.
|
||||
|
||||
@@ -162,6 +170,7 @@ def resolve_local_file(
|
||||
db: dict,
|
||||
zip_contents: dict | None = None,
|
||||
dest_hint: str = "",
|
||||
_depth: int = 0,
|
||||
) -> tuple[str | None, str]:
|
||||
"""Resolve a BIOS file to its local path using database.json.
|
||||
|
||||
@@ -293,11 +302,14 @@ def resolve_local_file(
|
||||
return path, "zip_exact"
|
||||
|
||||
# MAME clone fallback: if a file was deduped, resolve via canonical
|
||||
if _depth < 3:
|
||||
clone_map = _get_mame_clone_map()
|
||||
canonical = clone_map.get(name)
|
||||
if canonical and canonical != name:
|
||||
canonical_entry = {"name": canonical}
|
||||
result = resolve_local_file(canonical_entry, db, zip_contents, dest_hint)
|
||||
result = resolve_local_file(
|
||||
canonical_entry, db, zip_contents, dest_hint, _depth=_depth + 1,
|
||||
)
|
||||
if result[0]:
|
||||
return result[0], "mame_clone"
|
||||
|
||||
@@ -333,6 +345,9 @@ def check_inside_zip(container: str, file_name: str, expected_md5: str) -> str:
|
||||
with zipfile.ZipFile(container) as archive:
|
||||
for fname in archive.namelist():
|
||||
if fname.casefold() == file_name.casefold():
|
||||
info = archive.getinfo(fname)
|
||||
if info.file_size > 512 * 1024 * 1024:
|
||||
return "error"
|
||||
if expected_md5 == "":
|
||||
return "ok"
|
||||
with archive.open(fname) as entry:
|
||||
@@ -365,10 +380,16 @@ def build_zip_contents_index(db: dict, max_entry_size: int = 512 * 1024 * 1024)
|
||||
return index
|
||||
|
||||
|
||||
_emulator_profiles_cache: dict[tuple[str, bool], dict[str, dict]] = {}
|
||||
|
||||
|
||||
def load_emulator_profiles(
|
||||
emulators_dir: str, skip_aliases: bool = True,
|
||||
) -> dict[str, dict]:
|
||||
"""Load all emulator YAML profiles from a directory."""
|
||||
"""Load all emulator YAML profiles from a directory (cached)."""
|
||||
cache_key = (os.path.realpath(emulators_dir), skip_aliases)
|
||||
if cache_key in _emulator_profiles_cache:
|
||||
return _emulator_profiles_cache[cache_key]
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
@@ -385,6 +406,7 @@ def load_emulator_profiles(
|
||||
if skip_aliases and profile.get("type") == "alias":
|
||||
continue
|
||||
profiles[f.stem] = profile
|
||||
_emulator_profiles_cache[cache_key] = profiles
|
||||
return profiles
|
||||
|
||||
|
||||
@@ -461,6 +483,192 @@ def resolve_platform_cores(
|
||||
}
|
||||
|
||||
|
||||
def _parse_validation(validation: list | dict | None) -> list[str]:
|
||||
"""Extract the validation check list from a file's validation field.
|
||||
|
||||
Handles both simple list and divergent (core/upstream) dict forms.
|
||||
For dicts, uses the ``core`` key since RetroArch users run the core.
|
||||
"""
|
||||
if validation is None:
|
||||
return []
|
||||
if isinstance(validation, list):
|
||||
return validation
|
||||
if isinstance(validation, dict):
|
||||
return validation.get("core", [])
|
||||
return []
|
||||
|
||||
|
||||
# Validation types that require console-specific cryptographic keys.
|
||||
# verify.py cannot reproduce these — size checks still apply if combined.
|
||||
_CRYPTO_CHECKS = frozenset({"signature", "crypto"})
|
||||
|
||||
# All reproducible validation types.
|
||||
_HASH_CHECKS = frozenset({"crc32", "md5", "sha1", "adler32"})
|
||||
|
||||
|
||||
def _build_validation_index(profiles: dict) -> dict[str, dict]:
|
||||
"""Build per-filename validation rules from emulator profiles.
|
||||
|
||||
Returns {filename: {"checks": [str], "size": int|None, "min_size": int|None,
|
||||
"max_size": int|None, "crc32": str|None, "md5": str|None, "sha1": str|None,
|
||||
"adler32": str|None, "crypto_only": [str]}}.
|
||||
|
||||
``crypto_only`` lists validation types we cannot reproduce (signature, crypto)
|
||||
so callers can report them as non-verifiable rather than silently skipping.
|
||||
|
||||
When multiple emulators reference the same file, merges checks (union).
|
||||
Raises ValueError if two profiles declare conflicting values.
|
||||
"""
|
||||
index: dict[str, dict] = {}
|
||||
sources: dict[str, dict[str, str]] = {}
|
||||
for emu_name, profile in profiles.items():
|
||||
if profile.get("type") in ("launcher", "alias"):
|
||||
continue
|
||||
for f in profile.get("files", []):
|
||||
fname = f.get("name", "")
|
||||
if not fname:
|
||||
continue
|
||||
checks = _parse_validation(f.get("validation"))
|
||||
if not checks:
|
||||
continue
|
||||
if fname not in index:
|
||||
index[fname] = {
|
||||
"checks": set(), "sizes": set(),
|
||||
"min_size": None, "max_size": None,
|
||||
"crc32": set(), "md5": set(), "sha1": set(), "sha256": set(),
|
||||
"adler32": set(), "crypto_only": set(),
|
||||
}
|
||||
sources[fname] = {}
|
||||
index[fname]["checks"].update(checks)
|
||||
# Track non-reproducible crypto checks
|
||||
index[fname]["crypto_only"].update(
|
||||
c for c in checks if c in _CRYPTO_CHECKS
|
||||
)
|
||||
# Size checks
|
||||
if "size" in checks:
|
||||
if f.get("size") is not None:
|
||||
index[fname]["sizes"].add(f["size"])
|
||||
if f.get("min_size") is not None:
|
||||
cur = index[fname]["min_size"]
|
||||
index[fname]["min_size"] = min(cur, f["min_size"]) if cur is not None else f["min_size"]
|
||||
if f.get("max_size") is not None:
|
||||
cur = index[fname]["max_size"]
|
||||
index[fname]["max_size"] = max(cur, f["max_size"]) if cur is not None else f["max_size"]
|
||||
# Hash checks — collect all accepted hashes as sets (multiple valid
|
||||
# versions of the same file, e.g. MT-32 ROM versions)
|
||||
if "crc32" in checks and f.get("crc32"):
|
||||
norm = f["crc32"].lower()
|
||||
if norm.startswith("0x"):
|
||||
norm = norm[2:]
|
||||
index[fname]["crc32"].add(norm)
|
||||
for hash_type in ("md5", "sha1", "sha256"):
|
||||
if hash_type in checks and f.get(hash_type):
|
||||
index[fname][hash_type].add(f[hash_type].lower())
|
||||
# Adler32 — stored as known_hash_adler32 field (not in validation: list
|
||||
# for Dolphin, but support it in both forms for future profiles)
|
||||
adler_val = f.get("known_hash_adler32") or f.get("adler32")
|
||||
if adler_val:
|
||||
norm = adler_val.lower()
|
||||
if norm.startswith("0x"):
|
||||
norm = norm[2:]
|
||||
index[fname]["adler32"].add(norm)
|
||||
# Convert sets to sorted tuples/lists for determinism
|
||||
for v in index.values():
|
||||
v["checks"] = sorted(v["checks"])
|
||||
v["crypto_only"] = sorted(v["crypto_only"])
|
||||
# Keep hash sets as frozensets for O(1) lookup in check_file_validation
|
||||
return index
|
||||
|
||||
|
||||
def check_file_validation(
|
||||
local_path: str, filename: str, validation_index: dict[str, dict],
|
||||
bios_dir: str = "bios",
|
||||
) -> str | None:
|
||||
"""Check emulator-level validation on a resolved file.
|
||||
|
||||
Supports: size (exact/min/max), crc32, md5, sha1, adler32,
|
||||
signature (RSA-2048 PKCS1v15 SHA256), crypto (AES-128-CBC + SHA256).
|
||||
|
||||
Returns None if all checks pass or no validation applies.
|
||||
Returns a reason string if a check fails.
|
||||
"""
|
||||
entry = validation_index.get(filename)
|
||||
if not entry:
|
||||
return None
|
||||
checks = entry["checks"]
|
||||
|
||||
# Size checks — sizes is a set of accepted values
|
||||
if "size" in checks:
|
||||
actual_size = os.path.getsize(local_path)
|
||||
if entry["sizes"] and actual_size not in entry["sizes"]:
|
||||
expected = ",".join(str(s) for s in sorted(entry["sizes"]))
|
||||
return f"size mismatch: got {actual_size}, accepted [{expected}]"
|
||||
if entry["min_size"] is not None and actual_size < entry["min_size"]:
|
||||
return f"size too small: min {entry['min_size']}, got {actual_size}"
|
||||
if entry["max_size"] is not None and actual_size > entry["max_size"]:
|
||||
return f"size too large: max {entry['max_size']}, got {actual_size}"
|
||||
|
||||
# Hash checks — compute once, reuse for all hash types.
|
||||
# Each hash field is a set of accepted values (multiple valid ROM versions).
|
||||
need_hashes = (
|
||||
any(h in checks and entry.get(h) for h in ("crc32", "md5", "sha1", "sha256"))
|
||||
or entry.get("adler32")
|
||||
)
|
||||
if need_hashes:
|
||||
hashes = compute_hashes(local_path)
|
||||
if "crc32" in checks and entry["crc32"]:
|
||||
if hashes["crc32"].lower() not in entry["crc32"]:
|
||||
expected = ",".join(sorted(entry["crc32"]))
|
||||
return f"crc32 mismatch: got {hashes['crc32']}, accepted [{expected}]"
|
||||
if "md5" in checks and entry["md5"]:
|
||||
if hashes["md5"].lower() not in entry["md5"]:
|
||||
expected = ",".join(sorted(entry["md5"]))
|
||||
return f"md5 mismatch: got {hashes['md5']}, accepted [{expected}]"
|
||||
if "sha1" in checks and entry["sha1"]:
|
||||
if hashes["sha1"].lower() not in entry["sha1"]:
|
||||
expected = ",".join(sorted(entry["sha1"]))
|
||||
return f"sha1 mismatch: got {hashes['sha1']}, accepted [{expected}]"
|
||||
if "sha256" in checks and entry["sha256"]:
|
||||
if hashes["sha256"].lower() not in entry["sha256"]:
|
||||
expected = ",".join(sorted(entry["sha256"]))
|
||||
return f"sha256 mismatch: got {hashes['sha256']}, accepted [{expected}]"
|
||||
if entry["adler32"]:
|
||||
if hashes["adler32"].lower() not in entry["adler32"]:
|
||||
expected = ",".join(sorted(entry["adler32"]))
|
||||
return f"adler32 mismatch: got 0x{hashes['adler32']}, accepted [{expected}]"
|
||||
|
||||
# Signature/crypto checks (3DS RSA, AES)
|
||||
if entry["crypto_only"]:
|
||||
from crypto_verify import check_crypto_validation
|
||||
crypto_reason = check_crypto_validation(local_path, filename, bios_dir)
|
||||
if crypto_reason:
|
||||
return crypto_reason
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def validate_cli_modes(args, mode_attrs: list[str]) -> None:
|
||||
"""Validate mutual exclusion of CLI mode arguments."""
|
||||
modes = sum(1 for attr in mode_attrs if getattr(args, attr, None))
|
||||
if modes == 0:
|
||||
raise SystemExit(f"Specify one of: --{' --'.join(mode_attrs)}")
|
||||
if modes > 1:
|
||||
raise SystemExit(f"Options are mutually exclusive: --{' --'.join(mode_attrs)}")
|
||||
|
||||
|
||||
def filter_files_by_mode(files: list[dict], standalone: bool) -> list[dict]:
|
||||
"""Filter file entries by libretro/standalone mode."""
|
||||
result = []
|
||||
for f in files:
|
||||
fmode = f.get("mode", "")
|
||||
if standalone and fmode == "libretro":
|
||||
continue
|
||||
if not standalone and fmode == "standalone":
|
||||
continue
|
||||
result.append(f)
|
||||
return result
|
||||
|
||||
|
||||
def safe_extract_zip(zip_path: str, dest_dir: str) -> None:
|
||||
"""Extract a ZIP file safely, preventing zip-slip path traversal."""
|
||||
dest = os.path.realpath(dest_dir)
|
||||
@@ -470,3 +678,31 @@ def safe_extract_zip(zip_path: str, dest_dir: str) -> None:
|
||||
if not member_path.startswith(dest + os.sep) and member_path != dest:
|
||||
raise ValueError(f"Zip slip detected: {member.filename}")
|
||||
zf.extract(member, dest)
|
||||
|
||||
|
||||
def list_emulator_profiles(emulators_dir: str, skip_aliases: bool = True) -> None:
|
||||
"""Print available emulator profiles."""
|
||||
profiles = load_emulator_profiles(emulators_dir, skip_aliases=False)
|
||||
for name in sorted(profiles):
|
||||
p = profiles[name]
|
||||
if p.get("type") in ("alias", "test"):
|
||||
continue
|
||||
display = p.get("emulator", name)
|
||||
ptype = p.get("type", "libretro")
|
||||
systems = ", ".join(p.get("systems", [])[:3])
|
||||
more = "..." if len(p.get("systems", [])) > 3 else ""
|
||||
print(f" {name:30s} {display:40s} [{ptype}] {systems}{more}")
|
||||
|
||||
|
||||
def list_system_ids(emulators_dir: str) -> None:
|
||||
"""Print available system IDs with emulator count."""
|
||||
profiles = load_emulator_profiles(emulators_dir)
|
||||
system_emus: dict[str, list[str]] = {}
|
||||
for name, p in profiles.items():
|
||||
if p.get("type") in ("alias", "test", "launcher"):
|
||||
continue
|
||||
for sys_id in p.get("systems", []):
|
||||
system_emus.setdefault(sys_id, []).append(name)
|
||||
for sys_id in sorted(system_emus):
|
||||
count = len(system_emus[sys_id])
|
||||
print(f" {sys_id:35s} ({count} emulator{'s' if count > 1 else ''})")
|
||||
|
||||
@@ -19,6 +19,7 @@ from __future__ import annotations
|
||||
import hashlib
|
||||
import struct
|
||||
import subprocess
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@@ -418,7 +419,7 @@ def verify_otp(
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Map from (filename, validation_type) to verification function
|
||||
_CRYPTO_VERIFIERS: dict[str, callable] = {
|
||||
_CRYPTO_VERIFIERS: dict[str, Callable] = {
|
||||
"SecureInfo_A": verify_secure_info_a,
|
||||
"LocalFriendCodeSeed_B": verify_local_friend_code_seed_b,
|
||||
"movable.sed": verify_movable_sed,
|
||||
|
||||
@@ -25,10 +25,11 @@ from pathlib import Path
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from common import (
|
||||
build_zip_contents_index, check_inside_zip, compute_hashes,
|
||||
group_identical_platforms, load_database, load_data_dir_registry,
|
||||
load_emulator_profiles, load_platform_config, md5_composite,
|
||||
resolve_local_file,
|
||||
_build_validation_index, build_zip_contents_index, check_file_validation,
|
||||
check_inside_zip, compute_hashes, filter_files_by_mode,
|
||||
group_identical_platforms, list_emulator_profiles, list_system_ids,
|
||||
load_database, load_data_dir_registry, load_emulator_profiles,
|
||||
load_platform_config, md5_composite, resolve_local_file,
|
||||
)
|
||||
from deterministic_zip import rebuild_zip_deterministic
|
||||
|
||||
@@ -256,7 +257,6 @@ def generate_pack(
|
||||
file_reasons: dict[str, str] = {}
|
||||
|
||||
# Build emulator-level validation index (same as verify.py)
|
||||
from verify import _build_validation_index
|
||||
validation_index = {}
|
||||
if emu_profiles:
|
||||
validation_index = _build_validation_index(emu_profiles)
|
||||
@@ -367,7 +367,6 @@ def generate_pack(
|
||||
# In md5 mode: validation downgrades OK to UNTESTED
|
||||
if (file_status.get(dedup_key) == "ok"
|
||||
and local_path and validation_index):
|
||||
from verify import check_file_validation
|
||||
fname = file_entry.get("name", "")
|
||||
reason = check_file_validation(local_path, fname, validation_index)
|
||||
if reason:
|
||||
@@ -523,19 +522,6 @@ def _normalize_zip_for_pack(source_zip: str, dest_path: str, target_zf: zipfile.
|
||||
# Emulator/system mode pack generation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _filter_files_by_mode(files: list[dict], standalone: bool) -> list[dict]:
|
||||
"""Filter file entries by libretro/standalone mode."""
|
||||
result = []
|
||||
for f in files:
|
||||
fmode = f.get("mode", "")
|
||||
if standalone and fmode == "libretro":
|
||||
continue
|
||||
if not standalone and fmode == "standalone":
|
||||
continue
|
||||
result.append(f)
|
||||
return result
|
||||
|
||||
|
||||
def _resolve_destination(file_entry: dict, pack_structure: dict | None,
|
||||
standalone: bool) -> str:
|
||||
"""Resolve the ZIP destination path for a file entry."""
|
||||
@@ -620,7 +606,7 @@ def generate_emulator_pack(
|
||||
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
|
||||
for emu_name, profile in sorted(selected):
|
||||
pack_structure = profile.get("pack_structure")
|
||||
files = _filter_files_by_mode(profile.get("files", []), standalone)
|
||||
files = filter_files_by_mode(profile.get("files", []), standalone)
|
||||
for dd in profile.get("data_directories", []):
|
||||
ref_key = dd.get("ref", "")
|
||||
if not ref_key or not data_registry or ref_key not in data_registry:
|
||||
@@ -825,34 +811,6 @@ def generate_system_pack(
|
||||
return result
|
||||
|
||||
|
||||
def _list_emulators_pack(emulators_dir: str) -> None:
|
||||
"""Print available emulator profiles for pack generation."""
|
||||
profiles = load_emulator_profiles(emulators_dir, skip_aliases=False)
|
||||
for name in sorted(profiles):
|
||||
p = profiles[name]
|
||||
if p.get("type") in ("alias", "test"):
|
||||
continue
|
||||
display = p.get("emulator", name)
|
||||
ptype = p.get("type", "libretro")
|
||||
systems = ", ".join(p.get("systems", [])[:3])
|
||||
more = "..." if len(p.get("systems", [])) > 3 else ""
|
||||
print(f" {name:30s} {display:40s} [{ptype}] {systems}{more}")
|
||||
|
||||
|
||||
def _list_systems_pack(emulators_dir: str) -> None:
|
||||
"""Print available system IDs with emulator count."""
|
||||
profiles = load_emulator_profiles(emulators_dir)
|
||||
system_emus: dict[str, list[str]] = {}
|
||||
for name, p in profiles.items():
|
||||
if p.get("type") in ("alias", "test", "launcher"):
|
||||
continue
|
||||
for sys_id in p.get("systems", []):
|
||||
system_emus.setdefault(sys_id, []).append(name)
|
||||
for sys_id in sorted(system_emus):
|
||||
count = len(system_emus[sys_id])
|
||||
print(f" {sys_id:35s} ({count} emulator{'s' if count > 1 else ''})")
|
||||
|
||||
|
||||
def list_platforms(platforms_dir: str) -> list[str]:
|
||||
"""List available platform names from YAML files."""
|
||||
platforms = []
|
||||
@@ -893,10 +851,10 @@ def main():
|
||||
print(p)
|
||||
return
|
||||
if args.list_emulators:
|
||||
_list_emulators_pack(args.emulators_dir)
|
||||
list_emulator_profiles(args.emulators_dir)
|
||||
return
|
||||
if args.list_systems:
|
||||
_list_systems_pack(args.emulators_dir)
|
||||
list_system_ids(args.emulators_dir)
|
||||
return
|
||||
|
||||
# Mutual exclusion
|
||||
@@ -1022,10 +980,15 @@ def verify_pack(zip_path: str, db: dict) -> tuple[bool, dict]:
|
||||
if name.startswith("INSTRUCTIONS_") or name == "manifest.json":
|
||||
continue
|
||||
with zf.open(info) as f:
|
||||
data = f.read()
|
||||
sha1 = hashlib.sha1(data).hexdigest()
|
||||
md5 = hashlib.md5(data).hexdigest()
|
||||
size = len(data)
|
||||
sha1_h = hashlib.sha1()
|
||||
md5_h = hashlib.md5()
|
||||
size = 0
|
||||
for chunk in iter(lambda: f.read(65536), b""):
|
||||
sha1_h.update(chunk)
|
||||
md5_h.update(chunk)
|
||||
size += len(chunk)
|
||||
sha1 = sha1_h.hexdigest()
|
||||
md5 = md5_h.hexdigest()
|
||||
|
||||
# Look up in database: files_db keyed by SHA1
|
||||
db_entry = files_db.get(sha1)
|
||||
@@ -1080,11 +1043,19 @@ def verify_pack(zip_path: str, db: dict) -> tuple[bool, dict]:
|
||||
|
||||
def inject_manifest(zip_path: str, manifest: dict) -> None:
|
||||
"""Inject manifest.json into an existing ZIP pack."""
|
||||
import tempfile as _tempfile
|
||||
manifest_json = json.dumps(manifest, indent=2, ensure_ascii=False)
|
||||
|
||||
# ZipFile doesn't support appending to existing entries,
|
||||
# so we rebuild with the manifest added
|
||||
# Check if manifest already exists
|
||||
with zipfile.ZipFile(zip_path, "r") as zf:
|
||||
has_manifest = "manifest.json" in zf.namelist()
|
||||
|
||||
if not has_manifest:
|
||||
# Fast path: append directly
|
||||
with zipfile.ZipFile(zip_path, "a") as zf:
|
||||
zf.writestr("manifest.json", manifest_json)
|
||||
else:
|
||||
# Rebuild to replace existing manifest
|
||||
import tempfile as _tempfile
|
||||
tmp_fd, tmp_path = _tempfile.mkstemp(suffix=".zip", dir=os.path.dirname(zip_path))
|
||||
os.close(tmp_fd)
|
||||
try:
|
||||
@@ -1092,11 +1063,11 @@ def inject_manifest(zip_path: str, manifest: dict) -> None:
|
||||
zipfile.ZipFile(tmp_path, "w", zipfile.ZIP_DEFLATED) as dst:
|
||||
for item in src.infolist():
|
||||
if item.filename == "manifest.json":
|
||||
continue # replace existing
|
||||
continue
|
||||
dst.writestr(item, src.read(item.filename))
|
||||
dst.writestr("manifest.json", manifest_json)
|
||||
os.replace(tmp_path, zip_path)
|
||||
except Exception:
|
||||
except (OSError, zipfile.BadZipFile):
|
||||
os.unlink(tmp_path)
|
||||
raise
|
||||
|
||||
|
||||
@@ -198,11 +198,22 @@ def _download_and_extract(
|
||||
shutil.copyfileobj(src, dst)
|
||||
file_count += 1
|
||||
|
||||
# atomic swap: remove old cache, move new into place
|
||||
if cache_dir.exists():
|
||||
shutil.rmtree(cache_dir)
|
||||
# atomic swap: rename old before moving new into place
|
||||
cache_dir.parent.mkdir(parents=True, exist_ok=True)
|
||||
old_cache = cache_dir.with_suffix(".old")
|
||||
if cache_dir.exists():
|
||||
if old_cache.exists():
|
||||
shutil.rmtree(old_cache)
|
||||
cache_dir.rename(old_cache)
|
||||
try:
|
||||
shutil.move(str(extract_dir), str(cache_dir))
|
||||
except OSError:
|
||||
# Restore old cache on failure
|
||||
if old_cache.exists() and not cache_dir.exists():
|
||||
old_cache.rename(cache_dir)
|
||||
raise
|
||||
if old_cache.exists():
|
||||
shutil.rmtree(old_cache)
|
||||
|
||||
return file_count
|
||||
|
||||
|
||||
@@ -194,6 +194,7 @@ class Scraper(BaseScraper):
|
||||
"""Scraper for libretro-core-info firmware declarations."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._info_files: dict[str, dict] | None = None
|
||||
|
||||
def _fetch_info_list(self) -> list[str]:
|
||||
|
||||
@@ -185,6 +185,7 @@ class Scraper(BaseScraper):
|
||||
"""Scraper for EmuDeck checkBIOS.sh and CSV cheat sheets."""
|
||||
|
||||
def __init__(self, checkbios_url: str = CHECKBIOS_URL, csv_base_url: str = CSV_BASE_URL):
|
||||
super().__init__(url=checkbios_url)
|
||||
self.checkbios_url = checkbios_url
|
||||
self.csv_base_url = csv_base_url
|
||||
self._raw_script: str | None = None
|
||||
|
||||
@@ -93,7 +93,10 @@ class ValidationResult:
|
||||
def load_database(db_path: str) -> dict | None:
|
||||
try:
|
||||
return _load_database(db_path)
|
||||
except (FileNotFoundError, json.JSONDecodeError):
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"WARNING: corrupt database.json: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -35,13 +35,12 @@ except ImportError:
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from common import (
|
||||
build_zip_contents_index, check_inside_zip, compute_hashes,
|
||||
group_identical_platforms, load_data_dir_registry,
|
||||
load_emulator_profiles, load_platform_config,
|
||||
_build_validation_index, build_zip_contents_index, check_file_validation,
|
||||
check_inside_zip, compute_hashes, filter_files_by_mode,
|
||||
group_identical_platforms, list_emulator_profiles, list_system_ids,
|
||||
load_data_dir_registry, load_emulator_profiles, load_platform_config,
|
||||
md5sum, md5_composite, resolve_local_file, resolve_platform_cores,
|
||||
)
|
||||
from crypto_verify import check_crypto_validation
|
||||
|
||||
DEFAULT_DB = "database.json"
|
||||
DEFAULT_PLATFORMS_DIR = "platforms"
|
||||
DEFAULT_EMULATORS_DIR = "emulators"
|
||||
@@ -68,173 +67,6 @@ _STATUS_ORDER = {Status.OK: 0, Status.UNTESTED: 1, Status.MISSING: 2}
|
||||
_SEVERITY_ORDER = {Severity.OK: 0, Severity.INFO: 1, Severity.WARNING: 2, Severity.CRITICAL: 3}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Emulator-level validation (size, crc32 checks from emulator profiles)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _parse_validation(validation: list | dict | None) -> list[str]:
|
||||
"""Extract the validation check list from a file's validation field.
|
||||
|
||||
Handles both simple list and divergent (core/upstream) dict forms.
|
||||
For dicts, uses the ``core`` key since RetroArch users run the core.
|
||||
"""
|
||||
if validation is None:
|
||||
return []
|
||||
if isinstance(validation, list):
|
||||
return validation
|
||||
if isinstance(validation, dict):
|
||||
return validation.get("core", [])
|
||||
return []
|
||||
|
||||
|
||||
# Validation types that require console-specific cryptographic keys.
|
||||
# verify.py cannot reproduce these — size checks still apply if combined.
|
||||
_CRYPTO_CHECKS = frozenset({"signature", "crypto"})
|
||||
|
||||
# All reproducible validation types.
|
||||
_HASH_CHECKS = frozenset({"crc32", "md5", "sha1", "adler32"})
|
||||
|
||||
|
||||
def _build_validation_index(profiles: dict) -> dict[str, dict]:
|
||||
"""Build per-filename validation rules from emulator profiles.
|
||||
|
||||
Returns {filename: {"checks": [str], "size": int|None, "min_size": int|None,
|
||||
"max_size": int|None, "crc32": str|None, "md5": str|None, "sha1": str|None,
|
||||
"adler32": str|None, "crypto_only": [str]}}.
|
||||
|
||||
``crypto_only`` lists validation types we cannot reproduce (signature, crypto)
|
||||
so callers can report them as non-verifiable rather than silently skipping.
|
||||
|
||||
When multiple emulators reference the same file, merges checks (union).
|
||||
Raises ValueError if two profiles declare conflicting values.
|
||||
"""
|
||||
index: dict[str, dict] = {}
|
||||
sources: dict[str, dict[str, str]] = {}
|
||||
for emu_name, profile in profiles.items():
|
||||
if profile.get("type") in ("launcher", "alias"):
|
||||
continue
|
||||
for f in profile.get("files", []):
|
||||
fname = f.get("name", "")
|
||||
if not fname:
|
||||
continue
|
||||
checks = _parse_validation(f.get("validation"))
|
||||
if not checks:
|
||||
continue
|
||||
if fname not in index:
|
||||
index[fname] = {
|
||||
"checks": set(), "sizes": set(),
|
||||
"min_size": None, "max_size": None,
|
||||
"crc32": set(), "md5": set(), "sha1": set(), "sha256": set(),
|
||||
"adler32": set(), "crypto_only": set(),
|
||||
}
|
||||
sources[fname] = {}
|
||||
index[fname]["checks"].update(checks)
|
||||
# Track non-reproducible crypto checks
|
||||
index[fname]["crypto_only"].update(
|
||||
c for c in checks if c in _CRYPTO_CHECKS
|
||||
)
|
||||
# Size checks
|
||||
if "size" in checks:
|
||||
if f.get("size") is not None:
|
||||
index[fname]["sizes"].add(f["size"])
|
||||
if f.get("min_size") is not None:
|
||||
cur = index[fname]["min_size"]
|
||||
index[fname]["min_size"] = min(cur, f["min_size"]) if cur is not None else f["min_size"]
|
||||
if f.get("max_size") is not None:
|
||||
cur = index[fname]["max_size"]
|
||||
index[fname]["max_size"] = max(cur, f["max_size"]) if cur is not None else f["max_size"]
|
||||
# Hash checks — collect all accepted hashes as sets (multiple valid
|
||||
# versions of the same file, e.g. MT-32 ROM versions)
|
||||
if "crc32" in checks and f.get("crc32"):
|
||||
norm = f["crc32"].lower()
|
||||
if norm.startswith("0x"):
|
||||
norm = norm[2:]
|
||||
index[fname]["crc32"].add(norm)
|
||||
for hash_type in ("md5", "sha1", "sha256"):
|
||||
if hash_type in checks and f.get(hash_type):
|
||||
index[fname][hash_type].add(f[hash_type].lower())
|
||||
# Adler32 — stored as known_hash_adler32 field (not in validation: list
|
||||
# for Dolphin, but support it in both forms for future profiles)
|
||||
adler_val = f.get("known_hash_adler32") or f.get("adler32")
|
||||
if adler_val:
|
||||
norm = adler_val.lower()
|
||||
if norm.startswith("0x"):
|
||||
norm = norm[2:]
|
||||
index[fname]["adler32"].add(norm)
|
||||
# Convert sets to sorted tuples/lists for determinism
|
||||
for v in index.values():
|
||||
v["checks"] = sorted(v["checks"])
|
||||
v["crypto_only"] = sorted(v["crypto_only"])
|
||||
# Keep hash sets as frozensets for O(1) lookup in check_file_validation
|
||||
return index
|
||||
|
||||
|
||||
def check_file_validation(
|
||||
local_path: str, filename: str, validation_index: dict[str, dict],
|
||||
bios_dir: str = "bios",
|
||||
) -> str | None:
|
||||
"""Check emulator-level validation on a resolved file.
|
||||
|
||||
Supports: size (exact/min/max), crc32, md5, sha1, adler32,
|
||||
signature (RSA-2048 PKCS1v15 SHA256), crypto (AES-128-CBC + SHA256).
|
||||
|
||||
Returns None if all checks pass or no validation applies.
|
||||
Returns a reason string if a check fails.
|
||||
"""
|
||||
entry = validation_index.get(filename)
|
||||
if not entry:
|
||||
return None
|
||||
checks = entry["checks"]
|
||||
|
||||
# Size checks — sizes is a set of accepted values
|
||||
if "size" in checks:
|
||||
actual_size = os.path.getsize(local_path)
|
||||
if entry["sizes"] and actual_size not in entry["sizes"]:
|
||||
expected = ",".join(str(s) for s in sorted(entry["sizes"]))
|
||||
return f"size mismatch: got {actual_size}, accepted [{expected}]"
|
||||
if entry["min_size"] is not None and actual_size < entry["min_size"]:
|
||||
return f"size too small: min {entry['min_size']}, got {actual_size}"
|
||||
if entry["max_size"] is not None and actual_size > entry["max_size"]:
|
||||
return f"size too large: max {entry['max_size']}, got {actual_size}"
|
||||
|
||||
# Hash checks — compute once, reuse for all hash types.
|
||||
# Each hash field is a set of accepted values (multiple valid ROM versions).
|
||||
need_hashes = (
|
||||
any(h in checks and entry.get(h) for h in ("crc32", "md5", "sha1", "sha256"))
|
||||
or entry.get("adler32")
|
||||
)
|
||||
if need_hashes:
|
||||
hashes = compute_hashes(local_path)
|
||||
if "crc32" in checks and entry["crc32"]:
|
||||
if hashes["crc32"].lower() not in entry["crc32"]:
|
||||
expected = ",".join(sorted(entry["crc32"]))
|
||||
return f"crc32 mismatch: got {hashes['crc32']}, accepted [{expected}]"
|
||||
if "md5" in checks and entry["md5"]:
|
||||
if hashes["md5"].lower() not in entry["md5"]:
|
||||
expected = ",".join(sorted(entry["md5"]))
|
||||
return f"md5 mismatch: got {hashes['md5']}, accepted [{expected}]"
|
||||
if "sha1" in checks and entry["sha1"]:
|
||||
if hashes["sha1"].lower() not in entry["sha1"]:
|
||||
expected = ",".join(sorted(entry["sha1"]))
|
||||
return f"sha1 mismatch: got {hashes['sha1']}, accepted [{expected}]"
|
||||
if "sha256" in checks and entry["sha256"]:
|
||||
if hashes["sha256"].lower() not in entry["sha256"]:
|
||||
expected = ",".join(sorted(entry["sha256"]))
|
||||
return f"sha256 mismatch: got {hashes['sha256']}, accepted [{expected}]"
|
||||
if entry["adler32"]:
|
||||
if hashes["adler32"].lower() not in entry["adler32"]:
|
||||
expected = ",".join(sorted(entry["adler32"]))
|
||||
return f"adler32 mismatch: got 0x{hashes['adler32']}, accepted [{expected}]"
|
||||
|
||||
# Signature/crypto checks (3DS RSA, AES)
|
||||
if entry["crypto_only"]:
|
||||
crypto_reason = check_crypto_validation(local_path, filename, bios_dir)
|
||||
if crypto_reason:
|
||||
return crypto_reason
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Verification functions
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -269,7 +101,7 @@ def verify_entry_md5(
|
||||
base = {"name": name, "required": required}
|
||||
|
||||
if expected_md5 and "," in expected_md5:
|
||||
md5_list = [m.strip() for m in expected_md5.split(",") if m.strip()]
|
||||
md5_list = [m.strip().lower() for m in expected_md5.split(",") if m.strip()]
|
||||
else:
|
||||
md5_list = [expected_md5] if expected_md5 else []
|
||||
|
||||
@@ -695,19 +527,6 @@ def print_platform_result(result: dict, group: list[str]) -> None:
|
||||
# Emulator/system mode verification
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _filter_files_by_mode(files: list[dict], standalone: bool) -> list[dict]:
|
||||
"""Filter file entries by libretro/standalone mode."""
|
||||
result = []
|
||||
for f in files:
|
||||
fmode = f.get("mode", "")
|
||||
if standalone and fmode == "libretro":
|
||||
continue
|
||||
if not standalone and fmode == "standalone":
|
||||
continue
|
||||
result.append(f)
|
||||
return result
|
||||
|
||||
|
||||
def _effective_validation_label(details: list[dict], validation_index: dict) -> str:
|
||||
"""Determine the bracket label for the report.
|
||||
|
||||
@@ -783,7 +602,7 @@ def verify_emulator(
|
||||
data_dir_notices: list[str] = []
|
||||
|
||||
for emu_name, profile in selected:
|
||||
files = _filter_files_by_mode(profile.get("files", []), standalone)
|
||||
files = filter_files_by_mode(profile.get("files", []), standalone)
|
||||
|
||||
# Check data directories (only notice if not cached)
|
||||
for dd in profile.get("data_directories", []):
|
||||
@@ -976,34 +795,6 @@ def print_emulator_result(result: dict) -> None:
|
||||
print(f" Note: data directory '{ref}' required but not included (use refresh_data_dirs.py)")
|
||||
|
||||
|
||||
def _list_emulators(emulators_dir: str) -> None:
|
||||
"""Print available emulator profiles."""
|
||||
profiles = load_emulator_profiles(emulators_dir)
|
||||
for name in sorted(profiles):
|
||||
p = profiles[name]
|
||||
if p.get("type") in ("alias", "test"):
|
||||
continue
|
||||
display = p.get("emulator", name)
|
||||
ptype = p.get("type", "libretro")
|
||||
systems = ", ".join(p.get("systems", [])[:3])
|
||||
more = "..." if len(p.get("systems", [])) > 3 else ""
|
||||
print(f" {name:30s} {display:40s} [{ptype}] {systems}{more}")
|
||||
|
||||
|
||||
def _list_systems(emulators_dir: str) -> None:
|
||||
"""Print available system IDs with emulator count."""
|
||||
profiles = load_emulator_profiles(emulators_dir)
|
||||
system_emus: dict[str, list[str]] = {}
|
||||
for name, p in profiles.items():
|
||||
if p.get("type") in ("alias", "test", "launcher"):
|
||||
continue
|
||||
for sys_id in p.get("systems", []):
|
||||
system_emus.setdefault(sys_id, []).append(name)
|
||||
for sys_id in sorted(system_emus):
|
||||
count = len(system_emus[sys_id])
|
||||
print(f" {sys_id:35s} ({count} emulator{'s' if count > 1 else ''})")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Platform-native BIOS verification")
|
||||
parser.add_argument("--platform", "-p", help="Platform name")
|
||||
@@ -1021,10 +812,10 @@ def main():
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.list_emulators:
|
||||
_list_emulators(args.emulators_dir)
|
||||
list_emulator_profiles(args.emulators_dir)
|
||||
return
|
||||
if args.list_systems:
|
||||
_list_systems(args.emulators_dir)
|
||||
list_system_ids(args.emulators_dir)
|
||||
return
|
||||
|
||||
# Mutual exclusion
|
||||
|
||||
@@ -29,14 +29,15 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "scripts"))
|
||||
|
||||
import yaml
|
||||
from common import (
|
||||
build_zip_contents_index, check_inside_zip, group_identical_platforms,
|
||||
load_emulator_profiles, load_platform_config, md5_composite, md5sum,
|
||||
resolve_local_file, resolve_platform_cores,
|
||||
_build_validation_index, build_zip_contents_index, check_file_validation,
|
||||
check_inside_zip, compute_hashes, filter_files_by_mode,
|
||||
group_identical_platforms, load_emulator_profiles, load_platform_config,
|
||||
md5_composite, md5sum, parse_md5_list, resolve_local_file,
|
||||
resolve_platform_cores, safe_extract_zip,
|
||||
)
|
||||
from verify import (
|
||||
Severity, Status, verify_platform, find_undeclared_files, find_exclusion_notes,
|
||||
_build_validation_index, check_file_validation, verify_emulator,
|
||||
_filter_files_by_mode, _effective_validation_label,
|
||||
verify_emulator, _effective_validation_label,
|
||||
)
|
||||
|
||||
|
||||
@@ -58,6 +59,10 @@ class TestE2E(unittest.TestCase):
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
def setUp(self):
|
||||
# Clear emulator profile cache to avoid stale data between tests
|
||||
from common import _emulator_profiles_cache
|
||||
_emulator_profiles_cache.clear()
|
||||
|
||||
self.root = tempfile.mkdtemp()
|
||||
self.bios_dir = os.path.join(self.root, "bios")
|
||||
self.platforms_dir = os.path.join(self.root, "platforms")
|
||||
@@ -967,16 +972,16 @@ class TestE2E(unittest.TestCase):
|
||||
# test_validation has crc32, md5, sha1, size → all listed
|
||||
self.assertEqual(result["verification_mode"], "crc32+md5+sha1+signature+size")
|
||||
|
||||
def test_99_filter_files_by_mode(self):
|
||||
"""_filter_files_by_mode correctly filters standalone/libretro."""
|
||||
def test_99filter_files_by_mode(self):
|
||||
"""filter_files_by_mode correctly filters standalone/libretro."""
|
||||
files = [
|
||||
{"name": "a.bin"}, # no mode → both
|
||||
{"name": "b.bin", "mode": "libretro"}, # libretro only
|
||||
{"name": "c.bin", "mode": "standalone"}, # standalone only
|
||||
{"name": "d.bin", "mode": "both"}, # explicit both
|
||||
]
|
||||
lr = _filter_files_by_mode(files, standalone=False)
|
||||
sa = _filter_files_by_mode(files, standalone=True)
|
||||
lr = filter_files_by_mode(files, standalone=False)
|
||||
sa = filter_files_by_mode(files, standalone=True)
|
||||
lr_names = {f["name"] for f in lr}
|
||||
sa_names = {f["name"] for f in sa}
|
||||
self.assertEqual(lr_names, {"a.bin", "b.bin", "d.bin"})
|
||||
@@ -1012,5 +1017,86 @@ class TestE2E(unittest.TestCase):
|
||||
self.assertGreater(result["severity_counts"][Severity.WARNING], 0)
|
||||
|
||||
|
||||
def test_102_safe_extract_zip_blocks_traversal(self):
|
||||
"""safe_extract_zip must reject zip-slip path traversal."""
|
||||
malicious_zip = os.path.join(self.root, "evil.zip")
|
||||
with zipfile.ZipFile(malicious_zip, "w") as zf:
|
||||
zf.writestr("../../etc/passwd", "root:x:0:0")
|
||||
dest = os.path.join(self.root, "extract_dest")
|
||||
os.makedirs(dest)
|
||||
with self.assertRaises(ValueError):
|
||||
safe_extract_zip(malicious_zip, dest)
|
||||
|
||||
def test_103_safe_extract_zip_normal(self):
|
||||
"""safe_extract_zip extracts valid files correctly."""
|
||||
normal_zip = os.path.join(self.root, "normal.zip")
|
||||
with zipfile.ZipFile(normal_zip, "w") as zf:
|
||||
zf.writestr("subdir/file.txt", "hello")
|
||||
dest = os.path.join(self.root, "extract_normal")
|
||||
os.makedirs(dest)
|
||||
safe_extract_zip(normal_zip, dest)
|
||||
extracted = os.path.join(dest, "subdir", "file.txt")
|
||||
self.assertTrue(os.path.exists(extracted))
|
||||
with open(extracted) as f:
|
||||
self.assertEqual(f.read(), "hello")
|
||||
|
||||
def test_104_compute_hashes_correctness(self):
|
||||
"""compute_hashes returns correct values for known content."""
|
||||
test_file = os.path.join(self.root, "hash_test.bin")
|
||||
data = b"retrobios test content"
|
||||
with open(test_file, "wb") as f:
|
||||
f.write(data)
|
||||
import zlib
|
||||
expected_sha1 = hashlib.sha1(data).hexdigest()
|
||||
expected_md5 = hashlib.md5(data).hexdigest()
|
||||
expected_sha256 = hashlib.sha256(data).hexdigest()
|
||||
expected_crc32 = format(zlib.crc32(data) & 0xFFFFFFFF, "08x")
|
||||
|
||||
result = compute_hashes(test_file)
|
||||
self.assertEqual(result["sha1"], expected_sha1)
|
||||
self.assertEqual(result["md5"], expected_md5)
|
||||
self.assertEqual(result["sha256"], expected_sha256)
|
||||
self.assertEqual(result["crc32"], expected_crc32)
|
||||
|
||||
def test_105_resolve_with_empty_database(self):
|
||||
"""resolve_local_file handles empty database gracefully."""
|
||||
empty_db = {"files": {}, "indexes": {"by_md5": {}, "by_name": {}, "by_path_suffix": {}}}
|
||||
entry = {"name": "nonexistent.bin", "sha1": "abc123"}
|
||||
path, status = resolve_local_file(entry, empty_db)
|
||||
self.assertIsNone(path)
|
||||
self.assertEqual(status, "not_found")
|
||||
|
||||
def test_106_parse_md5_list(self):
|
||||
"""parse_md5_list normalizes comma-separated MD5s."""
|
||||
self.assertEqual(parse_md5_list(""), [])
|
||||
self.assertEqual(parse_md5_list("ABC123"), ["abc123"])
|
||||
self.assertEqual(parse_md5_list("abc, DEF , ghi"), ["abc", "def", "ghi"])
|
||||
self.assertEqual(parse_md5_list(",,,"), [])
|
||||
|
||||
def test_107filter_files_by_mode(self):
|
||||
"""filter_files_by_mode filters standalone/libretro correctly."""
|
||||
files = [
|
||||
{"name": "a.bin", "mode": "standalone"},
|
||||
{"name": "b.bin", "mode": "libretro"},
|
||||
{"name": "c.bin", "mode": "both"},
|
||||
{"name": "d.bin"}, # no mode
|
||||
]
|
||||
# Libretro mode: exclude standalone
|
||||
result = filter_files_by_mode(files, standalone=False)
|
||||
names = [f["name"] for f in result]
|
||||
self.assertNotIn("a.bin", names)
|
||||
self.assertIn("b.bin", names)
|
||||
self.assertIn("c.bin", names)
|
||||
self.assertIn("d.bin", names)
|
||||
|
||||
# Standalone mode: exclude libretro
|
||||
result = filter_files_by_mode(files, standalone=True)
|
||||
names = [f["name"] for f in result]
|
||||
self.assertIn("a.bin", names)
|
||||
self.assertNotIn("b.bin", names)
|
||||
self.assertIn("c.bin", names)
|
||||
self.assertIn("d.bin", names)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user