chore: merge board rule and installable cores

This commit is contained in:
Abdessamad Derraz committed 2026-11-09 21:54:24 +01:00
commit ceee9ffbe7
27 files changed
+2517 -838

No files matched your search

File diff suppressed because it is too large. Load diff
+164
View File
@@ -321,26 +321,43 @@
},
"atm7039": {
"cores": [
"2048",
"81",
"a5200",
"abuse",
"arduous",
"atari800",
"bennugd",
"bk",
"bluemsx",
"bsnes_cplusplus98",
"bsnes_mercury_accuracy",
"bsnes_mercury_balanced",
"bsnes_mercury_performance",
"bstone",
"cannonball",
"cap32",
"cdogs",
"cgenius",
"chailove",
"crocods",
"devilutionx",
"dinothawr",
"dosbox_core",
"dosbox_pure",
"dosbox_staging",
"dosbox_svn",
"dosbox_svn_ce",
"easyrpg",
"ecwolf",
"emuscv",
"fake08",
"fallout1-ce",
"fallout2-ce",
"fbalpha2012",
"fbalpha2012_cps1",
"fbalpha2012_cps2",
"fbalpha2012_neogeo",
"fbneo",
"fceumm",
"flycast",
@@ -349,37 +366,56 @@
"freeintv",
"fuse",
"gambatte",
"gearboy",
"gearsystem",
"genesis_plus_gx",
"genesisplusgx",
"geolith",
"gme",
"gpsp",
"gsplus",
"gw",
"handy",
"hatari",
"hcl",
"higan_sfc",
"higan_sfc_balanced",
"holani",
"hurrican",
"ioquake3",
"lowresnx",
"lutro",
"mame",
"mame0139",
"mame078plus",
"mame2000",
"mame2003",
"mame2003_plus",
"mednafen_gba",
"mednafen_lynx",
"mednafen_ngp",
"mednafen_pce",
"mednafen_pce_fast",
"mednafen_psx",
"mednafen_snes",
"mednafen_supergrafx",
"mednafen_vb",
"mednafen_wswan",
"mesen",
"mgba",
"minivmac",
"moonlight",
"mrboom",
"mu",
"mupen64plus",
"mupen64plus-next",
"nekop2",
"neocd",
"nestopia",
"np2kai",
"nxengine",
"o2em",
"oberon",
"openbor",
"openbor4432",
"openbor6412",
@@ -392,6 +428,7 @@
"pcfx",
"pcsx_rearmed",
"picodrive",
"pocketcdg",
"pokemini",
"potator",
"ppsspp",
@@ -402,22 +439,34 @@
"px68k",
"pygame",
"quasi88",
"quicknes",
"reminiscence",
"remotejoy",
"samcoupe",
"sameboy",
"sameduck",
"scummvm",
"sdlpop",
"smsplus",
"snes9x",
"snes9x2002",
"snes9x2005",
"snes9x2005_plus",
"snes9x2010",
"snes9x_next",
"solarus",
"sonic2013",
"soniccd",
"squirreljme",
"stella",
"stella2014",
"superbroswar",
"swanstation",
"taradino",
"test",
"tgbdual",
"theodore",
"thepowdertoy",
"tic80",
"tyrian",
"tyrquake",
@@ -425,15 +474,21 @@
"uzem",
"vb",
"vba-m",
"vba_next",
"vbam",
"vecx",
"vemulator",
"vice",
"vice_x128",
"vice_x64",
"vice_x64sc",
"vice_xcbm2",
"vice_xcbm5x0",
"vice_xpet",
"vice_xplus4",
"vice_xscpu64",
"vice_xvic",
"virtualjaguar",
"x1",
"x128",
"x16emu",
@@ -620,6 +675,7 @@
},
"r16": {
"cores": [
"2048",
"81",
"a5200",
"abuse",
@@ -627,20 +683,36 @@
"arduous",
"atari800",
"bennugd",
"bk",
"bluemsx",
"bsnes_cplusplus98",
"bsnes_mercury_accuracy",
"bsnes_mercury_balanced",
"bsnes_mercury_performance",
"bstone",
"cannonball",
"cap32",
"cdogs",
"cgenius",
"chailove",
"crocods",
"devilutionx",
"dinothawr",
"dosbox_core",
"dosbox_pure",
"dosbox_staging",
"dosbox_svn",
"dosbox_svn_ce",
"easyrpg",
"ecwolf",
"emuscv",
"fake08",
"fallout1-ce",
"fallout2-ce",
"fbalpha2012",
"fbalpha2012_cps1",
"fbalpha2012_cps2",
"fbalpha2012_neogeo",
"fbneo",
"fceumm",
"flycast",
@@ -649,37 +721,56 @@
"freeintv",
"fuse",
"gambatte",
"gearboy",
"gearsystem",
"genesis_plus_gx",
"genesisplusgx",
"geolith",
"gme",
"gpsp",
"gsplus",
"gw",
"handy",
"hatari",
"hcl",
"higan_sfc",
"higan_sfc_balanced",
"holani",
"hurrican",
"ioquake3",
"lowresnx",
"lutro",
"mame",
"mame0139",
"mame078plus",
"mame2000",
"mame2003",
"mame2003_plus",
"mednafen_gba",
"mednafen_lynx",
"mednafen_ngp",
"mednafen_pce",
"mednafen_pce_fast",
"mednafen_psx",
"mednafen_snes",
"mednafen_supergrafx",
"mednafen_vb",
"mednafen_wswan",
"mesen",
"mgba",
"minivmac",
"moonlight",
"mrboom",
"mu",
"mupen64plus",
"mupen64plus-next",
"nekop2",
"neocd",
"nestopia",
"np2kai",
"nxengine",
"o2em",
"oberon",
"openbor",
"openbor4432",
"openbor6412",
@@ -692,6 +783,7 @@
"pcfx",
"pcsx_rearmed",
"picodrive",
"pocketcdg",
"pokemini",
"potator",
"ppsspp",
@@ -702,22 +794,34 @@
"px68k",
"pygame",
"quasi88",
"quicknes",
"reminiscence",
"remotejoy",
"samcoupe",
"sameboy",
"sameduck",
"scummvm",
"sdlpop",
"smsplus",
"snes9x",
"snes9x2002",
"snes9x2005",
"snes9x2005_plus",
"snes9x2010",
"snes9x_next",
"solarus",
"sonic2013",
"soniccd",
"squirreljme",
"stella",
"stella2014",
"superbroswar",
"swanstation",
"taradino",
"test",
"tgbdual",
"theodore",
"thepowdertoy",
"tic80",
"tyrian",
"tyrquake",
@@ -725,15 +829,21 @@
"uzem",
"vb",
"vba-m",
"vba_next",
"vbam",
"vecx",
"vemulator",
"vice",
"vice_x128",
"vice_x64",
"vice_x64sc",
"vice_xcbm2",
"vice_xcbm5x0",
"vice_xpet",
"vice_xplus4",
"vice_xscpu64",
"vice_xvic",
"virtualjaguar",
"x1",
"x128",
"x16emu",
@@ -752,6 +862,7 @@
},
"rk3128": {
"cores": [
"2048",
"81",
"a5200",
"abuse",
@@ -759,21 +870,37 @@
"arduous",
"atari800",
"bennugd",
"bk",
"bluemsx",
"bsnes_cplusplus98",
"bsnes_mercury_accuracy",
"bsnes_mercury_balanced",
"bsnes_mercury_performance",
"bstone",
"cannonball",
"cap32",
"cdogs",
"cgenius",
"chailove",
"crocods",
"devilutionx",
"dinothawr",
"dosbox_core",
"dosbox_pure",
"dosbox_staging",
"dosbox_svn",
"dosbox_svn_ce",
"easyrpg",
"ecwolf",
"emuscv",
"fake08",
"fallout1-ce",
"fallout2-ce",
"fbalpha",
"fbalpha2012",
"fbalpha2012_cps1",
"fbalpha2012_cps2",
"fbalpha2012_neogeo",
"fbneo",
"fceumm",
"flycast",
@@ -782,37 +909,55 @@
"freeintv",
"fuse",
"gambatte",
"gearboy",
"gearsystem",
"genesis_plus_gx",
"genesisplusgx",
"geolith",
"gme",
"gpsp",
"gsplus",
"gw",
"handy",
"hatari",
"hcl",
"higan_sfc",
"higan_sfc_balanced",
"holani",
"hurrican",
"lightspark",
"lowresnx",
"lutro",
"mame",
"mame0139",
"mame078plus",
"mame2000",
"mame2003",
"mame2003_plus",
"mednafen_gba",
"mednafen_lynx",
"mednafen_ngp",
"mednafen_pce",
"mednafen_pce_fast",
"mednafen_psx",
"mednafen_snes",
"mednafen_supergrafx",
"mednafen_vb",
"mednafen_wswan",
"mesen",
"mgba",
"minivmac",
"mrboom",
"mu",
"mupen64plus",
"mupen64plus-next",
"nekop2",
"neocd",
"nestopia",
"np2kai",
"nxengine",
"o2em",
"oberon",
"openbor",
"openbor4432",
"openbor6412",
@@ -825,6 +970,7 @@
"pcfx",
"pcsx_rearmed",
"picodrive",
"pocketcdg",
"pokemini",
"potator",
"ppsspp",
@@ -835,22 +981,34 @@
"px68k",
"pygame",
"quasi88",
"quicknes",
"reminiscence",
"remotejoy",
"samcoupe",
"sameboy",
"sameduck",
"scummvm",
"sdlpop",
"smsplus",
"snes9x",
"snes9x2002",
"snes9x2005",
"snes9x2005_plus",
"snes9x2010",
"snes9x_next",
"solarus",
"sonic2013",
"soniccd",
"squirreljme",
"stella",
"stella2014",
"superbroswar",
"swanstation",
"taradino",
"test",
"tgbdual",
"theodore",
"thepowdertoy",
"tic80",
"tyrian",
"tyrquake",
@@ -858,15 +1016,21 @@
"uzem",
"vb",
"vba-m",
"vba_next",
"vbam",
"vecx",
"vemulator",
"vice",
"vice_x128",
"vice_x64",
"vice_x64sc",
"vice_xcbm2",
"vice_xcbm5x0",
"vice_xpet",
"vice_xplus4",
"vice_xscpu64",
"vice_xvic",
"virtualjaguar",
"x1",
"x128",
"x16emu",
File diff suppressed because it is too large. Load diff
+151 -24
View File
@@ -10,7 +10,6 @@ cores:
- a5200
- abuse
- amiberry
- applewin
- arduous
- atari800
- azahar
@@ -30,7 +29,6 @@ cores:
- cdogs
- cemu
- cgenius
- citron
- clk
- corsixth
- demul
@@ -50,7 +48,6 @@ cores:
- easyrpg
- ecwolf
- eduke32
- eka2l1
- emuscv
- ep128emu-core
- etlegacy
@@ -66,7 +63,6 @@ cores:
- fmsx
- freechaf
- freeintv
- freej2me
- fsuae
- fury
- fuse
@@ -76,7 +72,6 @@ cores:
- genesisplusgx
- genesisplusgx-expanded
- genesisplusgx-wide
- gong
- gpsp
- gsplus
- gw
@@ -158,7 +153,6 @@ cores:
- reminiscence
- rpcs3
- ruffle
- ryujinx
- samcoupe
- same_cdi
- sameduck
@@ -173,7 +167,6 @@ cores:
- sonic2013
- sonic3-air
- soniccd
- squirreljme
- stella
- stella2014
- superbroswar
@@ -192,12 +185,10 @@ cores:
- tyrian
- tyrquake
- uae4arm
- uqm
- uzem
- vb
- vba-m
- vecx
- vemulator
- vice
- vice_x128
- vice_x64
@@ -5683,7 +5674,6 @@ systems:
standalone_cores:
- abuse
- amiberry
- applewin
- azahar
- bigpemu
- bstone
@@ -5692,7 +5682,6 @@ standalone_cores:
- cdogs
- cemu
- cgenius
- citron
- clk
- corsixth
- demul
@@ -5709,7 +5698,6 @@ standalone_cores:
- easyrpg
- ecwolf
- eduke32
- eka2l1
- etlegacy
- fallout1-ce
- fallout2-ce
@@ -5755,7 +5743,6 @@ standalone_cores:
- redream
- rpcs3
- ruffle
- ryujinx
- samcoupe
- scummvm
- sdlpop
@@ -5776,7 +5763,6 @@ standalone_cores:
- tr2x
- tsugaru
- tyrian
- uqm
- vice
- vita3k
- vkquake
@@ -5800,7 +5786,6 @@ standalone_cores:
- ymir
- yquake2
dual_mode_cores:
- applewin
- dolphin
- easyrpg
- flycast
@@ -5813,6 +5798,157 @@ dual_mode_cores:
- scummvm
- stella
- tic80
installable_cores:
- "2048"
- 3dengine
- DoubleCherryGB
- amiarcadia
- anarch
- applewin
- ardens
- b2
- bbkemu
- boom3_xp
- bsnes-jg
- bsnes2014_accuracy
- bsnes2014_balanced
- bsnes2014_performance
- bsnes_cplusplus98
- bsnes_hd_beta
- bsnes_mercury_accuracy
- bsnes_mercury_balanced
- bsnes_mercury_performance
- cdi2015
- chailove
- citra
- citra2018
- clownmdemu
- coolbaremu
- craft
- crocods
- desmume2015
- dingooemu
- dinothawr
- dirksimple
- dosbox_core
- dosbox_svn
- dosbox_svn_ce
- doukutsu_rs
- ep128emu_core
- fbalpha2012
- fbalpha2012_cps1
- fbalpha2012_cps2
- fbalpha2012_cps3
- fbalpha2012_neogeo
- fixgb
- fixnes
- frodo
- galaksija
- gam4980
- gametank
- gearboy
- geargrafx
- gearlynx
- geartowns
- genesis_plus_gx
- genesis_plus_gx_wide
- geolith
- gme
- gong
- hatari2014
- hbmame
- higan_sfc
- higan_sfc_balanced
- hyperscanemu
- hypseushw
- irogb
- jaxe
- jollycv
- jumpnbump
- linuxloader
- m2000
- mame2000
- mame2003
- mame2003_midway
- mame2003_plus
- mame2010
- mame2015
- mame2016
- mcsoftserve
- mednafen_gba
- mednafen_pce
- mednafen_pce_fast
- mednafen_pcfx
- mednafen_psx_hw
- mednafen_saturn
- mednafen_snes
- mednafen_supafaust
- mednafen_vb
- mesen2
- meteor
- mojozork
- mu
- mupen64plus_next
- n64emu
- native32emu
- nekop2
- nicaiemu
- noods
- nside_sfc_balanced
- nuance
- numero
- oberon
- odin
- omicron
- openlara
- panda3ds
- parallel_dc
- pcee2
- pcem
- piece
- playdiaemu
- pocketcdg
- pokket
- pokketstation
- qemu
- quicknes
- race
- radio
- remotejoy
- retro8
- romcleaner
- rust_dos
- rustynes
- sameboy
- skyemu
- snes9x2002
- snes9x2005
- snes9x2005_plus
- snes9x2010
- spmp8000emu
- squirreljme
- stella2023
- tamalibretro
- test
- thepowdertoy
- tia
- uw8
- uzdoom
- vaporspec
- vba_next
- vbam
- vemulator
- vice_xcbm2
- vice_xcbm5x0
- video_processor
- virtualxt
- vitaquake2-rogue
- vitaquake2-xatrix
- vitaquake2-zaero
- vitaquake3
- wqxemu
- xenia_edge
- yabause
libretro_systems:
"81":
- zx81
@@ -6624,15 +6760,6 @@ launchers:
fails_when_missing: false
source_ref: "package/batocera/core/batocera-configgen/configgen/configgen/generators/shadps4/shadps4Generator.py:46,\
\ 228"
- emulator: citron
build: standalone
kind: passes_path
names: []
search_dir: /userdata/bios/switch
passes_to: "CitronPath::KeysDir in src/common/fs/path_util.cpp returns it, in place\
\ of citron_path / KEYS_DIR"
fails_when_missing: false
source_ref: "package/batocera/emulators/citron/002-adjust-paths.patch:9-10"
configs_root:
path: /userdata/system/configs
from_bios: ../system/configs
-69
View File
@@ -11,56 +11,38 @@ cores:
- "81"
- AMIBERRY
- AdvanceMame
- CGeniusExe
- Chocolate-Doom
- DOSBOXSDL2
- Duckstation
- ECWolf
- FBNEOSA
- GZDoom
- HATARISA
- HYPSEUS
- LZDoom
- OPENBOR
- OpenJazz
- PPSSPPSDL
- Pico-8
- RigelEngine
- SCUMMVMSA
- STELLASA
- Solarus
- VVVVVV
- a5200
- abuse
- atari800
- bgdi
- bk
- bluemsx
- bs
- bstone
- cannonball
- cap32
- ccleste
- cdogs-sdl
- daphne
- devilutionx
- dinothawr
- dolphin
- dosbox_pure
- dosbox_svn
- easyrpg
- eduke32
- fake08
- fallout-ce
- fallout2-ce
- fbalpha2012
- fbneo
- fceumm
- fceumm_mod
- fheroes2
- flycast
- flycast_32b
- flycast_dojo
- flycastsa
- fmsx
- freeintv
@@ -80,9 +62,6 @@ cores:
- gw
- handy
- hatari
- hcl
- hode
- hurrican
- ikemen
- jzintv
- mame
@@ -111,7 +90,6 @@ cores:
- np2kai
- nxengine
- o2em
- opentyrian
- opera
- parallel_n64_32b
- pcsx_rearmed
@@ -121,7 +99,6 @@ cores:
- pokemini
- potator
- prboom
- prince
- prosystem
- puae
- puae2021
@@ -131,17 +108,11 @@ cores:
- sameboy
- sameduck
- scummvm
- smw
- snes9x
- snes9x2002
- snes9x2005_plus
- snes9x2010
- sonic2013
- soniccd
- sonicmania
- stella
- supertux2
- supertuxkart
- swanstation
- tgbdual
- tic80
@@ -158,60 +129,29 @@ cores:
- vice_xvic
- virtualjaguar
- x1
- xash3d
- xrick
- yabasanshiro
standalone_cores:
- AMIBERRY
- AdvanceMame
- CGeniusExe
- Chocolate-Doom
- DOSBOXSDL2
- Duckstation
- ECWolf
- FBNEOSA
- GZDoom
- HATARISA
- HYPSEUS
- LZDoom
- OPENBOR
- OpenJazz
- PPSSPPSDL
- Pico-8
- RigelEngine
- SCUMMVMSA
- STELLASA
- Solarus
- VVVVVV
- abuse
- bgdi
- bs
- bstone
- ccleste
- cdogs-sdl
- devilutionx
- dolphin
- eduke32
- fallout-ce
- fallout2-ce
- fheroes2
- flycast_dojo
- flycastsa
- hcl
- hode
- hurrican
- ikemen
- jzintv
- mupen64plussa
- opentyrian
- prince
- smw
- sonic2013
- soniccd
- sonicmania
- supertux2
- supertuxkart
- xash3d
- yabasanshiro
dual_mode_cores:
- FBNEOSA
@@ -4412,15 +4352,6 @@ launchers:
fails_when_missing: false
source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:198-200, packages/sx05re/emulators/flycastsa/scripts/flycast.sh:11,\
\ 14"
- emulator: flycast_dojo
build: standalone
kind: links
names: []
search_dir: /storage/roms/bios/dc_dojo
passes_to: symlink /storage/.local/share/flycast-dojo -> /storage/roms/bios/dc_dojo
fails_when_missing: false
source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:201-203, packages/sx05re/emulators/flycast-dojo/scripts/flycastdojo.sh:11-12,\
\ 28-30"
- emulator: amiberry
build: standalone
kind: links
+51 -323
View File
@@ -12,55 +12,36 @@ cores:
- advanced_drastic
- aethersx2
- amiberry
- applewin
- arduous
- atari800
- azahar
- beetle-saturn
- bennugd
- bigpemu
- blastem
- bluemsx
- boom3
- bsnes
- bsnes_hd
- bstone
- cannonball
- cap32
- catacombgl
- cdogs
- cemu
- cgenius
- citra
- citron
- clk
- corsixth
- demul
- desmume
- devilutionx
- dhewm3
- dice
- dolphin
- dolphin_triforce
- dosbox
- dosbox-x
- dosbox_pure
- dosbox_staging
- dosboxx
- drastic
- duckstation
- duckstation-legacy
- dxx-rebirth
- easyrpg
- ecwolf
- eduke32
- eka2l1
- emuscv
- etlegacy
- fake08
- fallout1-ce
- fallout2-ce
- fba2x
- fbalpha
- fbneo
- fceumm
@@ -68,20 +49,15 @@ cores:
- flycast-xtreme
- flycastvl
- fmsx
- fpinball
- freechaf
- freeintv
- freej2me
- fsuae
- fury
- fuse
- gambatte
- gearsystem
- genesisplusgx
- genesisplusgx-expanded
- genesisplusgx-wide
- geolith
- gong
- gpsp
- gsplus
- gw
@@ -92,15 +68,9 @@ cores:
- hcl
- holani
- hurrican
- hypseus-singe
- ikemen
- imame4all
- ioquake3
- iortcw
- jazz2-native
- kronos
- lightspark
- lindbergh-loader
- lowresnx
- lutro
- mame
@@ -117,7 +87,6 @@ cores:
- mesen-s
- mgba
- minivmac
- model2emu
- moonlight
- mpv
- mrboom
@@ -134,21 +103,14 @@ cores:
- openbor7142
- openbor7530
- openjazz
- openjk
- openjkdf2
- openlara
- openmohaa
- openmsx
- opera
- parallel_n64
- pce
- pce_fast
- pcfx
- pcsx2
- pcsx_rearmed
- picodrive
- play
- pocketsnes
- pokemini
- potator
- ppsspp
@@ -158,20 +120,15 @@ cores:
- puae2021
- px68k
- pygame
- pyxel
- quasi88
- raze
- redream
- reminiscence
- rpcs3
- ruffle
- ryujinx
- samcoupe
- same_cdi
- sameduck
- scummvm
- sdlpop
- shadps4
- smsplus
- snes9x
- snes9x_next
@@ -180,31 +137,21 @@ cores:
- sonic2013
- sonic3-air
- soniccd
- squirreljme
- stella
- stella2014
- superbroswar
- supermodel
- supermodel-legacy
- swanstation
- taradino
- tgbdual
- theforceengine
- theodore
- thextech
- tic80
- tr1x
- tr2x
- tsugaru
- tyrian
- tyrquake
- uae4arm
- uqm
- uzem
- vb
- vba-m
- vecx
- vemulator
- vice
- vice_x128
- vice_x64
@@ -221,24 +168,18 @@ cores:
- vkquake2
- vkquake3
- vox_official
- vpinball
- wasm4
- x1
- x128
- x16emu
- x64
- x64dtv
- xash3d_fwgs
- xemu
- xenia
- xenia-canary
- xpet
- xplus4
- xrick
- xscpu64
- xvic
- yabasanshiro
- ymir
- zc210
systems:
atari-400-800:
@@ -4879,61 +4820,39 @@ standalone_cores:
- advanced_drastic
- aethersx2
- amiberry
- applewin
- azahar
- bigpemu
- bstone
- cannonball
- catacombgl
- cdogs
- cemu
- cgenius
- citra
- citron
- clk
- corsixth
- demul
- devilutionx
- dhewm3
- dolphin
- dolphin_triforce
- dosbox
- dosbox-x
- dosbox_staging
- dosboxx
- drastic
- duckstation
- duckstation-legacy
- dxx-rebirth
- easyrpg
- ecwolf
- eduke32
- eka2l1
- etlegacy
- fallout1-ce
- fallout2-ce
- fba2x
- flycast
- fpinball
- fsuae
- fury
- gsplus
- gzdoom
- hatari
- hcl
- hurrican
- hypseus-singe
- ikemen
- ioquake3
- iortcw
- jazz2-native
- kodi
- lexaloffle
- lightspark
- lindbergh-loader
- mame
- melonds
- model2emu
- moonlight
- mpv
- mupen64plus
@@ -4943,76 +4862,102 @@ standalone_cores:
- openbor7142
- openbor7530
- openjazz
- openjk
- openjkdf2
- openmohaa
- openmsx
- pcsx2
- play
- ppsspp
- pygame
- pyxel
- raze
- redream
- rpcs3
- ruffle
- ryujinx
- samcoupe
- scummvm
- sdlpop
- shadps4
- solarus
- sonic-mania
- sonic2013
- sonic3-air
- soniccd
- stella
- supermodel
- supermodel-legacy
- taradino
- theforceengine
- thextech
- tr1x
- tr2x
- tsugaru
- tyrian
- uqm
- vice
- vita3k
- vkquake
- vkquake2
- vkquake3
- vox_official
- vpinball
- x128
- x16emu
- x64
- x64dtv
- xash3d_fwgs
- xemu
- xenia
- xenia-canary
- xpet
- xplus4
- xscpu64
- xvic
- yabasanshiro
- ymir
dual_mode_cores:
- applewin
- citra
- dolphin
- easyrpg
- flycast
- hatari
- mame
- melonds
- pcsx2
- play
- ppsspp
- scummvm
- stella
- yabasanshiro
installable_cores:
- "2048"
- bk
- bsnes_cplusplus98
- bsnes_mercury_accuracy
- bsnes_mercury_balanced
- bsnes_mercury_performance
- chailove
- crocods
- dinothawr
- dosbox_core
- dosbox_svn
- dosbox_svn_ce
- ecwolf
- fbalpha2012
- fbalpha2012_cps1
- fbalpha2012_cps2
- fbalpha2012_neogeo
- gearboy
- genesis_plus_gx
- gme
- higan_sfc
- higan_sfc_balanced
- mame2000
- mame2003
- mame2003_plus
- mednafen_gba
- mednafen_pce
- mednafen_pce_fast
- mednafen_snes
- mednafen_vb
- mu
- nekop2
- oberon
- pocketcdg
- quicknes
- remotejoy
- sameboy
- snes9x2002
- snes9x2005
- snes9x2005_plus
- snes9x2010
- squirreljme
- stella2014
- test
- thepowdertoy
- vba_next
- vbam
- vemulator
- vice_xcbm2
- vice_xcbm5x0
libretro_systems:
"81":
- zx81
@@ -5393,129 +5338,6 @@ libretro_systems:
zc210:
- zc210
launchers:
- emulator: duckstation
build: standalone
kind: names_searched
slot: NTSCU
names:
- scph101.bin
- scph1001.bin
- scph5501.bin
- scph7001.bin
- scph7501.bin
case_insensitive: true
search_dir: /userdata/bios
passes_to: "[BIOS] PathNTSCU"
fails_when_missing: No PSX1 BIOS found
fails_scope: emulator
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/duckstation/duckstationGenerator.py:119-143,\
\ 589-611"
- emulator: duckstation
build: standalone
kind: names_searched
slot: PAL
names:
- scph1002.bin
- scph5502.bin
- scph5552.bin
- scph7002.bin
- scph7502.bin
- scph9002.bin
- scph102a.bin
- scph102b.bin
case_insensitive: true
search_dir: /userdata/bios
passes_to: "[BIOS] PathPAL"
fails_when_missing: No PSX1 BIOS found
fails_scope: emulator
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/duckstation/duckstationGenerator.py:119-143,\
\ 589-611"
- emulator: duckstation
build: standalone
kind: names_searched
slot: NTSCJ
names:
- scph100.bin
- scph1000.bin
- scph3000.bin
- scph3500.bin
- scph5500.bin
- scph7000.bin
- scph7003.bin
case_insensitive: true
search_dir: /userdata/bios
passes_to: "[BIOS] PathNTSCJ"
fails_when_missing: No PSX1 BIOS found
fails_scope: emulator
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/duckstation/duckstationGenerator.py:119-143,\
\ 589-611"
- emulator: duckstation
build: standalone
kind: names_searched
slot: Uni
names:
- psxonpsp660.bin
- ps1_rom.bin
case_insensitive: true
search_dir: /userdata/bios
passes_to: "[BIOS] PathNTSCU, PathPAL, PathNTSCJ"
fails_when_missing: No PSX1 BIOS found
fails_scope: emulator
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/duckstation/duckstationGenerator.py:119-143,\
\ 589-611"
- emulator: duckstation-legacy
build: standalone
kind: names_searched
slot: NTSCU
names:
- scph101.bin
- scph1001.bin
- scph5501.bin
- scph7001.bin
- scph7501.bin
case_insensitive: false
search_dir: /userdata/bios
passes_to: "[BIOS] PathNTSCU"
fails_when_missing: No PSX1 BIOS found
fails_scope: emulator
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/duckstation_legacy/duckstationLegacyGenerator.py:116-153"
- emulator: duckstation-legacy
build: standalone
kind: names_searched
slot: PAL
names:
- scph1002.bin
- scph5502.bin
- scph5552.bin
- scph7002.bin
- scph7502.bin
- scph9002.bin
- scph102a.bin
- scph102b.bin
case_insensitive: false
search_dir: /userdata/bios
passes_to: "[BIOS] PathPAL"
fails_when_missing: No PSX1 BIOS found
fails_scope: emulator
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/duckstation_legacy/duckstationLegacyGenerator.py:116-153"
- emulator: duckstation-legacy
build: standalone
kind: names_searched
slot: NTSCJ
names:
- scph100.bin
- scph1000.bin
- scph3000.bin
- scph3500.bin
- scph5500.bin
- scph7000.bin
- scph7003.bin
case_insensitive: false
search_dir: /userdata/bios
passes_to: "[BIOS] PathNTSCJ"
fails_when_missing: No PSX1 BIOS found
fails_scope: emulator
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/duckstation_legacy/duckstationLegacyGenerator.py:116-153"
- emulator: hatari
build: standalone
kind: names_searched
@@ -5683,33 +5505,6 @@ launchers:
passes_to: environment FLYCAST_BIOS_PATH
fails_when_missing: false
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/flycast/flycastGenerator.py:239"
- emulator: shadps4
build: standalone
kind: passes_path
names: []
search_dir: /userdata/system/configs/shadps4
passes_to: working directory of shadps4Generator.py
fails_when_missing: false
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/shadps4/shadps4Generator.py:33,\
\ 164"
- emulator: openmsx
build: standalone
kind: passes_path
names: []
search_dir: /userdata/bios/Machines
passes_to: "written: filepool add -path /userdata/bios/Machines -types system_rom\
\ -position 1"
fails_when_missing: false
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/openmsx/openmsxGenerator.py:95"
- emulator: openmsx
build: standalone
kind: passes_path
names: []
search_dir: /userdata/bios/openmsx
passes_to: "written: filepool add -path /userdata/bios/openmsx -types system_rom\
\ -position 2"
fails_when_missing: false
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/openmsx/openmsxGenerator.py:96"
- emulator: amiberry
build: standalone
kind: passes_path
@@ -5765,15 +5560,6 @@ launchers:
depends_on_option: neocd_bios
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/libretro/libretroOptions.py:2558-2559,\
\ 2566-2569"
- emulator: pcsx2
build: standalone
kind: passes_path
names: []
search_dir: /userdata/bios/ps2
passes_to: "inis/PCSX2.ini [Folders] Bios = ../../../bios/ps2, relative to /userdata/system/configs/PCSX2"
fails_when_missing: false
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/pcsx2/pcsx2Generator.py:92,\
\ 176, 213"
- emulator: aethersx2
build: standalone
kind: passes_path
@@ -5919,29 +5705,6 @@ launchers:
fails_when_missing: false
depends_on_option: "Rpcs3Generator.getFirmwareVersion() is None"
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/rpcs3/rpcs3Generator.py:355-358"
- emulator: ryujinx
build: standalone
kind: copies
names:
- switch/prod.keys
case_insensitive: false
search_dir: /userdata/bios
passes_to: /userdata/system/configs/Ryujinx/system/prod.keys
fails_when_missing: false
depends_on_option: "ryujinxKeys.exists()"
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/ryujinx/ryujinxGenerator.py:107-109"
- emulator: pcsx2
build: standalone
kind: copies
names:
- patches.zip
case_insensitive: false
search_dir: /usr/share/knulli/datainit/bios/ps2
passes_to: /userdata/bios/ps2/patches.zip
fails_when_missing: false
depends_on_option: "not pcsx2Patches.exists()"
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/pcsx2/pcsx2Generator.py:78,\
\ 121-122"
- emulator: aethersx2
build: standalone
kind: copies
@@ -5962,14 +5725,6 @@ launchers:
passes_to: g_cfg_rom_path
fails_when_missing: false
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/gsplus/gsplusGenerator.py:106"
- emulator: tsugaru
build: standalone
kind: passes_path
names: []
search_dir: /userdata/bios/fmtowns
passes_to: command line argument 1
fails_when_missing: false
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/tsugaru/tsugaruGenerator.py:18-19"
- emulator: scummvm
build: standalone
kind: passes_path
@@ -5978,14 +5733,6 @@ launchers:
passes_to: command line --extrapath
fails_when_missing: false
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/scummvm/scummvmGenerator.py:117-123"
- emulator: fsuae
build: standalone
kind: passes_path
names: []
search_dir: /userdata/bios/amiga
passes_to: command line --kickstarts_dir
fails_when_missing: false
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/fsuae/fsuaeGenerator.py:63-69"
- emulator: mame
build: standalone
kind: passes_path
@@ -6039,16 +5786,6 @@ launchers:
passes_to: command line -crosshairpath
fails_when_missing: false
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/mame/mameGenerator.py:180"
- emulator: fpinball
build: standalone
kind: passes_path
names: []
search_dir: /userdata/bios
passes_to: environment W_CACHE
fails_when_missing: false
depends_on_option: "not wsh57_done.exists()"
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/fpinball/fpinballGenerator.py:37,\
\ 39-43"
- emulator: retroarch
build: libretro
kind: passes_path
@@ -6142,15 +5879,6 @@ launchers:
depends_on_option: not system.name == 'ti99'
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/libretro/libretroMAMEConfig.py:431-432,\
\ 435"
- emulator: citron
build: standalone
kind: passes_path
names: []
search_dir: /userdata/bios/switch
passes_to: "CitronPath::KeysDir in src/common/fs/path_util.cpp returns it, in place\
\ of citron_path / KEYS_DIR"
fails_when_missing: false
source_ref: "batocera/package/batocera/emulators/citron/002-adjust-paths.patch:9-10"
configs_root:
path: /userdata/system/configs
from_bios: ../system/configs
+126 -1
View File
@@ -6,66 +6,191 @@ base_destination: bios
hash_type: md5
verification_mode: md5
cores:
- retroarch
- "81"
- DoubleCherryGB
- a5200
- amiberry
- arduous
- ares
- ares-sa
- armsx2
- armsx2-sa
- atari800
- azahar
- azahar-sa
- b2
- beetle_gba
- beetle_lynx
- beetle_ngp
- beetle_pce
- beetle_pce_fast
- beetle_pcfx
- beetle_psx
- beetle_saturn
- beetle_supafaust
- beetle_supergrafx
- beetle_vb
- beetle_wswan
- bigpemu
- bigpemu-sa
- bk
- bluemsx
- boom3
- boom3_xp
- bsnes
- bsnes2014_accuracy
- bsnes2014_balanced
- bsnes2014_performance
- bsnes_hd_beta
- bsnes_mercury_accuracy
- bsnes_mercury_balanced
- bsnes_mercury_performance
- cap32
- cemu
- cemu-sa
- crocods
- daedalusx64
- daedalusx64-sa
- daphne
- desmume
- dolphin
- dolphin-sa
- dosbox_core
- dosbox_pure
- drastic
- drastic-sa
- dsperate
- dsperate-sa
- duckstation
- duckstation-sa
- easyrpg
- ecwolf
- emuscv
- fake08
- fbalpha2012
- fbalpha2019
- fbneo
- fceumm
- flycast
- flycast-sa
- flycast2021
- fmsx
- freeintv
- freej2me
- fuse
- gambatte
- gearboy
- gearcoleco
- geargrafx
- gearlynx
- gearsystem
- genesis_plus_gx
- genesis_plus_gx_wide
- geolith
- gopher64
- gopher64-sa
- gpsp
- gw
- gzdoom
- gzdoom-sa
- handy
- hatari
- hatarisa
- heroic
- hypseus-singe
- jaxe
- kronos
- mame
- mame2003_plus
- mame2010
- mame2015
- mednafen
- melonds
- melonds-sa
- melondsds
- mesen
- mesen-s
- mgba
- minivmac
- mojozork
- mu
- mupen64plus
- mupen64plus-sa
- mupen64plus_next
- nanoboyadvance
- nanoboyadvance-sa
- neocd
- nestopia
- np2kai
- o2em
- openbor
- opera
- parallel_n64
- pcsx_rearmed
- pcsx_rearmed32
- pico-8
- picodrive
- pokemini
- potator
- ppsspp
- ppsspp-sa
- prboom
- prosystem
- puae
- puae2021
- px68k
- quasi88
- quicknes
- race
- rpcs3
- rpcs3-sa
- same_cdi
- sameboy
- sameduck
- scummvm
- scummvm-sa
- skyemu
- skyemu-sa
- smsplus
- snes9x
- snes9x2002
- snes9x2005_plus
- snes9x2010
- steam
- stella
- supermodel
- supermodel-sa
- supersnes9x
- swanstation
- tgbdual
- theodore
- tic80
- touchhle
- touchhle-sa
- tyrquake
- uae4arm
- uzem
- vba_next
- vbam
- vecx
- vice
- vice-sa
- vice_x128
- vice_x64
- vice_xpet
- vice_xplus4
- vice_xvic
- vircon32
- virtualjaguar
- vita3k
- vita3k-sa
- vitaquake2
- vitaquake3
- wasm4
- x1
- xemu
- xemu-sa
- yabadabadoo
- yabasanshiro
- yabasanshiro-sa
standalone_cores:
+19 -1
View File
@@ -1,6 +1,6 @@
platform: batocera
source: https://github.com/batocera-linux/batocera.linux/tree/batocera-43.1
scraped_at: '2026-11-06T02:18:59Z'
scraped_at: '2026-11-09T20:38:15Z'
targets:
a3gen2:
architecture: aarch64
@@ -203,6 +203,7 @@ targets:
- dosbox_staging
- dosboxx
- duckstation
- dxx-rebirth
- easyrpg
- ecwolf
- eduke32
@@ -300,7 +301,9 @@ targets:
- snes9x_next
- solarus
- sonic-mania
- sonic2013
- sonic3-air
- soniccd
- stella
- superbroswar
- supermodel
@@ -342,6 +345,7 @@ targets:
- xpet
- xplus4
- xrick
- xroar
- xscpu64
- xvic
- yabasanshiro
@@ -786,6 +790,7 @@ targets:
- xvic
- yquake2
- zc210
downloads: linux-armhf
bcm2837:
architecture: aarch64
cores:
@@ -1071,6 +1076,7 @@ targets:
- orangepi-one
- orangepi-pc
- orangepi-plus2e
downloads: linux-armhf
h5:
architecture: aarch64
cores:
@@ -2177,6 +2183,7 @@ targets:
- xvic
- yquake2
- zc210
downloads: linux-armhf
qcs6490:
architecture: aarch64
cores:
@@ -2512,6 +2519,7 @@ targets:
- powkiddy_a13
- ps5000
- ps7000
downloads: linux-armhf
rk3288:
architecture: arm
cores:
@@ -2670,6 +2678,7 @@ targets:
images:
- miqi
- tinkerboard
downloads: linux-armhf
rk3326:
architecture: aarch64
cores:
@@ -3883,6 +3892,7 @@ targets:
- xvic
- yquake2
- zc210
downloads: linux-armhf
s905:
architecture: aarch64
cores:
@@ -5379,6 +5389,7 @@ targets:
- model2emu
- moonlight
- mrboom
- mugen
- mupen64plus
- mupen64plus-next
- neocd
@@ -5475,6 +5486,8 @@ targets:
- vkquake3
- vpinball
- wasm4
- wine
- wine-tkg
- x1
- x128
- x16emu
@@ -5494,6 +5507,7 @@ targets:
- ymir
- yquake2
- zc210
downloads: linux-x86_64
zen3:
architecture: x86_64
cores:
@@ -5605,6 +5619,7 @@ targets:
- model2emu
- moonlight
- mrboom
- mugen
- mupen64plus
- mupen64plus-next
- neocd
@@ -5701,6 +5716,8 @@ targets:
- vkquake3
- vpinball
- wasm4
- wine
- wine-tkg
- x1
- x128
- x16emu
@@ -5722,3 +5739,4 @@ targets:
- zc210
images:
- x86-64-v3
downloads: linux-x86_64
+4 -1
View File
@@ -1,6 +1,6 @@
platform: knulli
source: https://github.com/knulli-cfw/knulli-linux/tree/20260511
scraped_at: '2026-11-06T02:19:00Z'
scraped_at: '2026-11-09T20:38:23Z'
targets:
a133:
architecture: aarch64
@@ -472,6 +472,7 @@ targets:
- zc210
images:
- rg35xx
downloads: linux-armhf
h700:
architecture: aarch64
cores:
@@ -787,6 +788,7 @@ targets:
- zc210
images:
- miyoo-a30
downloads: linux-armhf
rk3128:
architecture: arm
cores:
@@ -930,6 +932,7 @@ targets:
- powkiddy_a13
- ps5000
- ps7000
downloads: linux-armhf
rk3326:
architecture: aarch64
cores:
File diff suppressed because it is too large. Load diff
+8
View File
@@ -45,6 +45,14 @@
},
"uniqueItems": true
},
"installable_cores": {
"description": "Buildbot libretro cores the platform's RetroArch core downloader installs on at least one board, from the RetroArch targets entry of that board's architecture.",
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"dual_mode_cores": {
"type": "array",
"items": {
+22 -9
View File
@@ -869,11 +869,24 @@ def load_target_config(
)
)
declared = (targets[canonical] or {}).get("cores")
return _target_core_set(targets[canonical] or {}, overrides, canonical, platforms_dir)
def _target_core_set(
entry: dict, overrides: dict, target: str, platforms_dir: str
) -> set[str] | None:
"""A target's cores: what the board builds, overrides applied, and the
buildbot cores its RetroArch core downloader installs (`downloads`, a
RetroArch targets entry), which a Batocera x86_64 or armv7 board can
fetch from its menu."""
declared = entry.get("cores")
if declared is None:
return None
cores = set(str(c) for c in declared)
return apply_target_overrides(cores, overrides, canonical)
cores = apply_target_overrides({str(c) for c in declared}, overrides, target)
downloads = entry.get("downloads")
if downloads:
cores |= load_target_config("retroarch", str(downloads), platforms_dir) or set()
return cores
def apply_target_overrides(
@@ -917,12 +930,9 @@ def list_available_targets(
result = []
for tname, tdata in sorted(data.get("targets", {}).items()):
aliases = (overrides.get(tname) or {}).get("aliases", [])
declared = (tdata or {}).get("cores")
# None: the target runs every core the platform declares.
count = (
None if declared is None
else len(apply_target_overrides({str(c) for c in declared}, overrides, tname))
)
found = _target_core_set(tdata or {}, overrides, tname, platforms_dir)
count = None if found is None else len(found)
result.append(
{
"name": tname,
@@ -2533,6 +2543,7 @@ def group_identical_platforms(
cores if isinstance(cores, str) else sorted(map(str, cores or [])),
sorted(map(str, config.get("standalone_cores") or [])),
sorted(map(str, config.get("dual_mode_cores") or [])),
sorted(map(str, config.get("installable_cores") or [])),
# One archive cannot serve a filesystem that folds case and one
# that does not: Lakka keeps bios.col beside BIOS.col.
bool(config.get("case_insensitive_fs")),
@@ -3218,9 +3229,11 @@ def resolve_platform_cores(
# An emulator the platform launches standalone runs there even when
# its own list names only the cores: Batocera's es_systems.yml files
# lexaloffle and gsplus as standalone emulators and nowhere else.
# A core the platform's RetroArch installs from the buildbot runs
# there as well (installable_cores, Batocera x86_64 and armv7).
core_set = {str(c) for c in cores_config} | {
str(c) for c in config.get("standalone_cores") or []
}
} | {str(c) for c in config.get("installable_cores") or []}
index = upstream_profile_index(profiles)
platform = str(config.get("_key") or "")
+75 -64
View File
@@ -14,11 +14,11 @@ import re
import sys
import urllib.error
import urllib.request
from collections.abc import Callable, Iterable
from collections.abc import Callable
from pathlib import Path
from common import yaml_load
from common import load_target_config, yaml_load
from hashing import cut_md5_owner
from safeparse import yaml_load_upstream
@@ -30,6 +30,8 @@ from .base_scraper import (
requirement_entry,
)
from .batocera_launchers import Configgen, configs_root, extract_launchers
from .targets.batocera_targets_scraper import BuildSelection
from .targets.batocera_targets_scraper import Scraper as BoardTargets
PLATFORM_NAME = "batocera"
@@ -489,76 +491,51 @@ def configgen_cores(
for GameCube). `dropped` names what es_systems.yml files under an
emulator without being one.
A default es_systems.yml does not offer names an emulator the build may
lack: KNULLI's defaults pick suyu for the Switch, and no package of its
tree, its batocera submodule or its buildroot builds suyu. `built` says
whether the build has a standalone emulator (packages_and_programs); a
default it answers no for is not run, nor its core.
`built` says whether some board builds a name (boards_build). What it
answers no for is not run: KNULLI's es_systems lists citron and shadps4,
which no KNULLI board selects, and its defaults pick suyu, which no
package builds. A default's core follows a standalone emulator the
build runs (vox_official is lexaloffle's Voxatron build).
"""
data = yaml_load_upstream(defaults_text)
cores, standalone = es_systems_emulators(es_systems_text)
offered = set(cores)
libretro = es_systems_libretro(es_systems_text)
if not cores:
raise RuntimeError("es_systems.yml names no emulator")
if built is not None:
cores = {name for name in cores if built(name)}
standalone &= cores
for system, cfg in data.items():
if system == "default" or not isinstance(cfg, dict):
continue
emulator = cfg.get("emulator", "")
core = cfg.get("core", "")
if (built is not None and emulator and emulator != "libretro"
and emulator not in offered and not built(emulator)):
continue
if core:
cores.add(core)
if emulator and emulator != "libretro":
if built is not None and not built(emulator):
continue
standalone.add(emulator)
if core and core != emulator:
standalone.add(core)
elif built is not None and core and not built(core):
continue
if core:
cores.add(core)
dual = (standalone & libretro) - dropped
return sorted(cores - dropped), sorted(standalone - dropped), sorted(dual)
BATOCERA_REPO = "batocera-linux/batocera.linux"
def boards_build(selection: BuildSelection, launchers: list[dict]) -> Callable[[str], bool]:
"""Whether some board of a build runs an emulator.
def package_names(paths: Iterable[str]) -> set[str]:
"""The buildroot packages a tree defines: each folder holding a Config.in."""
return {
path.rsplit("/", 2)[-2]
for path in paths
if path.endswith("/Config.in") and path.count("/") >= 1
}
def repo_tree(repo: str, ref: str) -> list[str]:
"""Every path of a GitHub repository at a revision."""
url = f"https://api.github.com/repos/{repo}/git/trees/{ref}?recursive=1"
req = urllib.request.Request(url, headers=github_headers())
with urllib.request.urlopen(req, timeout=60) as resp:
found = json.loads(resp.read().decode("utf-8"))
if found.get("truncated"):
raise RuntimeError(f"{url}: tree truncated")
return [entry["path"] for entry in found.get("tree", [])]
def submodule_revision(repo: str, path: str, ref: str) -> str:
"""The commit a repository's submodule at `path` pins at a revision."""
url = f"https://api.github.com/repos/{repo}/contents/{path}?ref={ref}"
req = urllib.request.Request(url, headers=github_headers())
with urllib.request.urlopen(req, timeout=30) as resp:
found = json.loads(resp.read().decode("utf-8"))
if not isinstance(found, dict) or found.get("type") != "submodule" or not found.get("sha"):
raise RuntimeError(f"{url}: {path} is not a submodule")
return str(found["sha"])
def packages_and_programs(packages: set[str], launchers: list[dict]) -> Callable[[str], bool]:
"""Whether a build has a standalone emulator.
It does when a package of that name exists, or when its launcher runs a
program the user puts in the BIOS folder (Batocera's lexaloffle runs
/userdata/bios/pico-8/pico8), which no package builds.
es_systems.yml ties each name to the packages that build it
(requireAnyOf), and a name it lists is built when a board selects one of
them: KNULLI lists citron and shadps4, and its Config.in selects them
only on x86_64 boards, which it has none of. A name only
configgen-defaults.yml gives is built when a board selects the package
of that name: the symbol its Config.in defines, else the one Buildroot
derives from the folder (BR2_PACKAGE_<NAME>, package/pkg-generic.mk). A
program the launcher runs from the BIOS folder (lexaloffle,
/userdata/bios/pico-8/pico8) needs no package.
"""
supplied = {
name.strip()
@@ -567,7 +544,42 @@ def packages_and_programs(packages: set[str], launchers: list[dict]) -> Callable
and str(record.get("search_dir") or "").startswith("/userdata/bios")
for name in str(record.get("emulator", "")).split(",")
}
return lambda emulator: emulator in packages or emulator in supplied
def built(name: str) -> bool:
if name in selection.emulators or name in supplied:
return True
if name in selection.listed:
return False
symbol = selection.packages.get(name) or (
"BR2_PACKAGE_" + name.upper().replace("-", "_"))
return symbol in selection.symbols
return built
def installable_cores(selection: BuildSelection, cores: list[str]) -> list[str]:
"""Buildbot cores a board's RetroArch core downloader installs, beyond
the cores the image builds.
The targets scraper names, per board, the RetroArch targets entry its
downloader reads (`downloads`): a Batocera x86_64 board installs any
core of linux-x86_64, an armv7 one any of linux-armhf.
"""
found: set[str] = set()
for entry in selection.targets.values():
if entry.get("downloads"):
found |= load_target_config("retroarch", entry["downloads"]) or set()
return sorted(found - set(cores))
def records_run(launchers: list[dict], built: Callable[[str], bool]) -> list[dict]:
"""The launcher records of emulators the build runs.
configgen keeps the generators of emulators a build does not select
(KNULLI's citron, pcsx2, shadps4): what they read is never read on the
device, so the records do not describe the platform.
"""
return [r for r in launchers if any(built(n) for n in str(r["emulator"]).split(", "))]
class Scraper(BaseScraper):
@@ -578,20 +590,13 @@ class Scraper(BaseScraper):
super().__init__(url=SOURCE_URL.format(tag=self.tag))
def _fetch_cores(
self, launchers: list[dict] | None = None
self, built: Callable[[str], bool] | None = None
) -> tuple[list[str], list[str], list[str]]:
"""Core names, standalone cores and dual-mode cores at the pinned tag.
A default es_systems.yml does not offer counts when a package of
Batocera's tree or its buildroot builds it, or when its launcher
runs a program from the BIOS folder.
With `built` (boards_build), a name counts when some board builds
it or its launcher runs a program from the BIOS folder.
"""
built = None
if launchers is not None:
paths = repo_tree(BATOCERA_REPO, self.tag)
buildroot = submodule_revision(BATOCERA_REPO, "buildroot", self.tag)
paths += repo_tree("batocera-linux/buildroot", buildroot)
built = packages_and_programs(package_names(paths), launchers)
return configgen_cores(
self._fetch_text(CONFIGGEN_DEFAULTS_URL.format(tag=self.tag)),
self._fetch_text(ES_SYSTEMS_URL.format(tag=self.tag)),
@@ -633,7 +638,11 @@ class Scraper(BaseScraper):
launchers = extract_launchers(
lambda path: self._fetch_text(f"{_RAW_BASE}/{self.tag}/{path}")
)
cores, standalone, dual = self._fetch_cores(launchers)
selection = BoardTargets().build_selection(self.tag)
built = boards_build(selection, launchers)
launchers = records_run(launchers, built)
cores, standalone, dual = self._fetch_cores(built)
installable = installable_cores(selection, cores)
libretro_systems = es_systems_libretro_systems(
self._fetch_text(ES_SYSTEMS_URL.format(tag=self.tag)), _not_cores()
)
@@ -653,6 +662,8 @@ class Scraper(BaseScraper):
if dual:
# Launched both ways: each layout gets its files.
result["dual_mode_cores"] = dual
if installable:
result["installable_cores"] = installable
# The systems each libretro core runs for: its save folders.
result["libretro_systems"] = libretro_systems
result["launchers"] = launchers
+29 -4
View File
@@ -209,7 +209,18 @@ class Scraper(BaseScraper):
)
not_cores = _not_cores(PLATFORM_NAME)
cores, standalone, dual = es_cores(es_systems, RUNNERS, BUILDS_OF, not_cores)
# Each system keeps the list es_systems.json gives it: it says whose
# files a platform entry asks for. What runs is what an image carries.
attach_system_cores(systems, system_cores(es_systems, RUNNERS, not_cores))
built = self._built()
unbuilt = {n for n in cores if n not in built}
cores, standalone, dual = ([n for n in names if n in built] for names in (cores, standalone, dual))
# Runners (retroarch, retrorun) and the launcher's own spellings are
# no ES core name: only a record of an emulator no image carries goes.
launchers = [
r for r in extract_launchers(RepoTree.github(REPO, launcher_revision(self.url)))
if not set(str(r["emulator"]).split(", ")) <= unbuilt
]
return {
"platform": "EmuELEC",
"version": self.tag.removeprefix("v"),
@@ -225,12 +236,26 @@ class Scraper(BaseScraper):
"standalone_cores": standalone,
"dual_mode_cores": dual,
"systems": systems,
# Read at the revision the BIOS list comes from.
"launchers": extract_launchers(
RepoTree.github(REPO, launcher_revision(self.url))
),
# Read at the revision the BIOS list comes from, for the
# emulators an image carries.
"launchers": launchers,
}
def _built(self) -> set[str]:
"""The cores at least one released device image carries.
Read by the targets scraper: a device keeps a core when a package it
builds installs it and its es_systems.cfg still offers it. The port
engines depend on emuelec-ports, which no package of an image
depends on, and flycast-dojo is in no device list (UNBUILT there).
"""
try:
from .targets.emuelec_targets_scraper import Scraper as DeviceTargets
except ImportError:
from scraper.targets.emuelec_targets_scraper import Scraper as DeviceTargets
targets = DeviceTargets().device_targets(self.tag)["targets"]
return {name for entry in targets.values() for name in entry["cores"]}
def main() -> None:
from .base_scraper import scraper_cli
+13 -13
View File
@@ -37,18 +37,18 @@ from .batocera_scraper import (
)
from .batocera_scraper import (
_not_cores,
boards_build,
configgen_cores,
es_systems_by_system,
es_systems_libretro_systems,
extract_systems_dict,
package_names,
packages_and_programs,
installable_cores,
platform_systems,
repo_tree,
records_run,
requirements_from_systems,
submodule_revision,
)
from .es_emulators import attach_system_cores
from .targets.knulli_targets_scraper import Scraper as BoardTargets
from .batocera_launchers import configs_root
from .knulli_launchers import BATOCERA_SUBMODULE, extract_launchers
from .knulli_launchers import configgen as knulli_configgen
@@ -134,16 +134,14 @@ class Scraper(BaseScraper):
lambda path: self._fetch_text(_RAW.format(ref=self.tag) + path),
lambda path: self._fetch_text(_BATOCERA_RAW.format(ref=revision) + path),
)
# The packages KNULLI builds come from its own tree, its batocera
# submodule and its buildroot submodule: a default no package of
# them builds (suyu) is not run.
paths = repo_tree(REPO, self.tag)
paths += repo_tree("batocera-linux/batocera.linux", revision)
paths += repo_tree(
"knulli-cfw/buildroot", submodule_revision(REPO, "buildroot", self.tag)
)
built = packages_and_programs(package_names(paths), launchers)
# What runs is what at least one board builds, read by the targets
# scraper from the Kconfig of KNULLI's tree and its batocera and
# buildroot submodules (boards_build).
selection = BoardTargets().build_selection(self.tag)
built = boards_build(selection, launchers)
launchers = records_run(launchers, built)
cores, standalone, dual = configgen_cores(defaults, es_systems, dropped, built)
installable = installable_cores(selection, cores)
# Each system's own list (psx, es_systems.yml:640-653: libretro
# pcsx_rearmed, swanstation, mednafen_psx, then duckstation and
# duckstation-legacy).
@@ -165,6 +163,8 @@ class Scraper(BaseScraper):
result["standalone_cores"] = standalone
if dual:
result["dual_mode_cores"] = dual
if installable:
result["installable_cores"] = installable
# configgen hands a libretro core /userdata/saves/<system>, as
# Batocera's does: the systems each core runs for are its save folders.
result["libretro_systems"] = es_systems_libretro_systems(es_systems, dropped)
+21 -4
View File
@@ -190,6 +190,25 @@ class Scraper(BaseScraper):
self.tag = None if url else fetch_github_latest_version(GITHUB_REPO)
super().__init__(url=url or SOURCE_TEMPLATE.format(ref=self.tag))
def _built_cores(self, standalone: list[str]) -> tuple[list[str], list[str]]:
"""(cores, standalone cores) at least one device image carries.
RetroArch runs the libretro cores the packages of its emulators
metapackage install per device
(projects/ROCKNIX/packages/virtual/emulators/package.mk), read by
the targets scraper, where every libretro profile counted before. A
standalone package no device carries is not run either.
"""
try:
from .targets.rocknix_targets_scraper import Scraper as DeviceTargets
except ImportError:
from scraper.targets.rocknix_targets_scraper import Scraper as DeviceTargets
builds = DeviceTargets().device_builds(self.tag or launcher_revision(self.url))
libretro = sorted({core for _arch, lr, _sa in builds.values() for core in lr})
carried = set(standalone_emulators(sorted({p for *_rest, sa in builds.values() for p in sa})))
standalone = [name for name in standalone if name in carried]
return sorted({*libretro, *standalone}), standalone
def _fetch_standalone(self) -> list[str]:
"""The packages of projects/ROCKNIX/packages/emulators/standalone."""
import json
@@ -281,7 +300,7 @@ class Scraper(BaseScraper):
)
entry["files"].append(requirement_entry(req))
standalone = self._fetch_standalone()
cores, standalone = self._built_cores(self._fetch_standalone())
return {
"platform": "ROCKNIX",
"version": self.tag or fetch_github_latest_version(GITHUB_REPO),
@@ -290,9 +309,7 @@ class Scraper(BaseScraper):
"base_destination": "bios",
"hash_type": "md5",
"verification_mode": "md5",
# RetroArch with every libretro core, plus the standalone
# emulators ROCKNIX packages beside it.
"cores": ["retroarch", *standalone],
"cores": cores,
"standalone_cores": standalone,
# Launched both ways only where package.mk also adds the
# emulator as a RetroArch core.
@@ -3,6 +3,8 @@
Sources (batocera-linux/batocera.linux):
- configs/batocera-*.board -- board definitions, each sets BR2_PACKAGE_BATOCERA_TARGET_*
- package/batocera/core/batocera-system/Config.in -- select PACKAGE if CONDITION lines
- Config.in and every Kconfig file it sources -- the selects a package
declares itself (advanced_drastic selects drastic)
- package/batocera/emulationstation/batocera-es-system/es_systems.yml
-- emulator requireAnyOf flag mapping
Sources (batocera-linux/buildroot, at the submodule commit the tag pins):
@@ -19,6 +21,8 @@ import re
import sys
import urllib.error
import urllib.request
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass
from datetime import datetime, timezone
import yaml
@@ -79,6 +83,16 @@ def _fetch(url: str, headers: dict | None = None) -> str:
raise RuntimeError(f"cannot fetch {url}: {e}") from e
def _fetch_present(url: str) -> str | None:
"""A file's text, or None when the server answers that it does not exist."""
try:
return _fetch(url)
except RuntimeError as e:
if isinstance(e.__cause__, urllib.error.HTTPError) and e.__cause__.code == 404:
return None
raise
def _fetch_json(url: str) -> list | dict:
text = _fetch(url, headers=github_headers())
try:
@@ -205,11 +219,14 @@ def _parse_meta_flags(text: str) -> list[tuple[str, str]]:
# ("select BR2_PACKAGE_XROAR if BR2_PACKAGE_HAS_LIBGL") reached no board.
BUILDROOT_KCONFIG = (
"arch/Config.in",
# Each armv7-A CPU selects BR2_ARM_CPU_ARMV7A (cortex_a15_a7 included).
"arch/Config.in.arm",
"package/mesa3d/Config.in",
"package/libglvnd/Config.in",
)
_CONFIG_LINE_RE = re.compile(r"^\s*(?:menu)?config\s+(BR2_\w+)")
_SOURCE_RE = re.compile(r'^\s*source\s+"\$BR2_EXTERNAL_\w+_PATH/([^"]+)"')
_OWNED_SELECT_RE = re.compile(r"^\s+select\s+(BR2_\w+)(?:\s+if\s+([^#]+))?")
_BOARD_FLAG_RE = re.compile(r"^(BR2_\w+)=y\s*$", re.MULTILINE)
_BOARD_INCLUDE_RE = re.compile(r"^(-?)include\s+(\S+)", re.MULTILINE)
@@ -315,6 +332,51 @@ def _parse_es_systems(text: str) -> dict[str, list[str]]:
return package_to_emulators
# RetroArch's core downloader fetches from DEFAULT_BUILDBOT_SERVER_URL, which
# Linux sets for x86_64, i386 and armv7 hard-float alone, and leaves empty on
# aarch64 (config.def.h:1855-1864 at Batocera's 4b74434 and at v1.22.2).
def buildbot_target(symbols: set[str] | frozenset[str]) -> str | None:
"""The RetroArch targets entry a board's core downloader installs from.
`symbols` are the board's active symbols. An armv7-A CPU selects
BR2_ARM_CPU_ARMV7A (arch/Config.in.arm) and is hard-float unless the
board asks for soft-float (BR2_ARM_EABI): Buildroot picks EABIHF for a
CPU with a VFP, which every Cortex-A has. armv6 (bcm2835), aarch64 and
riscv64 get no buildbot URL.
"""
if "BR2_x86_64" in symbols:
return "linux-x86_64"
if "BR2_i386" in symbols:
return "linux-x86"
if "BR2_ARM_CPU_ARMV7A" in symbols and "BR2_ARM_EABI" not in symbols:
return "linux-armhf"
return None
# configure options and make variables that compile the downloader out.
_NO_DOWNLOADER = ("--disable-update-cores", "--disable-online-updater", "--disable-networking",
"HAVE_UPDATE_CORES=0", "HAVE_ONLINE_UPDATER=0", "HAVE_LAKKA=1")
_BLANK_CORES_URL = re.compile(r'^\s*core_updater_buildbot_cores_url\s*=\s*""', re.MULTILINE)
def downloader_disabled(retroarch_mk: str, shipped_cfgs: dict[str, str]) -> str | None:
"""Why RetroArch's core downloader installs nothing, or None.
Compiled out by its build options, or left without a URL by a shipped
retroarch.cfg. A config that hides the menu entry alone does not stop
it: without HAVE_LAKKA, Settings > User Interface offers it back
(menu_setting.c). A config that blanks the URL for some boards only is
a case this reader does not split, and an error.
"""
for option in _NO_DOWNLOADER:
if option in retroarch_mk:
return f"retroarch.mk sets {option}"
blank = sorted(path for path, text in shipped_cfgs.items() if _BLANK_CORES_URL.search(text))
if blank:
raise RuntimeError(f"{', '.join(blank)} blank the core downloader URL: split per board")
return None
# Buildroot's architecture choice, as a board sets it (arch/Config.in).
_ARCH_SYMBOLS = {
"BR2_aarch64": "aarch64",
@@ -342,6 +404,44 @@ def _arch_from_flag(flag: str, board_flags: set[str] | frozenset[str] = frozense
return "aarch64"
@dataclass(frozen=True)
class BuildSelection:
"""What the boards of a build turn on, read once from its Kconfig.
`targets` is each board's entry of the targets file, `downloads` naming
the RetroArch targets entry its core downloader installs from. `emulators` holds
every es_systems name some board builds and `listed` every name
es_systems ties to a package, `symbols` every symbol some board ends up
with, and `packages` the config symbol each package folder of the tree
defines.
"""
targets: dict[str, dict]
emulators: frozenset[str]
listed: frozenset[str]
symbols: frozenset[str]
packages: dict[str, str]
def _sourced(text: str) -> list[str]:
"""The files a Kconfig file sources, relative to the external tree."""
return [m.group(1) for line in text.splitlines() if (m := _SOURCE_RE.match(line))]
def _package_symbols(files: dict[str, str]) -> dict[str, str]:
"""package folder -> the first config symbol its Config.in defines."""
found: dict[str, str] = {}
for path, text in files.items():
parts = path.split("/")
if len(parts) < 3 or parts[-1] != "Config.in" or "package" not in parts:
continue
config = next((m.group(1) for line in text.splitlines()
if (m := _CONFIG_LINE_RE.match(line))), None)
if config:
found.setdefault(parts[-2], config)
return found
class Scraper(BaseTargetScraper):
"""Cross-references Batocera boards, Config.in, and es_systems to build target lists.
@@ -357,6 +457,10 @@ class Scraper(BaseTargetScraper):
ES_SYSTEMS_PATH = "package/batocera/emulationstation/batocera-es-system/es_systems.yml"
SYSTEM_SYMBOL = "BR2_PACKAGE_BATOCERA_SYSTEM"
BUILDROOT_REPO = "batocera-linux/buildroot"
# Path prefix -> repository of a submodule the root Config.in sources
# from: KNULLI reads Batocera's packages as batocera/package/...
SUBMODULES: dict[str, str] = {}
RETROARCH_MK = "package/batocera/emulators/retroarch/retroarch/retroarch.mk"
def __init__(self, url: str = "") -> None:
super().__init__(url=url or f"https://github.com/{self.REPO}")
@@ -411,6 +515,62 @@ class Scraper(BaseTargetScraper):
pending += [(inc, bool(dash)) for dash, inc in _BOARD_INCLUDE_RE.findall(text)]
return target, flags
def _kconfig_files(self) -> dict[str, str]:
"""The root Config.in and every file it sources, recursively.
The build reads these and nothing else: a package's own Config.in
can select another package (advanced_drastic selects drastic), and
such a select sits in no system Config.in. A sourced file absent at
the pin defines nothing and is named: KNULLI 20260511 sources
package/utils/vtree/Config.in, a package its tree gained later.
"""
bases: list[tuple[str, str]] = []
for prefix, repo in self.SUBMODULES.items():
pin = _fetch_json(self._api(prefix.rstrip("/")))
sha = pin.get("sha") if isinstance(pin, dict) else None
if not sha:
raise RuntimeError(f"no {prefix.rstrip('/')} submodule commit at {self.tag}")
bases.append((prefix, f"https://raw.githubusercontent.com/{repo}/{sha}/"))
def url(path: str) -> str:
for prefix, base in bases:
if path.startswith(prefix):
return base + path.removeprefix(prefix)
return self._raw(path)
files: dict[str, str] = {}
absent: list[str] = []
pending = ["Config.in"]
with ThreadPoolExecutor(max_workers=8) as pool:
while pending:
found = dict(zip(pending, pool.map(lambda path: _fetch_present(url(path)), pending)))
absent += [path for path, text in found.items() if text is None]
files.update((path, text or "") for path, text in found.items())
pending = sorted({src for text in found.values() if text
for src in _sourced(text)} - set(files))
if "Config.in" in absent or len(files) - len(absent) < 2:
raise RuntimeError(f"Config.in at {self.tag} sources no readable Kconfig file")
for path in absent:
print(f" sourced but absent at {self.tag}: {path}", file=sys.stderr)
return files
def _shipped_retroarch_cfgs(self) -> dict[str, str]:
"""Every retroarch.cfg the tree ships, its submodules included."""
repos = [("", self.REPO, self.tag)]
for prefix, repo in self.SUBMODULES.items():
pin = _fetch_json(self._api(prefix.rstrip("/")))
repos.append((prefix, repo, pin["sha"]))
found: dict[str, str] = {}
for prefix, repo, ref in repos:
listing = _fetch_json(f"https://api.github.com/repos/{repo}/git/trees/{ref}?recursive=1")
if not isinstance(listing, dict) or listing.get("truncated"):
raise RuntimeError(f"{repo} tree at {ref} is unreadable or truncated")
for entry in listing.get("tree", []):
if re.search(r"(^|/)retroarch\.cfg$", entry["path"]):
raw = f"https://raw.githubusercontent.com/{repo}/{ref}/{entry['path']}"
found[prefix + entry["path"]] = _fetch(raw)
return found
def _buildroot_rules(self) -> list[tuple[str, str, str]]:
"""Buildroot's selects for the symbols the tree tests, at its pin."""
pin = _fetch_json(self._api("buildroot"))
@@ -423,26 +583,32 @@ class Scraper(BaseTargetScraper):
rules += _parse_owned_selects(_fetch(f"{raw}/{path}"))
return rules
def fetch_targets(self) -> dict:
"""Build per-board emulator availability map."""
self.tag = self.pinned_revision(self.PLATFORM)
def build_selection(self, tag: str | None = None) -> BuildSelection:
"""Each board's selection, from the Kconfig the build reads.
`tag` is the revision to read, by default the one the platform's
YAML was scraped at: a platform scraper moving to a new release
passes its own.
"""
self.tag = tag or self.pinned_revision(self.PLATFORM)
print(" fetching board list...", file=sys.stderr)
boards = self._list_boards()
print(" fetching Config.in...", file=sys.stderr)
config_in_url = self._raw(self.CONFIG_IN_PATH)
config_in_text = _fetch(config_in_url)
print(" fetching Kconfig files...", file=sys.stderr)
files = self._kconfig_files()
config_in_text = files.get(self.CONFIG_IN_PATH)
if not config_in_text:
raise RuntimeError(f"empty Config.in from {config_in_url}")
raise RuntimeError(f"Config.in at {self.tag} does not source {self.CONFIG_IN_PATH}")
meta_rules = _parse_meta_flags(config_in_text)
rules = _parse_owned_selects(config_in_text)
rules = [rule for text in files.values() for rule in _parse_owned_selects(text)]
print(
f" parsed {len(meta_rules)} meta-flag rules, {len(rules)} select lines",
f" parsed {len(meta_rules)} meta-flag rules, {len(rules)} select lines "
f"in {len(files)} Kconfig files",
file=sys.stderr,
)
if not rules:
raise RuntimeError(f"no 'select BR2_PACKAGE_*' lines parsed from {config_in_url}")
if not _parse_owned_selects(config_in_text):
raise RuntimeError(f"no 'select BR2_PACKAGE_*' lines parsed from {self.CONFIG_IN_PATH}")
rules += self._buildroot_rules()
print(" fetching es_systems.yml...", file=sys.stderr)
@@ -459,9 +625,14 @@ class Scraper(BaseTargetScraper):
raise RuntimeError(f"no package->emulator mappings parsed from {es_systems_url}")
images_by_flag = _parse_images(config_in_text)
if not images_by_flag:
raise RuntimeError(f"no target image list parsed from {config_in_url}")
raise RuntimeError(f"no target image list parsed from {self.CONFIG_IN_PATH}")
disabled = downloader_disabled(_fetch(self._raw(self.RETROARCH_MK)),
self._shipped_retroarch_cfgs())
if disabled:
print(f" core downloader off: {disabled}", file=sys.stderr)
targets: dict[str, dict] = {}
symbols: set[str] = set()
for board_name in sorted(boards):
target_key = board_name.removeprefix(self.BOARD_PREFIX).removesuffix(".board")
print(f" processing {target_key}...", file=sys.stderr)
@@ -476,6 +647,7 @@ class Scraper(BaseTargetScraper):
if self.SYSTEM_SYMBOL not in selected_packages:
print(f" {board_name} builds no system image", file=sys.stderr)
continue
symbols |= selected_packages
# Map selected packages to emulator names via es_systems.yml
emulators: set[str] = set()
@@ -491,16 +663,30 @@ class Scraper(BaseTargetScraper):
images = sorted(set(images_by_flag.get(primary_flag, [])))
if images and images != [target_key]:
targets[target_key]["images"] = images
downloads = None if disabled else buildbot_target(selected_packages)
if downloads:
targets[target_key]["downloads"] = downloads
print(
f" {len(emulators)} emulators ({len(selected_packages)} packages selected)",
file=sys.stderr,
)
return BuildSelection(
targets=targets,
emulators=frozenset(c for entry in targets.values() for c in entry["cores"]),
listed=frozenset(n for names in package_to_emulators.values() for n in names),
symbols=frozenset(symbols),
packages=_package_symbols(files),
)
def fetch_targets(self) -> dict:
"""Build per-board emulator availability map."""
selection = self.build_selection()
return {
"platform": self.PLATFORM,
"source": f"{self.url}/tree/{self.tag}",
"scraped_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"targets": targets,
"targets": selection.targets,
}
@@ -610,7 +610,12 @@ class Scraper(BaseTargetScraper):
return fetch_bytes(RAW.format(ref=self.ref, path=path)).decode("utf-8")
def fetch_targets(self) -> dict:
self.ref = self.pinned_revision(PLATFORM_NAME)
return self.device_targets()
def device_targets(self, ref: str | None = None) -> dict:
"""The targets file, read at `ref`, by default the revision
platforms/emuelec.yml was scraped at."""
self.ref = ref or self.pinned_revision(PLATFORM_NAME)
release = json.loads(
fetch_bytes(RELEASE_API.format(ref=self.ref), headers=github_headers())
)
@@ -9,6 +9,8 @@ the tag platforms/knulli.yml names):
the root Config.in in place of Batocera's batocera-system/Config.in
- package/emulationstation/knulli-es-system/es_systems.yml
-- emulator requireAnyOf flag mapping
- Config.in and what it sources, Batocera's packages read from the
batocera submodule at the commit the tag pins
Sources (knulli-cfw/buildroot, at the submodule commit the tag pins):
- arch/Config.in, package/mesa3d/Config.in, package/libglvnd/Config.in
@@ -32,6 +34,8 @@ class Scraper(BatoceraScraper):
ES_SYSTEMS_PATH = "package/emulationstation/knulli-es-system/es_systems.yml"
SYSTEM_SYMBOL = "BR2_PACKAGE_KNULLI_SYSTEM"
BUILDROOT_REPO = "knulli-cfw/buildroot"
SUBMODULES = {"batocera/": "batocera-linux/batocera.linux"}
RETROARCH_MK = "package/retroarch/retroarch/retroarch.mk"
def main() -> None:
@@ -5,6 +5,8 @@ Sources (ROCKNIX/distribution, at the release tag platforms/rocknix.yml names):
image carries, those aarch64 adds, and a `case "${DEVICE}"` block per SoC
- projects/ROCKNIX/devices/<DEVICE>/options -- ENABLE_32BIT, which the 32-bit
cores of a device depend on (distributions/ROCKNIX/options defaults it on)
- each libretro package's package.mk -- the cores its makeinstall_target
copies to ${INSTALL}/usr/lib/libretro (vice-lr installs ten of them)
An H700 or RK3326 handheld carries neither Dolphin nor AetherSX2: without a
target list, its pack shipped the PS2, GameCube and PS3 firmware anyway.
@@ -13,6 +15,7 @@ target list, its pack shipped the PS2, GameCube and PS3 firmware anyway.
from __future__ import annotations
import argparse
import json
import re
import sys
from datetime import datetime, timezone
@@ -89,18 +92,90 @@ def parse_device_cores(
return libretro, standalone
def core_names(libretro: list[str], standalone: list[str]) -> list[str]:
"""Package names as the profiles' cores: lists spell them.
_INSTALL_BODY_RE = re.compile(r"^makeinstall_target\(\)\s*\{\n(.*?)^\}", re.M | re.S)
_COPY_RE = re.compile(r"\bcp\s+(.*?)\s+\$\{?INSTALL\}?/usr/lib/libretro(?:/(\S*))?")
_CORE_FILE_RE = re.compile(r"([\w+.-]+)_libretro\.(?:so\b|\{[^}]*\bso\b[^}]*\})")
_TEST_RE = re.compile(r'\[\s*"\$\{(\w+)\}"\s*=\s*"([^"]*)"\s*\]')
A libretro package is the core's name with `-lr` (`beetle-psx-lr`,
`neocd_lr`), spelled with underscores as the buildbot names it. A
standalone package is kept under both spellings, as the platform
scraper keeps it: `vice-sa` is the only name vice-standalone answers
to, `vice` that of the libretro profile.
def installed_cores(package_mk: str, arch: str, with_32bit: bool) -> list[str]:
"""The cores a libretro package installs on a device.
The name is that of the file makeinstall_target copies to
${INSTALL}/usr/lib/libretro ($INSTALL in some), renamed when the copy names its target:
beetle-psx-lr copies mednafen_psx_libretro.so as beetle_psx_libretro.so,
vice-lr ten EMUTYPE builds. pcsx_rearmed-lr picks its file by
TARGET_ARCH and adds the 32-bit build when ENABLE_32BIT is on, desmume-lr
copies the 32-bit build on aarch64 (ARCH). A test on another variable,
or a package that installs no core, is an error.
"""
names = {re.sub(r"[-_]lr$", "", package).replace("-", "_") for package in libretro}
names.update(standalone_emulators(standalone))
return sorted(names)
body = _INSTALL_BODY_RE.search(package_mk)
if body is None:
raise RuntimeError("package.mk has no makeinstall_target")
# config/options:36 sets TARGET_ARCH="${ARCH}".
values = {"ARCH": arch, "TARGET_ARCH": arch, "ENABLE_32BIT": "true" if with_32bit else "false"}
def holds(condition: str) -> bool:
tests = _TEST_RE.findall(condition)
if not tests or re.sub(_TEST_RE, "", condition).strip(" ;&") not in ("", "then"):
raise RuntimeError(f"makeinstall_target tests {condition.strip()!r}")
for variable, value in tests:
if variable not in values:
raise RuntimeError(f"makeinstall_target tests ${{{variable}}}")
if values[variable] != value:
return False
return True
found: list[str] = []
stack: list[bool] = []
in_case = False
for raw in body.group(1).splitlines():
line = raw.strip()
if re.match(r"case \$\{TARGET_ARCH\} in$", line):
in_case = True
stack.append(False)
continue
if line == "esac":
in_case = False
stack.pop()
continue
if in_case and (arm := re.match(r"([\w|*]+)\)\s*(.*)$", line)):
stack[-1] = arch in arm.group(1).split("|") or arm.group(1) == "*"
line = arm.group(2)
if line.startswith("if "):
stack.append(holds(line[3:]))
continue
if line == "else":
stack[-1] = not stack[-1]
continue
if line == "fi":
stack.pop()
continue
if not all(stack):
continue
for sources, target in _COPY_RE.findall(line):
if named := _CORE_FILE_RE.search(target):
found.append(named.group(1))
continue
for source in sources.split():
if not source.startswith("-") and (named := _CORE_FILE_RE.search(source.rsplit("/", 1)[-1])):
found.append(named.group(1))
if not found:
raise RuntimeError("makeinstall_target installs no libretro core")
return sorted(set(found))
def package_paths(tree: list[str]) -> dict[str, str]:
"""package name -> its package.mk, a ROCKNIX project package first.
The build takes projects/ROCKNIX/packages/<...>/<name> over
packages/<...>/<name> when both exist.
"""
found: dict[str, str] = {}
for path in sorted(tree, key=lambda p: not p.startswith("projects/ROCKNIX/")):
if path.endswith("/package.mk"):
found.setdefault(path.rsplit("/", 2)[-2], path)
return found
# A package that builds nothing itself and pulls other cores in. idtech-lr
@@ -149,13 +224,40 @@ class Scraper(BaseTargetScraper):
raise RuntimeError(f"no device in {DEVICES_DIR} at {self.tag}")
return sorted(devices)
def fetch_targets(self) -> dict:
self.tag = self.pinned_revision(PLATFORM_NAME)
def _tree(self) -> list[str]:
listing = json.loads(fetch_bytes(
f"https://api.github.com/repos/ROCKNIX/distribution/git/trees/{self.tag}?recursive=1",
headers=github_headers(),
))
if listing.get("truncated"):
raise RuntimeError(f"ROCKNIX tree at {self.tag} is truncated")
return [entry["path"] for entry in listing.get("tree", [])]
def device_builds(self, tag: str | None = None) -> dict[str, tuple[str, list[str], list[str]]]:
"""{device: (architecture, libretro cores, standalone packages)}.
Read at `tag`, by default the revision platforms/rocknix.yml was
scraped at: metapackages resolved to the packages they pull in, and
each libretro package to the cores it installs on the device.
"""
self.tag = tag or self.pinned_revision(PLATFORM_NAME)
package_mk = self._raw(EMULATORS_MK)
if "LIBRETRO_CORES" not in package_mk:
raise RuntimeError(f"no LIBRETRO_CORES in {EMULATORS_MK} at {self.tag}")
paths = package_paths(self._tree())
texts: dict[str, str] = {}
targets: dict[str, dict] = {}
def cores_of(package: str, arch: str, with_32bit: bool) -> list[str]:
if package not in paths:
raise RuntimeError(f"no package.mk for {package} at {self.tag}")
if package not in texts:
texts[package] = self._raw(paths[package])
try:
return installed_cores(texts[package], arch, with_32bit)
except RuntimeError as e:
raise RuntimeError(f"{paths[package]}: {e}") from e
builds: dict[str, tuple[str, list[str], list[str]]] = {}
for device in self._devices():
options = self._raw(f"{DEVICES_DIR}/{device}/options")
arch = "x86_64" if device == "AMD64" else "aarch64"
@@ -165,9 +267,19 @@ class Scraper(BaseTargetScraper):
if meta in libretro:
libretro.remove(meta)
libretro += metapackage_cores(self._raw(path), arch)
cores = core_names(libretro, standalone)
targets[device.lower()] = {"architecture": arch, "cores": cores}
print(f" {device.lower()} ({arch}): {len(cores)} cores", file=sys.stderr)
cores = sorted({c for package in libretro for c in cores_of(package, arch, with_32bit)})
builds[device.lower()] = (arch, cores, standalone)
return builds
def fetch_targets(self) -> dict:
targets: dict[str, dict] = {}
for device, (arch, libretro, standalone) in self.device_builds().items():
# A standalone package is kept under both spellings, as the
# platform scraper keeps it: vice-sa is the only name
# vice-standalone answers to, vice that of the libretro profile.
cores = sorted({*libretro, *standalone_emulators(standalone)})
targets[device] = {"architecture": arch, "cores": cores}
print(f" {device} ({arch}): {len(cores)} cores", file=sys.stderr)
return {
"platform": PLATFORM_NAME,
+9 -2
View File
@@ -284,7 +284,10 @@ class TheExportKeepsTheirShape(unittest.TestCase):
class TheCommittedListsAgree(unittest.TestCase):
"""A system's cores are a part of the platform's: a name only the system
carries would escape every reader of the platform list."""
carries would escape every reader of the platform list. A system keeps
the list its ES file writes, which says whose files its entries ask for,
so the one name it may carry beyond the platform's is one no device
image builds (EmuELEC's VVVVVV port engine, KNULLI's duckstation)."""
def test_system_cores_are_platform_cores(self):
import yaml
@@ -292,10 +295,14 @@ class TheCommittedListsAgree(unittest.TestCase):
for name in ("amberelec", "emuelec", "knulli"):
with self.subTest(platform=name):
config = yaml.safe_load((REPO_ROOT / "platforms" / f"{name}.yml").read_text())
targets = yaml.safe_load(
(REPO_ROOT / "platforms" / "targets" / f"{name}.yml").read_text())["targets"]
built = {c for entry in targets.values() for c in entry["cores"]}
platform = {str(c) for c in config["cores"]}
listed = {str(c) for s in config["systems"].values() for c in s.get("cores", [])}
self.assertTrue(listed, "no system carries its cores")
self.assertEqual(listed - platform, set())
self.assertEqual(listed - platform - (listed - built), set())
self.assertEqual(platform - built - {"lexaloffle", "mpv", "vox_official"}, set())
if __name__ == "__main__":
+2
View File
@@ -207,6 +207,8 @@ class APlatformEntryTakesNoOtherEmulatorsTree(unittest.TestCase):
config = load_platform_config("emuelec", str(REPO_ROOT / "platforms"))
entry = config["systems"]["vvvvvv"]["files"][0]
# No image builds the VVVVVV port engine, but es_systems.json still
# names it for the system: its tree is the one the entry asks for.
self.assertEqual(entry["platform_cores"], ["VVVVVV"])
db = load_database(str(REPO_ROOT / "database.json"))
path, _status = resolve_local_file(
+58 -16
View File
@@ -231,15 +231,20 @@ class APortableUserFolder(unittest.TestCase):
class ADefaultTheBuildLacksIsNotRun(unittest.TestCase):
"""KNULLI's configgen-defaults.yml picks suyu for the Switch, which its
es_systems.yml does not offer and no package builds."""
class OnlyWhatABoardBuildsIsRun(unittest.TestCase):
"""KNULLI's es_systems.yml lists citron and shadps4, which its Config.in
selects only on x86_64 boards (it has none), and its
configgen-defaults.yml picks suyu, which no package builds."""
ES = textwrap.dedent("""\
switch:
emulators:
citron:
citron: { requireAnyOf: [BR2_PACKAGE_CITRON] }
gba:
emulators:
libretro:
mgba: { requireAnyOf: [BR2_PACKAGE_LIBRETRO_MGBA] }
voxatron:
emulators: {}
""")
@@ -253,31 +258,67 @@ class ADefaultTheBuildLacksIsNotRun(unittest.TestCase):
kodi:
emulator: kodi
core: kodi
mpv:
emulator: mpv
core: mpv
""")
LAUNCHERS = [{"emulator": "lexaloffle", "kind": "fixed_name", "passes_to": "exec",
"search_dir": "/userdata/bios"}]
def test_only_what_a_package_or_the_user_supplies_is_run(self):
def selection(self, symbols: set[str]):
from scraper.targets.batocera_targets_scraper import BuildSelection
return BuildSelection(
targets={}, emulators=frozenset({"mgba"}),
listed=frozenset({"citron", "mgba"}), symbols=frozenset(symbols),
packages={"kodi": "BR2_PACKAGE_KODI"},
)
def test_only_what_a_board_builds_or_the_user_supplies_is_run(self):
from scraper import batocera_scraper as bs
paths = ["package/batocera/emulators/citron/Config.in", "package/kodi/Config.in"]
built = bs.packages_and_programs(bs.package_names(paths), self.LAUNCHERS)
# A board selects kodi's package and buildroot's mpv; citron is
# listed by es_systems, so the symbol of a folder of that name does
# not count for it.
built = bs.boards_build(
self.selection({"BR2_PACKAGE_KODI", "BR2_PACKAGE_MPV", "BR2_PACKAGE_CITRON_X"}),
self.LAUNCHERS,
)
cores, standalone, _dual = bs.configgen_cores(self.DEFAULTS, self.ES, set(), built)
self.assertEqual(sorted(cores), ["citron", "kodi", "vox_official"])
self.assertIn("lexaloffle", standalone)
self.assertNotIn("suyu", standalone)
self.assertEqual(sorted(cores), ["kodi", "mgba", "mpv", "vox_official"])
self.assertEqual(sorted(standalone), ["kodi", "lexaloffle", "mpv", "vox_official"])
# Each system keeps the list es_systems.yml writes (whose files its
# entries ask for), less a default no package builds.
by_system = bs.es_systems_by_system(self.ES, self.DEFAULTS, set(), built)
self.assertEqual(by_system["switch"], ["citron"])
# Without the packages, every default counts, as before.
self.assertEqual(by_system["voxatron"], ["lexaloffle", "vox_official"])
self.assertEqual(by_system["gba"], ["mgba"])
def test_a_listed_name_is_built_only_through_its_packages(self):
from scraper import batocera_scraper as bs
built = bs.boards_build(self.selection({"BR2_PACKAGE_CITRON"}), [])
self.assertFalse(built("citron"))
self.assertTrue(built("mgba"))
self.assertFalse(built("lexaloffle"))
# Without a selection, every name counts, as before.
self.assertIn("suyu", bs.configgen_cores(self.DEFAULTS, self.ES, set())[0])
def test_the_real_knulli_list_names_no_suyu(self):
def test_the_real_lists_name_only_what_a_board_builds(self):
import yaml
config = yaml.safe_load((REPO_ROOT / "platforms" / "knulli.yml").read_text())
self.assertNotIn("suyu", config["cores"])
self.assertNotIn("suyu", config.get("standalone_cores") or [])
for platform, absent in (("knulli", ("suyu", "citron", "shadps4", "pcsx2")),
("batocera", ("citron", "ryujinx", "applewin"))):
config = yaml.safe_load((REPO_ROOT / "platforms" / f"{platform}.yml").read_text())
names = set(config["cores"]) | set(config.get("standalone_cores") or [])
targets = yaml.safe_load(
(REPO_ROOT / "platforms" / "targets" / f"{platform}.yml").read_text())["targets"]
boards = {c for entry in targets.values() for c in entry["cores"]}
with self.subTest(platform=platform):
self.assertFalse(names & set(absent))
# What a board builds and the list once named is still there,
# and what no board builds is a program the user supplies.
self.assertLessEqual(names - boards, {"kodi", "lexaloffle", "mpv", "vox_official"})
class AUserFolderInTheConfigsFolder(unittest.TestCase):
@@ -289,7 +330,8 @@ class AUserFolderInTheConfigsFolder(unittest.TestCase):
from common import standalone_set
profile = load_emulator_profiles(str(REPO_ROOT / "emulators"))["shadps4"]
for platform in ("batocera", "knulli"):
# KNULLI's generator does the same, but no KNULLI board builds shadps4.
for platform in ("batocera",):
config = load_platform_config(platform, str(REPO_ROOT / "platforms"))
((view, _standalone),) = platform_layouts("shadps4", profile, standalone_set(config))
with self.subTest(platform=platform):
+3 -1
View File
@@ -639,8 +639,10 @@ class PlatformRecords(unittest.TestCase):
cls.config = yaml.safe_load((REPO_ROOT / "platforms" / "rocknix.yml").read_text())
def test_every_emulator_is_one_rocknix_packages(self):
# RetroArch itself runs the libretro cores the list names.
cores = set(self.config["cores"])
self.assertEqual(sorted({r["emulator"] for r in self.config["launchers"]} - cores), [])
self.assertEqual(sorted({r["emulator"] for r in self.config["launchers"]} - cores),
["retroarch"])
def test_every_reference_is_in_the_rocknix_project(self):
for record in self.config["launchers"]:
+4 -1
View File
@@ -480,7 +480,10 @@ class RocknixRunsItsStandaloneEmulators(unittest.TestCase):
root = Path(__file__).resolve().parents[1]
platform = yaml.safe_load((root / "platforms" / "rocknix.yml").read_text())
self.assertIn(f"/distribution/{platform['version']}/", platform["source"])
self.assertIn("retroarch", platform["cores"])
# RetroArch runs the libretro cores some device image installs, not
# every libretro profile.
self.assertNotIn("retroarch", platform["cores"])
self.assertIn("beetle_psx", platform["cores"])
registry = yaml.safe_load((root / "platforms" / "_registry.yml").read_text())
self.assertNotIn("cores", registry["platforms"]["rocknix"])
+3 -3
View File
@@ -548,12 +548,12 @@ class OpenmsxOnEachPlatform(unittest.TestCase):
"""openMSX finds system ROMs by SHA1 in share/systemroms below its user
directory and in every filepool entry. RetroBat sets OPENMSX_HOME to
bios/openmsx, RetroPie links share/systemroms of its user directory to
BIOS/openmsx, Batocera and KNULLI add bios/Machines and bios/openmsx to
the filepool and list their own files in bios/openmsx."""
BIOS/openmsx, Batocera adds bios/Machines and bios/openmsx to the
filepool and lists its own files in bios/openmsx. KNULLI's generator
does the same, but no KNULLI board builds openmsx."""
EXPECTED = {
"batocera": {"share/systemroms": ("", "openmsx")},
"knulli": {"share/systemroms": ("", "openmsx")},
"retrobat": {"": ("", "openmsx")},
"retropie": {"share/systemroms": ("", "openmsx")},
}
+142 -24
View File
@@ -97,6 +97,13 @@ class FailedRequestsStopTheScrape(unittest.TestCase):
_refuse()
return _Body(b"BR2_PACKAGE_BATOCERA_TARGET_X86_64=y\n"
b"BR2_PACKAGE_BATOCERA_SYSTEM=y\n")
if "/git/trees/" in url:
return _Body(b'{"tree": [], "truncated": false}')
if url.endswith("retroarch.mk"):
return _Body(b"RETROARCH_CONF_OPTS = --enable-networking\n")
if url.startswith("https://raw.githubusercontent.com/") and url.count("/") == 6:
return _Body(b'source "$BR2_EXTERNAL_BATOCERA_PATH/'
b'package/batocera/core/batocera-system/Config.in"\n')
if url.endswith("Config.in"):
return _Body(b"config BR2_PACKAGE_BATOCERA_SYSTEM\n"
b"\tselect BR2_PACKAGE_MGBA\n\n"
@@ -240,6 +247,61 @@ class BatoceraDerivedFlags(unittest.TestCase):
self.assertIn("BR2_PACKAGE_DUCKSTATION_LEGACY", r16)
class CoreDownloaderReach(unittest.TestCase):
"""Batocera and KNULLI build RetroArch with its core downloader, shown in
the menu, and its compiled buildbot URL serves Linux x86_64, i386 and
armv7 hard-float alone (config.def.h:743-749, 1855-1864): a board of
those runs any core of the buildbot list, an aarch64 one none."""
def test_each_architecture_reads_its_buildbot_list(self):
from scraper.targets.batocera_targets_scraper import buildbot_target # noqa: PLC0415
self.assertEqual(buildbot_target({"BR2_x86_64"}), "linux-x86_64")
self.assertEqual(buildbot_target({"BR2_arm", "BR2_ARM_CPU_ARMV7A"}), "linux-armhf")
self.assertIsNone(buildbot_target({"BR2_arm", "BR2_ARM_CPU_ARMV7A", "BR2_ARM_EABI"}))
self.assertIsNone(buildbot_target({"BR2_arm", "BR2_arm1176jzf_s"}))
self.assertIsNone(buildbot_target({"BR2_aarch64"}))
def test_a_build_or_a_blank_url_turns_it_off(self):
from scraper.targets.batocera_targets_scraper import downloader_disabled # noqa: PLC0415
self.assertIsNone(downloader_disabled("--enable-networking", {
"a/retroarch.cfg": 'menu_show_core_updater = "false"\n'}))
self.assertIn("HAVE_LAKKA=1", downloader_disabled("make HAVE_LAKKA=1", {}))
with self.assertRaises(RuntimeError):
downloader_disabled("", {"a/retroarch.cfg": 'core_updater_buildbot_cores_url = ""\n'})
def test_a_board_that_downloads_gets_the_buildbot_list(self):
from common import load_target_config # noqa: PLC0415
root = str(REPO_ROOT / "platforms")
x86 = set(load_target_config("retroarch", "linux-x86_64", root) or ())
armhf = set(load_target_config("retroarch", "linux-armhf", root) or ())
self.assertLessEqual(x86, set(load_target_config("batocera", "x86_64", root) or ()))
self.assertLessEqual(armhf, set(load_target_config("batocera", "rk3288", root) or ()))
self.assertFalse(x86 <= set(load_target_config("batocera", "bcm2711", root) or ()))
self.assertLessEqual(armhf, set(load_target_config("knulli", "r16", root) or ()))
def test_the_platform_names_what_its_boards_install(self):
import yaml # noqa: PLC0415
from common import load_target_config # noqa: PLC0415
root = str(REPO_ROOT / "platforms")
for platform in ("batocera", "knulli"):
config = yaml.safe_load((REPO_ROOT / "platforms" / f"{platform}.yml").read_text())
targets = yaml.safe_load(
(REPO_ROOT / "platforms" / "targets" / f"{platform}.yml").read_text())["targets"]
fetched = set()
for entry in targets.values():
if entry.get("downloads"):
fetched |= load_target_config("retroarch", entry["downloads"], root) or set()
if entry["architecture"] == "aarch64":
self.assertNotIn("downloads", entry)
with self.subTest(platform=platform):
self.assertEqual(set(config["installable_cores"]), fetched - set(config["cores"]))
class KnulliPerBoard(unittest.TestCase):
"""KNULLI builds per board on Batocera's model from its own tree:
configs/knulli-*.board, package/system/knulli-system/Config.in and
@@ -254,7 +316,7 @@ class KnulliPerBoard(unittest.TestCase):
"BR2_PACKAGE_BATOCERA_TARGET_H700=y\n"
),
"configs/knulli-r16.board": (
"include knulli-board.common\n\nBR2_arm=y\n"
"include knulli-board.common\n\nBR2_arm=y\nBR2_cortex_a7=y\n"
"BR2_PACKAGE_BATOCERA_TARGET_SUNXI_R16=y\n"
),
"configs/knulli-h700_armhf_libs.board": (
@@ -262,6 +324,15 @@ class KnulliPerBoard(unittest.TestCase):
"BR2_arm=y\nBR2_PACKAGE_BATOCERA_TARGET_H700=y\n"
),
"configs/knulli-h700_armhf_libs.packages": "BR2_PACKAGE_SDL2=y\n",
# The root Config.in sources KNULLI's system and Batocera's packages
# from the batocera submodule, where a package selects another.
"Config.in": (
'source "$BR2_EXTERNAL_KNULLI_PATH/package/system/knulli-system/Config.in"\n'
'source "$BR2_EXTERNAL_KNULLI_PATH/batocera/package/batocera/emulators/'
'ppsspp/Config.in"\n'
'#source "$BR2_EXTERNAL_KNULLI_PATH/package/utils/off/Config.in"\n'
'source "$BR2_EXTERNAL_KNULLI_PATH/package/utils/vtree/Config.in"\n'
),
"package/system/knulli-system/Config.in": (
"if BR2_PACKAGE_BATOCERA_TARGET_H700 || \\\n"
" BR2_PACKAGE_BATOCERA_TARGET_A133\n"
@@ -282,6 +353,8 @@ class KnulliPerBoard(unittest.TestCase):
" mgba: {requireAnyOf: [BR2_PACKAGE_LIBRETRO_MGBA]}\n"
"psp:\n emulators:\n ppsspp:\n"
" ppsspp: {requireAnyOf: [BR2_PACKAGE_PPSSPP]}\n"
"pspassets:\n emulators:\n libretro:\n"
" assets: {requireAnyOf: [BR2_PACKAGE_PPSSPP_ASSETS]}\n"
),
}
@@ -314,6 +387,23 @@ class KnulliPerBoard(unittest.TestCase):
return _Body(json.dumps([{"name": n} for n in names]))
if url == f"{api}buildroot?ref={pin}":
return _Body('{"sha": "c2d7985b"}')
if url == f"{api}batocera?ref={pin}":
return _Body('{"sha": "487014d9"}')
if "/git/trees/" in url:
# A board config that hides the downloader leaves its URL.
cfg = "board/r16/retroarch.cfg" if "knulli-linux" in url else "x"
return _Body(json.dumps({"tree": [{"path": cfg}], "truncated": False}))
if url == f"{raw}board/r16/retroarch.cfg":
return _Body('menu_show_core_updater = "false"\n')
if url == f"{raw}package/retroarch/retroarch/retroarch.mk":
return _Body("RETROARCH_CONF_OPTS = --enable-networking\n")
if url == ("https://raw.githubusercontent.com/batocera-linux/batocera.linux/"
"487014d9/package/batocera/emulators/ppsspp/Config.in"):
return _Body("config BR2_PACKAGE_PPSSPP\n\tselect BR2_PACKAGE_PPSSPP_ASSETS\n")
if url == f"{raw}package/utils/vtree/Config.in":
raise urllib.error.HTTPError(url, 404, "Not Found", {}, None)
if url.endswith("/c2d7985b/arch/Config.in.arm"):
return _Body("config BR2_cortex_a7\n\tselect BR2_ARM_CPU_ARMV7A\n")
if url.startswith("https://raw.githubusercontent.com/knulli-cfw/buildroot/c2d7985b/"):
return _Body("config BR2_aarch64\n\tselect BR2_ARCH_IS_64\n")
path = url.removeprefix(raw)
@@ -330,11 +420,14 @@ class KnulliPerBoard(unittest.TestCase):
self.assertEqual(sorted(data["targets"]), ["h700", "r16"])
# The images name what a card's boot/knulli.board holds.
self.assertEqual(data["targets"]["h700"],
{"architecture": "aarch64", "cores": ["mgba", "ppsspp"],
{"architecture": "aarch64", "cores": ["assets", "mgba", "ppsspp"],
"images": ["rg35xx-plus", "rg35xx-sp"]})
# An armv7 hard-float board's RetroArch installs the armhf buildbot list.
self.assertEqual(data["targets"]["r16"],
{"architecture": "arm", "cores": ["mgba"], "images": ["miyoo-a30"]})
self.assertTrue(all(pin in url or "buildroot/c2d7985b" in url for url in seen), seen)
{"architecture": "arm", "cores": ["mgba"], "images": ["miyoo-a30"],
"downloads": "linux-armhf"})
self.assertTrue(all(pin in url or "buildroot/c2d7985b" in url
or "487014d9" in url for url in seen), seen)
class RocknixPerDevice(unittest.TestCase):
@@ -371,35 +464,60 @@ class RocknixPerDevice(unittest.TestCase):
)
def test_each_device_gets_its_own_cores(self):
from scraper.targets.rocknix_targets_scraper import ( # noqa: PLC0415
core_names,
parse_device_cores,
)
from scraper.targets.rocknix_targets_scraper import parse_device_cores # noqa: PLC0415
h700 = core_names(*parse_device_cores(self.PACKAGE_MK, "H700", "aarch64", True))
self.assertTrue({"drastic", "gpsp", "duckstation", "beetle_psx", "hatari",
"81", "neocd"} <= set(h700))
self.assertFalse({"aethersx2", "dolphin"} & set(h700))
no32 = core_names(*parse_device_cores(self.PACKAGE_MK, "H700", "aarch64", False))
self.assertNotIn("gpsp", no32)
amd64 = core_names(*parse_device_cores(self.PACKAGE_MK, "AMD64", "x86_64", False))
self.assertIn("dolphin", amd64)
self.assertNotIn("drastic", amd64)
libretro, standalone = parse_device_cores(self.PACKAGE_MK, "H700", "aarch64", True)
self.assertTrue({"gpsp-lr", "duckstation-lr", "beetle-psx-lr", "81-lr",
"neocd_lr"} <= set(libretro))
self.assertTrue({"drastic-sa", "hatarisa"} <= set(standalone))
self.assertFalse({"aethersx2-sa", "dolphin-sa"} & set(standalone))
libretro, _ = parse_device_cores(self.PACKAGE_MK, "H700", "aarch64", False)
self.assertNotIn("gpsp-lr", libretro)
_, standalone = parse_device_cores(self.PACKAGE_MK, "AMD64", "x86_64", False)
self.assertIn("dolphin-sa", standalone)
self.assertNotIn("drastic-sa", standalone)
def test_a_package_names_the_cores_it_installs(self):
"""vice-lr builds ten EMUTYPEs: read as the package name, vice_x64
and its siblings reached no ROCKNIX device."""
from scraper.targets.rocknix_targets_scraper import installed_cores # noqa: PLC0415
vice = ("makeinstall_target() {\n mkdir -p ${INSTALL}/usr/lib/libretro\n"
" cp -a built/vice_x128_libretro.so ${INSTALL}/usr/lib/libretro\n"
" cp -a built/vice_x64_libretro.so ${INSTALL}/usr/lib/libretro\n}\n")
self.assertEqual(installed_cores(vice, "aarch64", True), ["vice_x128", "vice_x64"])
renamed = ("makeinstall_target() {\n"
" cp -a mednafen_psx_libretro.so ${INSTALL}/usr/lib/libretro/beetle_psx_libretro.so\n"
" cp -a fake08_libretro.{so,info} $INSTALL/usr/lib/libretro\n}\n")
self.assertEqual(installed_cores(renamed, "aarch64", True), ["beetle_psx", "fake08"])
pcsx = ("makeinstall_target() {\n"
" case ${TARGET_ARCH} in\n"
" aarch64|x86_64) cp -a ../pcsx_rearmed_libretro.so ${INSTALL}/usr/lib/libretro ;;\n"
" arm) cp -a ../pcsx_rearmed_libretro.so ${INSTALL}/usr/lib/libretro/pcsx_rearmed32_libretro.so ;;\n"
" esac\n"
' if [ "${TARGET_ARCH}" = "aarch64" ] && [ "${ENABLE_32BIT}" = "true" ]; then\n'
" cp -a x/usr/lib/libretro/pcsx_rearmed32_libretro.so ${INSTALL}/usr/lib/libretro\n"
" fi\n}\n")
self.assertEqual(installed_cores(pcsx, "aarch64", True), ["pcsx_rearmed", "pcsx_rearmed32"])
self.assertEqual(installed_cores(pcsx, "aarch64", False), ["pcsx_rearmed"])
self.assertEqual(installed_cores(pcsx, "x86_64", True), ["pcsx_rearmed"])
with self.assertRaises(RuntimeError):
installed_cores('makeinstall_target() {\n if [ "${DEVICE}" = "H700" ]; then\n'
" cp a_libretro.so ${INSTALL}/usr/lib/libretro\n fi\n}\n",
"aarch64", True)
with self.assertRaises(RuntimeError):
installed_cores("makeinstall_target() {\n :\n}\n", "aarch64", True)
def test_a_standalone_package_keeps_both_spellings(self):
"""vice-standalone answers to vice-sa only, as scummvm-standalone to
scummvm-sa: stripped to vice, every targeted pack left them out
while the untargeted one, read from the platform scraper, kept them."""
from scraper.rocknix_scraper import standalone_emulators # noqa: PLC0415
from scraper.targets.rocknix_targets_scraper import ( # noqa: PLC0415
core_names,
parse_device_cores,
)
from scraper.targets.rocknix_targets_scraper import parse_device_cores # noqa: PLC0415
libretro, standalone = parse_device_cores(self.PACKAGE_MK, "SM8550", "aarch64", False)
names = set(core_names(libretro, standalone))
_, standalone = parse_device_cores(self.PACKAGE_MK, "SM8550", "aarch64", False)
names = set(standalone_emulators(standalone))
self.assertTrue({"aethersx2-sa", "aethersx2", "dolphin-sa", "dolphin"} <= names)
self.assertTrue(set(standalone_emulators(standalone)) <= names)
def test_a_metapackage_names_the_cores_it_installs(self):
from scraper.targets.rocknix_targets_scraper import metapackage_cores # noqa: PLC0415