feat: index arcade sets by bios parent

This commit is contained in:
Abdessamad Derraz committed 2026-11-03 16:36:26 +01:00
1 parent dc337c893c
commit 249b189ec9
17 files changed
+1045

No files matched your search

+1
View File
@@ -0,0 +1 @@
{"bios":{"astrocde":["280zzap","adssm","amazmaze","artiduel","artiduelp1","artiduelp2","artiduelp3","astrobat","ballypin","baseball","biorhy","blckjack","bowling","brckyard","bstdroid","buylove","candyman","checkers","claim","colbook","conan","conanp","cosmicrd","cosmicrd35","cosmicrd36","cosmicrda","cosmicrdb","dealdemo","dogpatch","eightday","fawn","fawna","feelfine","football","galactic","galacticp","golddig","golddiga","golddigb","grandprx","hardday","icbmatk","icbmatkp","letitbe","life","lttrmtch","matk","mazeman","mazemanp","mlangman","mscandy","muncher","musicmak","musicmakp","musicmakp2","nodie","pacmaze","paperbck","pirate","rainbow2","seadevil","seawolf","sheloves","sloshed","sneaky","soccer","soccera","soccera2","soccera3","soccera4","soccerp","solarcnq","solarcnqp","solarcnqp28","solarcnqp39","songs","songsa","songsu","spacefrt","spacefrtp","spaceinv","spaceinva","spaceinvf","spaceinvj","spaceinvp","spaceinvt","spedmath","sslope","starbttl","stweek","tests","treasure","treasurep","vidstory","war","wizard","yesternv","yesterv","ziggyjx","ziggymo"],"atarisy1":["indytemp","indytemp2","indytemp3","indytemp4","indytempc","indytempd","marble","marble2","marble3","marble4","marble5","peterpak","reliefs1","roadblst","roadblst1","roadblst2","roadblst3","roadblstc","roadblstc1","roadblstcg","roadblstg","roadblstg1","roadblstg2","roadblstgu","roadblstj","roadblstp","roadrunn","roadrunn1","roadrunn2"],"bubsys":["bs_gradius","bs_gwarrior","bs_rf2","bs_twinbee"],"cchip":["bonzeadv","bonzeadvo","bonzeadvp","bonzeadvp2","bonzeadvu","jigkmgri","jigkmgria","jumping","jumpinga","jumpingi","megablst","megablstj","megablstu","opwolf","opwolfa","opwolfb","opwolfj","opwolfjsc","opwolfu","rbisland","rbislandb","rbislande","rbislando","rbislands","superman","supermanj","supermanp","supermans","supermanu","volfied","volfiedj","volfiedjo","volfiedo","volfiedu","volfieduo"],"channelf":["alieninv","backgamm","builtin","casinop","clrorgan","democrt1","democrt2","dodgeit","dragrace","drawpkr","dsrtfox","hangman","lightsout","magicnum","maze","mazea","memory","mthquiz1","mthquiz2","muehle","multicrt","multicrto","ordtvlng","pfootbll","pinbacvbyred","pinball","pinballa","ratmal","robotwar","robotwarp","schach","slotmchn","sonrsrch","spacewar","spitfire","spitfirep","tctactoe","vblckjck","werbetxt","whizball","wrdlf"],"coleco":["1942sgm","1on1","2010","2010p","421","advisland","ae","aerial","alcazar","alpharoid","alphazoo","amazing","amzsnake","antarct","antarcta","antarctj","antarctk","antarctp","antiair","apshai","aquatack","aquatacka","arkadion","armagedn","arnodash","artduel","ascend","asteroids","astrododge","astroinv","awass","aztecchal","bankbuild","bankpanic","barbarricade","barnbuster","battlot","bbears","bcquest","bcquest2","bcquest2ca","bcquesta","bcquestca","bdash","bdashp","beachead","beamridr","bejeweled","bigcatch","blackonyx","blockrun","bloktris","bofhoth","bokosukawars","bombking","bomblast","bomblast2","boomingboy","boothill","bootskell","bosconian","bountybob","boxxle","brainstr","breakout","breakout23j","breakout23r","buckrogsgm","bugsbots","bullmighty","bustabobble","bustabobble2","bustinout0","bustinout1","bustinout2","bustinout3","cabbage","cabbagep1","cabbagep2","cabbagepk","cabbshow","cacorm","camknights","camknightsb","campaign","canamini","caosbgns","carfight","castelo","castle","castleex","castlek","castlet","catsos","caverns","cavfight","cavit","ccharlie","ccredux","ccrisis","chackpop","challenger","chesschal","chprowres","chucknor","cicehockey","cix","clock","coco","coldblood","coltennis","comicbakery","comicbakeryo","comspace","cotn","crackpots","cracky","crazychickyjr","crazychickyjr2","cso","csok","csot","cure","cvjoytest","cye","dacman","dacman2","dacmana","dambust","dambustca","dangrtower","datrolls","dbreakout","ddash","ddash2","deathrace","decathln","deepdngadv","deflektor","demoattackna","demonattack","desolator","destruct","destructsce","digdugsgm","dkarcade","dkjrarcade","dkongarcade","dkongjrsgm","dkongsgm","dlairsgm","dncfntsy","donuts","drgnfire","drol","drolk","drolt","drseuss","eaclassics","easterbunny","ed2083","eggerland","elecquart","elevaction","elevactionsgm","enerquiz","evolutio","explodingfoot","eyebrawls","f2ffootball","f2fhockey","f2fsoccer","facemakr","fallguy","fantasy","fathom","ffactory","ffreddy","finaltst","fireman","flapbrd","flipslip","flora","flux","fortune","fracfevr","frantic","frogfeast","frogger2","frontlinsgm","frostbite","fruitpanic","fuzzywomp","gamepack1","gamepack2","gamester81","gamewatch","gblaster","gerbils","ggarden","ghost","ghostbst","ghostbst1","ghostbsts","ghostbstse","ghostrap","gnz","golgo13","golgo13c","golgo13sc","goonies","gooniesa","gooniesb","gooniesc","gooniesoc","gooniesttc","gpvic20","gpworld","gpworlda","gpworldt","guardic","guardica","gulkave","gulkavea","gulkavek","gunfright","guntus","gustbust","hamsterww","hamurabi","hardhatmack","hazzard","hchumy","heist","helifire","heroesar","holeone","hopman","hrally","huojiche","icemuncher","iceLine truncated
+1
View File
@@ -0,0 +1 @@
{"bios":{"airlbios":["alpilot","alpilotj"],"awbios":["anmlbskt","anmlbskta","basschal","basschalo","blokpong","claychal","demofist","dirtypig","dolphin","fotns","ftspeed","ggisuka","ggx15","kofnw","kofnwj","kofxi","kov7sprt","maxspeed","mslug6","ngbc","ngbcj","rangrmsn","rumblef","rumblef2","rumblefp","rumblf2p","salmankt","samsptk","sprtshot","sushibar","vfurlong","waidrive","xtrmhnt2","xtrmhunt"],"f355bios":["f355twin","f355twinp","f355twn2"],"hod2bios":["hotd2","hotd2e","hotd2o","hotd2p"],"naomi":["18wheelr","18wheelro","18wheelrt","18wheels","18wheelu","alienfnt","alienfnta","anpanman2","anpanman2a","asndynmt","asndynmto","ausfache","azumanga","bdrdown","capsnk","capsnka","capsnkb","cfield","chocomk","cleoftp","confmiss","crackndj","crakndj2","crzytaxi","csmash","csmasho","cspike","cvs2","cvs2mf","cvsgd","deathcox","deathcoxj","deathcoxo","derbyo2k","derbyoc","derbyoc2","derbyocw","doa2","doa2a","doa2m","dragntr","dragntr2","dragntr3","dragntra","drbyocwa","drbyocwb","drbyocwc","drbyocwt","dybb99","dybbnao","dygolf","dygolfp","ggx","ggxx","ggxxac","ggxxrl","ggxxrlo","ggxxsla","gram2000","gundmct","gundmgd","gundmxgd","gunsur2","gunsur2j","gwing2","hmgeo","hopper","ikaruga","illvelo","inunoos","jambo","jingystm","karous","keyboard","kick4csh","kurucham","lupinsho","lupinshoo","luptype","mamonoro","manicpnc","marstv","mazan","mazanj","mazanu","mbaa","mbaao","meltyb","meltybld","meltyblo","meltybo","mj1","moeru","mok","monkeyba","mushi2eo","mushi2k3","mushi2k4","mushi2k5","mushi2k61","mushi2k62","mushik2e","mushik2k","mushik4e","mushik4t","mushikc","mushike","mushikeo","mushikep","mushikk","mvsc2","mvsc2u","ninjaslt","ninjaslta","ninjasltj","ninjasltu","ntvmys","oinori","otrigger","pjustic","pjustica","pokasuka","pstone","pstone2","pstone2b","psyvar2","puyoda","puyofev","puyofevj","puyofevp","qmegamis","quizqgd","radirgy","radirgyn","radirgyo","rhytngk","ringout","ringouto","samba","samba2k","sambaa","sambap","senko","senkoo","senkosp","sfz3ugd","sgtetris","shaktam","shaktamb","shaktmsp","shangril","shikgam2","shootopl","shootpl","shootplm","shootplmp","shors2k1","shors2k2","shors2k2l","shors2k2m","shors2k2s","shorse","shorsel","shorsem","shorsep","shorsepb","shorsepl","shorsepm","shorsepr","shorseps","sl2007","slasho","slashout","smarinef","smlg99","spawn","spkrbtl","sprtjam","ss2005","ss2005o","sstrkfgt","sstrkfgta","starseek","suchie3","takoron","tduno","tduno2","tetkiwam","tokyobus","totd","totdo","toukon4","toyfight","trgheart","trghearto","trizeal","undefeat","usagiym","vathlete","virnba","virnbao","virnbap","virnbapa","vonot","vs2_2k","vs2_2ko","vtenis2c","vtennis","vtennis2","vtennisg","wccf116","wccf1dup","wccf212e","wccf234j","wccf310j","wccf322e","wccf331e","wccf331j","wccf341j","wccf400j","wccf420e","wldkicks","wldkicksj","wldkickspj","wldkickspw","wldkicksu","wrungp","wrungpo","wsbbgd","wwfroyal","zerogu2","zombrvn","zombrvne","zombrvno","zunou"],"naomi2":["beachspi","clubk2k3","clubk2kp","clubk2kpa","clubkcyc","clubkcyco","clubkprz","clubkpzb","clubkpzbp","clubkrt","clubkrta","clubkrtc","clubkrto","inidv3ca","inidv3cy","initd","initdexp","initdexpo","initdo","initdv2e","initdv2j","initdv2ja","initdv2jo","initdv3e","initdv3j","initdv3jb","kingrt66","kingrt66p","soulsurf","vf4","vf4b","vf4cart","vf4evo","vf4evoa","vf4evob","vf4evoct","vf4o","vf4tuned","vf4tuneda","vf4tunedd","vstrik3c","vstrik3co","wldrider"],"segasp":["bingogal","bingogals","brickppl","btlracer","dinoki25","dinoki4","dinokich","dinoking","dinokior","galilfac","huhimage","isshoni","kingyo","kingyoch","loveber3","loveber3cn","lovebero","lovebery","magicpop","manpuku","monomedal","ochaken","puyomedal","shateki","tetgiano","tetgiant","unomedal","westdrmg"]},"chd":["azumanga","bdrdown","beachspi","cfield","chocomk","cleoftp","clubkcyc","clubkcyco","confmiss","cvs2","cvs2mf","cvsgd","dinoki25","dinoki4","dinokich","dinokior","dragntr","dragntr2","dragntr3","dragntra","dygolf","ggxx","ggxxac","ggxxrl","ggxxrlo","ggxxsla","gundmgd","gundmxgd","huhimage","ikaruga","inidv3ca","inidv3cy","initd","initdexp","initdexpo","initdo","initdv2e","initdv2j","initdv2ja","initdv2jo","initdv3e","initdv3j","initdv3jb","jingystm","karous","keyboard","kingyoch","kurucham","loveber3","loveber3cn","lupinsho","lupinshoo","luptype","meltyb","meltybld","meltyblo","meltybo","mj1","moeru","mok","monkeyba","monomedal","psyvar2","puyofev","puyofevj","quizqgd","radirgy","radirgyo","senko","senkoo","senkosp","sfz3ugd","shaktam","shaktamb","shaktmsp","shikgam2","slashout","spkrbtl","sprtjam","ss2005","ss2005o","starseek","takoron","tetgiano","tetkiwam","trgheart","trghearto","trizeal","undefeat","usagiym","vathlete","vf4","vf4b","vf4evo","vf4evoa","vf4evob","vf4o","vf4tuned","vf4tuneda","vf4tunedd","vtennis2","vtennisg","wccf116","wccf1dup","wccf212e","wccf234j","wccf310j","wccf322e","wccf331e","wccf331j","wccf341j","wccf400j","wccf420e","wsbbgd"],"core_version":"v2.7-90-ge88b0ce","counts":{"bios_roots_used":7,"bios_sets":12,"chd_games":114,"games":349,"games_witLine truncated
+1
View File
@@ -0,0 +1 @@
{"bios":{"3dobios":["orbatak"],"airlbios":["alpilot"],"aleck64":["11beat","doncdoon","hipai","hipai2","kurufev","mayjin3","mtetrisc","srmvs","srmvsa","starsldr","twrshaft","vivdolls"],"alg3do":["md23do","sht3do"],"alg_bios":["aplatoon","crimep2","crimep2_11","crimepat","crimepat_10","crimepat_12","crimepat_14","fastdraw","fastdraw_130","gallgall","gallgall_21","lastbh","lastbh_006","maddog","maddog2","maddog2_100","maddog2_110","maddog2_202","maddog_202","maddoga","marvice","marvice100hz","spacepir","spacepir_14","tierras100hz","wsjr","wsjr_15","zortonbr","zortonbr_100"],"allied":["circa33","disco79","erosone","foathens","heartspd","hoedown","royclark","starshot","suprpick","takefive","thndbolt"],"ar_bios":["ar_airh","ar_airh2","ar_argh","ar_blast","ar_bowl","ar_dart","ar_dart2","ar_dlta","ar_fast","ar_fasta","ar_ldrb","ar_ldrba","ar_ldrbb","ar_ninj","ar_ninj2","ar_pm","ar_rdwr","ar_sdwr","ar_sdwr2","ar_socc","ar_spot","ar_sprg","ar_xeon"],"aristmk5":["adonis","adonisa","adonisce","adonisu","alchemst","baddog","blackpnt","blackpntu","bootsctn","bootsctnu","bootsctnua","bootsctnub","bootsctnuc","bootsctnud","bparty","bpartya","bpartyb","bumblbug","bumblbugq","bumblbugu","bumblbugua","bumblbugub","buttdeli","canrose","cashcat","cashcata","cashcatnz","cashcham","cashchama","cashchamnz","cashchamu","cashcra5","cashcra5a","chariotc","chariotcpe","chariotcu","checkma5","chickna5","chickna5q","chickna5u","chickna5ua","chickna5v","coralrc2","cuckoo","cuckoou","diamdest","diamdove","dimtouch","dmdfever","dolphntr","dolphntra","dolphntrb","dolphntrce","dolphntrcea","dolphntrceb","dolphntrq","dolphntru","dolphntrua","dreamwv","drgneye","dstbloom","dstblooma","dynajack","dynajacku","eforsta5","eforsta5u","eforsta5ua","eforstce","eforstcea","eldorda5","fastfort","fortellr","fortfvr","gamblra5","geisha","geishanz","genmagi","glizrdce","gnomeatw","goldenra","goldpyr","goldpyru","goldpyrua","honeypot","honktonk","hrtthrob","incasun","incasunnz","incasunsp","incasunu","incasunua","incasunv","indrema5","indrema5q","indrema5v","jkrpkra5","jumpbean","jumpjoey","jumpjoeyu","jungjuic","kgalah","kgalaha","kgalahce","kgbirda5","kingsran","koalamnt","kookabuk","kyhaton","locoloot","locoloota","locolootnz","locolootq","locolootu","lonewolf","luckyclo","magimask","magimaska","magimaskb","magimaskc","magtcha5","magtcha5a","mammothm","marmagic","marmagicu","marmagicua","mgarden","mgardena","minemine","minemineu","minemineua","minemineub","monmouse","monmousea","montree","mountmon","mountmona","mountmonce","mountmonu","mountmonua","multidrw","mystgard","one4all","orchidms","orchidmsa","orchidmsnz","oscara5","oscara5a","pantmag","pantmaga","partygrs","partygrsa","partygrsb","peaflut","peafluta","pengpay","pengpaya","pengpayb","pengpayc","pengpayce","pengpaycea","pengpayd","pengpaydx","pengpayu","pengpayua","pengpayub","pengpuck","penpir","penpir2","penpira","petshop","petshopa","phantpay","phantpyc","przfight","qcash","qnile","qnilea","qnileb","qnilebr","qnilec","qnilece","qnilecea","qnileceb","qniled","qnilefl","qnilemax","qnilenl","qnilenz","qnilepe","qnileq","qnileu","qnileua","qtbird","rainwarr","rainwarrce","reelpwr","reelrock","reelrockq","retrsam","retrsama","retrsamb","rushrst","sbuk2","sbuk2a","sbuk3","sbuk3a","sldeluxe","slvrwolf","snowcat","sumospin","swhr2","swhr2a","swhr2q","swhr2u","swhr2v","thndh","thndha","thor","topbana","toutango","toutangonl","tritreat","trojhors","trpdlght","trpdlghtu","trstrove","unicornd","unicornda","unicorndnz","unicorndu","venicea5","wafrica","wamazon","wamazona","wamazonv","wcougar","wcougaru","wcougarua","wcougarub","wcougaruc","wcougarud","wcoyote","wikwin","wildbill","wizways","wldangel","wldangelu","wnpost","wthing","wthinga","wtigerc","yukongl5"],"aristmk6":["100lions","100lionsa","15lions","50lionrr","50lions","50lionsa","50lionsu","5drag","5draga","5dragce","5dragnz","5dragsp","5koi","5koia","adonisa6","aduskcm","afterhrs","alchemy","amzqueen","amztempl","antcleo","antcleoa","apocof","arabnga6","arabnga6a","arabnga6u","arish","arwincm","asnprinc","atkgld","beavbuk","betzoo","bigben","bigbena","bigbenb","bigbenq","bigheart","bigred","bigred2","bigreda","blkbrum","blkmust","bluetigr","bmbugs","bobdolly","brazil","brazila","bucaneer","bucaneera","bucaneeru","buckstop","buffalo","buffaloa","buffalou","bushrngr","cactusco","callwld","calyking","canaryrc","centrion","cinhell","cmtonig","corrida","cryspri","cryspria","cryspriu","csd","csda","csdce","csdjc","csdq","csdsp","csret","dagenta6","dbldolph","dbldolphq","dbldolphu","deepfrz","deepfrzu","deltabel","deltaqn","di4ever","diamhrts","dimeye","dimeyece","dimeyeu","dinosa6","doltra6","doltra6nz","drgnlord","drgnmgic","drmcatch","drmcatcha","drmred","dstgold","eaglrock","eastland","efce","eggsrccm","enchantr","encspell","eott","estrike","firednc","firednca","flamolce","flamoljc","followst","fortath","fortking","frogwld","gambjack","gbmoney","geishaa6","geishaa6a","geishaa6b","geishajc","gldgong","gldgongq","gldgongu","gldnflc","goape","gogreen"Line truncated
+1
View File
@@ -0,0 +1 @@
{"bios":{"3dobios":["orbatak"],"airlbios":["alpilot"],"aleck64":["11beat","doncdoon","hipai","hipai2","kurufev","mayjin3","mtetrisc","srmvs","srmvsa","starsldr","twrshaft","vivdolls"],"alg3do":["md23do","sht3do"],"alg_bios":["aplatoon","crimep2","crimep2_11","crimepat","crimepat_10","crimepat_12","crimepat_14","fastdraw","fastdraw_130","gallgall","gallgall_21","lastbh","lastbh_006","maddog","maddog2","maddog2_100","maddog2_110","maddog2_202","maddog_202","maddoga","marvice","marvice100hz","spacepir","spacepir_14","tierras100hz","wsjr","wsjr_15","zortonbr","zortonbr_100"],"allied":["circa33","disco79","erosone","foathens","heartspd","hoedown","royclark","starshot","suprpick","takefive","thndbolt"],"ar_bios":["ar_airh","ar_airh2","ar_argh","ar_blast","ar_bowl","ar_dart","ar_dart2","ar_dlta","ar_fast","ar_fasta","ar_ldrb","ar_ldrba","ar_ldrbb","ar_ninj","ar_ninj2","ar_pm","ar_rdwr","ar_sdwr","ar_sdwr2","ar_socc","ar_spot","ar_sprg","ar_xeon"],"aristmk5":["adonis","adonisa","adonisce","adonisu","alchemst","baddog","blackpnt","bootsctn","bootsctnu","bootsctnua","bootsctnub","bparty","bpartya","bumblbug","bumblbugql","bumblbugu","bumblbugua","buttdeli","canrose","cashcat","cashcata","cashcatnz","cashcham","cashchama","cashchamnz","cashchamu","cashcra5","cashcra5a","chariotc","chariotcv","checkma5","chickna5","chickna5ql","chickna5u","chickna5ua","chickna5v","coralrc2","cuckoo","cuckoou","diamdest","diamdove","dimtouch","dmdfever","dolphntr","dolphntra","dolphntrb","dolphntrce","dolphntrcea","dolphntrceb","dolphntrql","dolphntru","dreamwv","drgneye","dstbloom","dstblooma","dynajack","dynajacku","eforsta5","eforsta5ce","eforsta5cea","eforsta5u","eforsta5ua","eldorda5","fastfort","fortellr","fortfvr","gambler","geisha","genmagi","glizrdce","gnomeatw","goldenra","goldpyr","goldpyra","goldpyrb","hnktonku","honeypot","incasun","incasunnz","incasunsp","incasunu","incasunua","incasunv","indrema5","indrema5ql","jumpbean","jumpjoey","jungjuic","kgalah","kgalaha","kgalahce","kgbirda5","koalamnt","kookabuk","kyhatonu","locoloot","locoloota","locolootnz","locolootu","lonewolf","luckyclo","magimask","magimaska","magimaskb","magtcha5","magtcha5a","mammothm","marmagic","marmagicu","marmagicua","mgarden","minemine","minemineu","minemineua","monmouse","monmousea","montree","mountmon","mountmona","mountmonce","mountmonu","mountmonua","multidrw","mystgard","one4all","orchidms","orchidmsa","orchidmsnz","oscara5","oscara5a","pantmag","pantmaga","partygrs","partygrsa","partygrsb","peaflut","peafluta","pengpay","pengpaya","pengpayb","pengpayc","pengpayce","pengpayd","pengpayu","pengpayua","pengpayub","pengpuck","penpir","penpir2","penpira","petshop","petshopa","phantpay","phantpyc","przfight","qcash","qnile","qnilea","qnileb","qnilebr","qnilec","qnilece","qnilecea","qnileceb","qniled","qnilefl","qnilemax","qnilenl","qnilenz","qnileql","qnileu","qnileua","qnilev","qtbird","rainwarr","rainwarrce","reelpwr","reelrock","reelrockql","retrsam","retrsama","retrsamb","rushrst","sbuk2","sbuk2a","sbuk3","sbuk3a","sldeluxe","slvrwolf","snowcat","sumospin","swhr2","swhr2a","swhr2u","swhr2v","thndh","thndha","thor","topbana","toutango","toutangonl","tritreat","trojhors","trpdlght","trpdlghtu","trstrove","unicornd","unicornda","unicorndnz","unicorndu","venicea5","wafricau","wamazon","wamazona","wamazonv","wcougar","wcougaru","wcougarua","wcougarub","wcougaruc","wcoyote","wikwin","wildbill","wizways","wldangel","wnpost","wthing","wthinga","wtiger","yukongl5"],"aristmk6":["100lions","100lionsa","15lions","50lionrr","50lions","50lionsa","50lionsu","5drag","5draga","5dragce","5dragnz","5dragsp","5koi","5koia","adonisa6","aduskcm","afterhrs","alchemy","amzqueen","amztempl","antcleo","antcleoa","apocof","arabnga6","arabnga6a","arabnga6u","arish","arwincm","asnprinc","atkgld","beavbuk","betzoo","bigben","bigbena","bigbenb","bigbenq","bigheart","bigred","bigred2","bigreda","blkbrum","blkmust","bluetigr","bmbugs","bobdolly","brazil","brazila","bucaneer","bucaneera","bucaneeru","buckstop","buffalo","buffaloa","buffalou","bushrngr","cactusco","callwld","calyking","canaryrc","centrion","cinhell","cmtonig","corrida","cryspri","cryspria","cryspriu","csd","csda","csdce","csdjc","csdq","csdsp","csret","dblagent","dbldolph","dbldolphq","deepfrz","deepfrzu","deltabel","deltaqn","di4ever","diamhrts","dimeye","dimeyece","dimeyeu","dinosa6","doltra6","doltra6nz","drgnlord","drgnmgic","drmcatch","drmcatcha","drmred","dstgold","eaglrock","eastland","efce","eggsrccm","enchantr","encspell","eott","estrike","firednc","firednca","flamolce","flamoljc","followst","fortath","fortking","frogwld","gambjack","gbmoney","geishaa6","geishaa6a","geishaa6b","geishajc","gldgong","gldgongq","gldgongu","gldnflc","goape","gogreen","gogreena","goldarch","goldinc","goldincu","goldpya6","goldpym","goldsam","grizzly","gwnn","heartgld","heartglda","heartveg","highnoon","hlntroy","hlntroya","hlntroyb","hlntroyc","hlntroyu","holdrma6","holdrma6a","househrt","househrta","incachf","indgldcm","indprnLine truncated
+1
View File
@@ -0,0 +1 @@
{"bios":{"3dobios":["orbatak"],"airlbios":["alpilot"],"aleck64":["11beat","doncdoon","hipai","hipai2","kurufev","mayjin3","mtetrisc","srmvs","srmvsa","starsldr","twrshaft","vivdolls"],"alg3do":["md23do","sht3do"],"alg_bios":["aplatoon","crimep2","crimep2_11","crimepat","crimepat_10","crimepat_12","crimepat_14","fastdraw","fastdraw_130","gallgall","gallgall_21","lastbh","lastbh_006","maddog","maddog2","maddog2_100","maddog2_110","maddog2_202","maddog_202","maddoga","marvice","marvice100hz","spacepir","spacepir_14","tierras100hz","wsjr","wsjr_15","zortonbr","zortonbr_100"],"allied":["circa33","disco79","erosone","foathens","heartspd","hoedown","royclark","starshot","suprpick","takefive","thndbolt"],"ar_bios":["ar_airh","ar_airh2","ar_argh","ar_blast","ar_bowl","ar_dart","ar_dart2","ar_dlta","ar_fast","ar_fasta","ar_ldrb","ar_ldrba","ar_ldrbb","ar_ninj","ar_ninj2","ar_pm","ar_rdwr","ar_sdwr","ar_sdwr2","ar_socc","ar_spot","ar_sprg","ar_xeon"],"aristmk5":["adonis","adonisa","adonisce","adonisu","alchemst","baddog","blackpnt","blackpntu","bootsctn","bootsctnu","bootsctnua","bootsctnub","bootsctnuc","bootsctnud","bparty","bpartya","bpartyb","bumblbug","bumblbugq","bumblbugu","bumblbugua","bumblbugub","buttdeli","canrose","cashcat","cashcata","cashcatnz","cashcham","cashchama","cashchamnz","cashchamu","cashcra5","cashcra5a","chariotc","chariotcpe","chariotcu","checkma5","chickna5","chickna5q","chickna5u","chickna5ua","chickna5v","coralrc2","cuckoo","cuckoou","diamdest","diamdove","dimtouch","dmdfever","dolphntr","dolphntra","dolphntrb","dolphntrce","dolphntrcea","dolphntrceb","dolphntrq","dolphntru","dolphntrua","dreamwv","drgneye","dstbloom","dstblooma","dynajack","dynajacku","eforsta5","eforsta5u","eforsta5ua","eforstce","eforstcea","eldorda5","fastfort","fortellr","fortfvr","gamblra5","geisha","geishanz","genmagi","glizrdce","gnomeatw","goldenra","goldpyr","goldpyru","goldpyrua","honeypot","honktonk","hrtthrob","incasun","incasunnz","incasunsp","incasunu","incasunua","incasunv","indrema5","indrema5q","indrema5v","jkrpkra5","jumpbean","jumpjoey","jumpjoeyu","jungjuic","kgalah","kgalaha","kgalahce","kgbirda5","kingsran","koalamnt","kookabuk","kyhaton","locoloot","locoloota","locolootnz","locolootq","locolootu","lonewolf","luckyclo","magimask","magimaska","magimaskb","magimaskc","magtcha5","magtcha5a","mammothm","marmagic","marmagicu","marmagicua","mgarden","mgardena","minemine","minemineu","minemineua","minemineub","monmouse","monmousea","montree","mountmon","mountmona","mountmonce","mountmonu","mountmonua","multidrw","mystgard","one4all","orchidms","orchidmsa","orchidmsnz","oscara5","oscara5a","pantmag","pantmaga","partygrs","partygrsa","partygrsb","peaflut","peafluta","pengpay","pengpaya","pengpayb","pengpayc","pengpayce","pengpaycea","pengpayd","pengpaydx","pengpayu","pengpayua","pengpayub","pengpuck","penpir","penpir2","penpira","petshop","petshopa","phantpay","phantpyc","przfight","qcash","qnile","qnilea","qnileb","qnilebr","qnilec","qnilece","qnilecea","qnileceb","qniled","qnilefl","qnilemax","qnilenl","qnilenz","qnilepe","qnileq","qnileu","qnileua","qtbird","rainwarr","rainwarrce","reelpwr","reelrock","reelrockq","retrsam","retrsama","retrsamb","rushrst","sbuk2","sbuk2a","sbuk3","sbuk3a","sldeluxe","slvrwolf","snowcat","sumospin","swhr2","swhr2a","swhr2q","swhr2u","swhr2v","thndh","thndha","thor","topbana","toutango","toutangonl","tritreat","trojhors","trpdlght","trpdlghtu","trstrove","unicornd","unicornda","unicorndnz","unicorndu","venicea5","wafrica","wamazon","wamazona","wamazonv","wcougar","wcougaru","wcougarua","wcougarub","wcougaruc","wcougarud","wcoyote","wikwin","wildbill","wizways","wldangel","wldangelu","wnpost","wthing","wthinga","wtigerc","yukongl5"],"aristmk6":["100lions","100lionsa","15lions","50lionrr","50lions","50lionsa","50lionsu","5drag","5draga","5dragce","5dragnz","5dragsp","5koi","5koia","adonisa6","aduskcm","afterhrs","alchemy","amzqueen","amztempl","antcleo","antcleoa","apocof","arabnga6","arabnga6a","arabnga6u","arish","arwincm","asnprinc","atkgld","beavbuk","betzoo","bigben","bigbena","bigbenb","bigbenq","bigheart","bigred","bigred2","bigreda","blkbrum","blkmust","bluetigr","bmbugs","bobdolly","brazil","brazila","bucaneer","bucaneera","bucaneeru","buckstop","buffalo","buffaloa","buffalou","bushrngr","cactusco","callwld","calyking","canaryrc","centrion","cinhell","cmtonig","corrida","cryspri","cryspria","cryspriu","csd","csda","csdce","csdjc","csdq","csdsp","csret","dagenta6","dbldolph","dbldolphq","dbldolphu","deepfrz","deepfrzu","deltabel","deltaqn","di4ever","diamhrts","dimeye","dimeyece","dimeyeu","dinosa6","doltra6","doltra6nz","drgnlord","drgnmgic","drmcatch","drmcatcha","drmred","dstgold","eaglrock","eastland","efce","eggsrccm","enchantr","encspell","eott","estrike","firednc","firednca","flamolce","flamoljc","followst","fortath","fortking","frogwld","gambjack","gbmoney","geishaa6","geishaa6a","geishaa6b","geishajc","gldgong","gldgongq","gldgongu","gldnflc","goape","gogreen"Line truncated
+1
View File
@@ -0,0 +1 @@
{"bios":{"neogeo":["2020bb","2020bbh","3countb","alpham2","androdun","aodk","aof","aof2","aof3","bakatono","bjourney","blazstar","breakers","breakrev","bstars","bstars2","burningf","burningh","crsword","cyberlip","doubledr","eightman","fatfursp","fatfury1","fatfury2","fatfury3","fbfrenzy","fightfev","flipshot","galaxyfg","goalx3","gowcaizr","gpilots","gururin","irrmaze","janshin","joyjoy","kabukikl","karnovr","kizuna","kof94","kof95","kof96","kof97","kof98","kotm","kotm2","lastblad","lastbld2","lbowling","legendos","lresort","magdrop2","magdrop3","maglord","maglordh","mahretsu","marukodq","miexchng","minasan","mosyougi","mslug","mslug2","mutnat","nam1975","ncombat","ncommand","neobombe","neocup98","neodrift","neomrdo","ninjamas","overtop","panicbom","pbobble","pgoal","popbounc","pspikes2","pulstar","puzbobb","puzzldpr","puzzledp","quizdai2","quizdais","quizkof","ragnagrd","rbff1","rbff2","rbffspec","ridhero","roboarmy","samsho","samsho2","samsho3","samsho4","savagere","sdodgeb","sengokh","sengoku","sengoku2","shocktr2","shocktro","socbrawl","sonicwi2","sonicwi3","spinmast","ssideki","ssideki2","ssideki3","ssideki4","stakwin","stakwin2","strhoop","superspy","tophuntr","tpgolf","trally","turfmast","twinspri","tws96","viewpoin","wakuwak7","wh1","wh2","wh2j","whp","wjammers","zedblade"]},"chd":[],"core_version":"0.37b5","counts":{"bios_roots_used":1,"bios_sets":1,"chd_games":0,"games":2240,"games_with_bios":128,"games_with_devices":0,"missing_parents":0,"sets":2241},"devices":{},"missing_parents":{},"profile":"mame2000","schema":1,"source":{"commit":"181b42e5da0d2f0c7a2e59244f23633ed594de2e","files":{"metadata/MAME 0.37b5 XML.dat":"3ff1773c4982e21d2301b14708e8257bdefec0db"},"repo":"https://github.com/libretro/mame2000-libretro"}}
+1
View File
@@ -0,0 +1 @@
{"bios":{"acpsx":["jdredd","jdreddb","nbajamex"],"cpzn1":["glpracr","jgakuen","rvschool","sfex","sfexj","sfexp","sfexpj","starglad","tgmj","ts2","ts2j"],"cpzn2":["kikaioh","sfex2","sfex2p","sg2j","shiryu2","strider2","stridr2a","techromn"],"cvs":["8ball","8ball1","cosmos","darkwar","dazzler","goldbug","heartatk","herbiedk","hero","herodk","herodku","hunchbak","hunchbkd","hunchbks","huncholy","logger","outline","radarzn1","radarznt","radarzon","sbdk","spacefrt","superbik","wallst"],"decocass":["castfant","cbdash","cbnj","cbtime","cburnrb2","cburnrub","cdiscon1","cexplore","cfghtice","cflyball","cgraplop","cgraplp2","chwy","clapapa","clapapa2","clocknch","cluckypo","cmissnx","cnights2","cnightst","cppicf","cppicf2","cprobowl","cprogolf","cprosocc","cptennis","cscrtry","cscrtry2","csuperas","csweetht","cterrani","ctisland","ctislnd2","ctislnd3","ctornado","ctsttape","czeroize"],"konamigx":["daiskiss","dragoonj","fantjour","ggreats2","gokuparo","le2","le2u","opengolf","puzldama","racinfrc","rungun2","rushhero","salmndr2","sexyparo","slamdnk2","soccerss","tbyahhoo","tkmmpzdm","tokkae","vsnetscr","winspike"],"megaplay":["mp_gaxe2","mp_sonic"],"megatech":["mt_aftrb","mt_astro","mt_bbros","mt_beast","mt_eswat","mt_fshrk","mt_gaxe","mt_gaxe2","mt_ggolf","mt_gng","mt_gsocr","mt_kcham","mt_lastb","mt_mwalk","mt_mystd","mt_parlg","mt_revsh","mt_shar2","mt_shnbi","mt_smgp","mt_sonic","mt_stbld","mt_tetri","mt_tfor2","mt_tgolf","mt_tlbba","mt_wcsoc"],"neogeo":["2020bb","2020bbh","3countb","alpham2","androdun","aodk","aof","aof2","aof3","bakatono","bangbead","bjourney","blazstar","breakers","breakrev","bstars","bstars2","burningf","burningh","crsword","ctomaday","cyberlip","doubledr","eightman","fatfursa","fatfursp","fatfury1","fatfury2","fatfury3","fbfrenzy","fightfev","fightfva","flipshot","galaxyfg","ganryu","garou","garouo","garoup","goalx3","gowcaizr","gpilots","gururin","irrmaze","janshin","joyjoy","kabukikl","karnovr","kizuna","kof2000","kof2000n","kof94","kof95","kof95a","kof96","kof96h","kof97","kof97a","kof98","kof98n","kof99","kof99a","kof99e","kof99n","kof99p","kotm","kotm2","kotmh","lastblad","lastbld2","lastblda","lbowling","legendos","lresort","magdrop2","magdrop3","maglord","maglordh","mahretsu","marukodq","miexchng","minasan","mosyougi","mslug","mslug2","mslug3","mslug3n","mslugx","mutnat","nam1975","ncombat","ncommand","neobombe","neocup98","neodrift","neomrdo","ninjamas","nitd","overtop","panicbom","pbobbl2n","pbobblen","pbobblna","pgoal","popbounc","preisle2","pspikes2","pulstar","puzzldpr","puzzledp","quizdai2","quizdais","quizkof","ragnagrd","rbff1","rbff2","rbff2a","rbffspec","ridhero","ridheroh","roboarmy","s1945p","samsho","samsho2","samsho3","samsho4","savagere","sdodgeb","sengokh","sengoku","sengoku2","shocktr2","shocktra","shocktro","socbrawl","sonicwi2","sonicwi3","spinmast","ssideki","ssideki2","ssideki3","ssideki4","stakwin","stakwin2","strhoop","superspy","tophuntr","tpgolf","trally","turfmast","twinspri","tws96","viewpoin","wakuwak7","wh1","wh1h","wh2","wh2j","whp","wjammers","zedblade"],"nss":["nss_actr","nss_adam","nss_aten","nss_con3","nss_fzer","nss_lwep","nss_ncaa","nss_rob3","nss_skin","nss_smw","nss_ssoc","nss_sten"],"pgm":["dragwld2","kov","kov115","kovplus","kovsh","orlegend","orlegndc","orlegnde","photoy2k"],"playch10":["pc_1942","pc_bball","pc_bfght","pc_bstar","pc_cntra","pc_cshwk","pc_cvnia","pc_dbldr","pc_ddrgn","pc_drmro","pc_duckh","pc_ebike","pc_ftqst","pc_gntlt","pc_golf","pc_goons","pc_grdue","pc_grdus","pc_hgaly","pc_kngfu","pc_mario","pc_miket","pc_mman3","pc_moglf","pc_mtoid","pc_ngai2","pc_ngai3","pc_ngaid","pc_pinbt","pc_pwbld","pc_pwrst","pc_radr2","pc_radrc","pc_rcpam","pc_rkats","pc_rnatk","pc_rrngr","pc_rygar","pc_sjetm","pc_smb","pc_smb2","pc_smb3","pc_suprc","pc_tbowl","pc_tenis","pc_tkfld","pc_tmnt","pc_tmnt2","pc_trjan","pc_vball","pc_wcup","pc_wgnmn","pc_ynoid"],"skns":["cyvern","galpani4","galpanis","galpans2","gutsn","jjparad2","jjparads","panicstr","puzloopj","puzloopu","puzzloop","ryouran","sarukani","sengekij","sengekis","senknow","teljan","vblokbrk"],"stvbios":["astrass","bakubaku","batmanfr","colmns97","cotton2","cottonbm","danchih","decathlt","diehard","dnmtdeka","ejihon","elandore","ffreveng","fhboxers","findlove","finlarch","gaxeduel","grdforce","groovef","hanagumi","introdon","kiwames","maruchan","mausuke","myfairld","othellos","pblbeach","prikura","puyosun","rsgun","sandor","sassisu","seabass","shanhigw","shienryu","sleague","sokyugrt","sss","suikoenb","thunt","twcup98","vfkids","vfremix","vmahjong","winterht","znpwfv"],"taitofx1":["beastrzb","ftimpcta","gdarius","gdarius2","mgcldate","mgcldtea","psyforce","raystorm","sfchamp"],"tps":["doapp","glprac2j","glprac2l","glpracr2","tondemo"]},"chd":["a51mxr3k","area51","area51mx","biofreak","blitz","blitz2k","blitz99","bm1stmix","bm2ndmix","bm2ndmxa","bm4thmix","bmcompmx","bmcorerm","bmdct","calspeed","carnevil","cryptklr","hmcompm2","hmcompmx","jdredd","jdreddb","kinst","kinst2","mace","maxf_102","mLine truncated
+1
View File
@@ -0,0 +1 @@
{"bios":{"acpsx":["nbajamex"],"coh1000a":["jdredd","jdreddb"],"coh1000c":["glpracr","glpracrj","sfex","sfexa","sfexj","sfexp","sfexpj","sfexpj1","sfexpu1","sfexu","starglad","stargladj","ts2","ts2j","ts2ja","ts2u","ts2ua"],"coh1000t":["ftimpact","ftimpactj","ftimpactu","ftimpcta","gdarius","gdarius2","gdariusj","gdariusu","mgcldate","mgcldtex","psyforce","psyforcej","psyforcex","raystorm","raystormj","raystormo","raystormu","sfchamp","sfchampj","sfchampo","sfchampu"],"coh1002m":["1on1gov","brvblade","brvbladea","brvbladej","brvbladeu","cbaj","cbajbl","doapp","doappk","flamegun","flamegunj","glpracr2","glpracr2j","glpracr2l","glpracr3","glpracr3j","lpadv","mfjump","shngmtkb","tblkkuzu","tondemo","twcupmil"],"coh3002c":["jgakuen","jgakuen1","kikaioh","plsmaswd","plsmaswda","rvschool","rvschoola","rvschoolu","sfex2","sfex2a","sfex2h","sfex2j","sfex2p","sfex2pa","sfex2ph","sfex2pj","sfex2pu","sfex2u","sfex2u1","shiryu2","stargld2","strider2","strider2a","strider2u","techromn","techromna","techromnu","tgmj"],"cvs":["8ball","8ball1","cosmos","darkwar","dazzler","diggerc","goldbug","heartatk","herbiedk","hero","herodk","herodku","hunchbak","hunchbkd","hunchbks","huncholy","logger","outline","radarzn1","radarznt","radarzon","sbdk","spacefrt","superbik","wallst"],"decocass":["cadanglr","castfant","cbdash","cbnj","cbtime","cburnrb2","cburnrub","cdiscon1","cexplore","cfghtice","cfishing","cflyball","cgraplop","cgraplp2","chwy","clapapa","clapapa2","clocknch","cluckypo","cmanhat","cmissnx","cnights2","cnightst","coozumou","cppicf","cppicf2","cprobowl","cprogolf","cprosocc","cptennis","cscrtry","cscrtry2","csdtenis","cskater","csuperas","csweetht","cterrani","ctisland","ctislnd2","ctislnd3","ctornado","ctsttape","czeroize"],"konamigx":["daiskiss","dragoona","dragoonj","fantjour","ggreats2","gokuparo","le2","le2u","opengolf","puzldama","racinfrc","rungun2","rushhero","salmndr2","sexyparo","sexyparoa","slamdnk2","soccerss","tbyahhoo","tkmmpzdm","tokkae","vsnetscr","winspike"],"megaplay":["mp_addams","mp_bio","mp_col3","mp_gaxe2","mp_gslam","mp_gunhe","mp_mazin","mp_shnb3","mp_soni2","mp_sonic","mp_sor2","mp_twc"],"megatech":["mt_aftrb","mt_astro","mt_bbros","mt_beast","mt_eswat","mt_fshrk","mt_gaxe","mt_gaxe2","mt_ggolf","mt_gng","mt_gsocr","mt_kcham","mt_lastb","mt_mwalk","mt_mystd","mt_parlg","mt_revsh","mt_shar2","mt_shnbi","mt_smgp","mt_sonic","mt_stbld","mt_tetri","mt_tfor2","mt_tgolf","mt_tlbba","mt_wcsoc"],"namcoc76":["danceyes","danceyesj","danceyesu","dunkmnia","dunkmniajc","myangel3","pocketrc","primglex","souledge","souledgea","souledgeja","souledgeua","souledgeuc","starswep","starswepj","tekken","tekken2","tekken2a","tekken2b","tekken2jb","tekken2jc","tekken2ua","tekken2ub","tekken2ud","tekkenac","tekkenb","tekkenjb","xevi3dg","xevi3dga","xevi3dgj"],"neogeo":["2020bb","2020bbh","3countb","alpham2","androdun","aodk","aof","aof2","aof3","b2b","bakatono","bangbead","bjourney","blazstar","breakers","breakrev","bstars","bstars2","burningf","burningh","captainbarrel","crswd2bl","crsword","ct2k3sp","cthd2003","ctomaday","cyberlip","cyborgforce","doubled1","doubled1a","doubled1b","doubled1c","doubled1d","doubled1e","doubledr","eightman","fatfursa","fatfursp","fatfury1","fatfury2","fatfury3","fbfrenzy","fightfev","fightfva","flipshot","galaxyfg","ganryu","garou","garouo","garoup","ghostlop","gladmort","goalx3","gowcaizr","gpilots","gururin","hypernoid","ironclad","irrmaze","janshin","joyjoy","kabukikl","karnovr","kf10thep","kf2k2mp","kf2k2mp2","kf2k2pla","kf2k2pls","kf2k3bl","kf2k3bla","kf2k3pl","kf2k3upl","kf2k5uni","kizuna","knightsch","kof10th","kof2000","kof2000n","kof2001","kof2001h","kof2002","kof2003","kof2003d","kof2k4se","kof94","kof95","kof95a","kof96","kof96h","kof97","kof97a","kof97pls","kof98","kof98k","kof98n","kof99","kof99a","kof99e","kof99n","kof99p","kog","kotm","kotm2","kotmh","lastblad","lastbld2","lastblda","lasthope","lbowling","legendos","lresort","magdrop2","magdrop3","maglord","maglordh","mahretsu","marukodq","matrim","miexchng","minasan","mosyougi","mslug","mslug2","mslug2t","mslug3","mslug3n","mslug4","mslug5","mslugx","mutnat","nam1975","ncombat","ncommand","neobombe","neocup98","neodrift","neomrdo","ninjamas","nitd","overtop","panicbom","pbobbl2n","pbobblen","pbobblna","pgoal","pnyaa","popbounc","preisle2","pspikes2","pulstar","puzzldpr","puzzledp","quizdai2","quizdais","quizkof","ragnagrd","rbff1","rbff2","rbff2a","rbffspec","ridhero","ridheroh","roboarmy","rotd","s1945p","samsh5sp","samsho","samsho2","samsho3","samsho4","samsho5","savagere","sdodgeb","sengokh","sengoku","sengoku2","sengoku3","shocktr2","shocktra","shocktro","socbrawl","sonicwi2","sonicwi3","spinmast","ssideki","ssideki2","ssideki3","ssideki4","stakwin","stakwin2","strhoop","superspy","svc","svcboot","svcplus","svcplusa","svcsplus","teot","tophuntr","tpgolf","trally","turfmast","twinspri","tws96","viewpoin","wakuwak7","wh1","wh1h","wh2","wh2j","whp","wjammers","xeno","zedblade","zintrckb","zupapa"],"nss":["nss_actr","nss_Line truncated
+1
View File
@@ -0,0 +1 @@
{"bios":{"acpsx":["jdredd","jdreddb","nbajamex"],"airlbios":["alpilota","alpiltdx"],"aleck64":["11beat","doncdoon","hipai","kurufev","mayjin3","mtetrisc","srmvs","starsldr","twrshaft","vivdolls"],"alg_bios":["aplatoon","crimep2","crimep211","crimepat","fastdraw","gallgall","lastbh","maddog","maddog2","maddog21","maddog22","maddoga","spacepir","wsjr","wsjr15","zortonbr"],"ar_bios":["ar_airh","ar_airh2","ar_bowl","ar_dart","ar_dart2","ar_fast","ar_ldrb","ar_ldrba","ar_ninj","ar_ninj2","ar_rdwr","ar_sdwr","ar_sdwr2","ar_socc","ar_spot","ar_sprg","ar_xeon"],"atarisy1":["indytemp","indytemp2","indytemp3","indytemp4","indytempc","indytempd","marble","marble2","marble3","marble4","marble5","peterpak","roadblst","roadblst1","roadblst2","roadblst3","roadblstc","roadblstc1","roadblstcg","roadblstg","roadblstg1","roadblstg2","roadrunn","roadrunn1","roadrunn2"],"atluspsx":["hvnsgate"],"atpsx":["primrag2"],"awbios":["demofist","dolphin","fotns","ggisuka","kofnw","kofnwj","kov7sprt","ngbc","rangrmsn","rumblef","salmankt","sprtshot","vfurlong","xtrmhnt2","xtrmhunt"],"bctvidbs":["adders","crmaze","crmaze2","crmaze2a","crmaze2d","crmaze3","crmaze3a","crmaze3d","crmazea","crmazed","eyesdown","eyesdownd","mating","matingd","quidgrid","quidgrid2","quidgrid2d","quidgridd","skiltrek","strikeit","strikeit2","strikeit2d","strikeitd","timemchn","turnover"],"cd32":["cndypuzl","haremchl","lasstixx","lsrquiz","lsrquiz2","mgnumber","mgprem11"],"cdi":["quizard","quizrd22","quizrd32","quizrr41"],"chihiro":["ghostsqu","gundamos","hotd3","mj2","mj3","ollie","outr2","scg06nt","vcop3","wangmid","wangmid2"],"cpzn1":["glpracr","sfex","sfexa","sfexj","sfexp","sfexpj","sfexpu1","sfexu","starglad","stargladj","ts2","ts2j"],"cpzn2":["jgakuen","kikaioh","plsmaswd","plsmaswda","rvschool","rvschoola","sfex2","sfex2a","sfex2h","sfex2j","sfex2p","sfex2pa","sfex2pj","shiryu2","stargld2","strider2","strider2a","techromn","techromnu","tgmj"],"crysbios":["crysking","evosocc","topbladv"],"decocass":["castfant","cbdash","cbnj","cbtime","cburnrub","cburnrub2","cdiscon1","cexplore","cfghtice","cflyball","cgraplop","cgraplop2","chwy","clapapa","clapapa2","clocknch","cluckypo","cmissnx","cnightst","cnightst2","cppicf","cppicf2","cprobowl","cprogolf","cprosocc","cptennis","cscrtry","cscrtry2","csuperas","csweetht","cterrani","ctisland","ctisland2","ctisland3","ctornado","ctsttape","czeroize"],"f355bios":["f355","f355twin","f355twn2"],"galgbios":["galgame2"],"gq863":["bmiidx","bmiidx3","bmiidx4","bmiidx6","bmiidx7","bmiidx8","bmiidxc","bmiidxc2","bmiidxca"],"hikaru":["airtrix","pharrier","podrace"],"hng64":["bbust2","buriki","fatfurwa","roadedge","sams64","sams64_2","xrally"],"hod2bios":["hotd2","hotd2o"],"konamigv":["btchamp","hyperath","kdeadeye","nagano98","pbball96","simpbowl","susume","tokimosh","tokimosp","weddingr"],"konamigx":["daiskiss","dragoona","dragoonj","fantjour","fantjoura","ggreats2","gokuparo","le2","le2j","le2u","opengolf","opengolf2","puzldama","racinfrc","racinfrcu","rungun2","rushhero","salmndr2","salmndr2a","sexyparo","slamdnk2","soccerss","soccerssa","soccerssj","soccerssja","tbyahhoo","tkmmpzdm","tokkae","vsnetscr","vsnetscra","vsnetscreb","vsnetscrj","vsnetscru","winspike","winspikej"],"kviper":["boxingm","code1d","code1db","gticlub2","gticlub2ea","jpark3","mfightc","mfightcc","mocapb","mocapbj","mocapglf","p911","p9112","p911e","p911j","p911kc","p911uc","popn9","ppp2nd","sogeki","sscopex","thrild2","thrild2a","thrild2c","tsurugi","tsurugij","wcombat","wcombatj","wcombatk","xtrial"],"macsbios":["cultname","kisekaeh","kisekaem","yujan","yuka"],"maxaflex":["mf_achas","mf_bdash","mf_brist","mf_flip"],"megaplay":["mp_bio","mp_col3","mp_gaxe2","mp_gslam","mp_mazin","mp_shnb3","mp_soni2","mp_sonic","mp_sor2","mp_twc"],"megatech":["mt_aftrb","mt_arrow","mt_astrm","mt_astro","mt_asyn","mt_bbros","mt_beast","mt_cols","mt_crack","mt_eswat","mt_fshrk","mt_fwrld","mt_gaxe","mt_gaxe2","mt_gfoot","mt_ggolf","mt_gng","mt_gsocr","mt_kcham","mt_lastb","mt_mlh","mt_mwalk","mt_mystd","mt_orun","mt_parlg","mt_revsh","mt_shado","mt_shang","mt_shar2","mt_shnbi","mt_smgp","mt_soni2","mt_sonia","mt_sonic","mt_spman","mt_srage","mt_srbb","mt_stbld","mt_stf","mt_tetri","mt_tfor2","mt_tgolf","mt_tlbba","mt_tout","mt_wcsoc","mt_wwar"],"naomi":["18wheelr","capsnk","capsnka","crackndj","crzytaxi","csmash","csmasho","cspike","deathcox","derbyoc","derbyoc2","derbyocw","doa2","doa2m","dybb99","dybbnao","ggram2","ggx","gram2000","gunsur2","gwing2","hmgeo","jambo","kick4csh","mvsc2","otrigger","pjustic","pstone","pstone2","qmegamis","samba","sgtetris","shangril","shootopl","shootpl","slasho","smarinef","smlg99","spawn","sstrkfgt","suchie3","totd","toyfight","virnba","virnbao","vs2_2k","vtenis2c","vtennis","wwfroyal","zombrvn"],"naomi2":["beachspi","clubkrte","initd","initdexp","initdv2j","initdv3j","vf4","vf4b","vf4c","vf4cart","vf4evo","vf4evoa","vf4evoct","vf4tuned","vf4tuneda","vf4tunedd","vstrik3","vstrik3c","wldrider"],"naomigd":["azumanga","bdrdown","cfield","chocomLine truncated
+1
View File
@@ -0,0 +1 @@
{"bios":{"3dobios":["orbatak"],"acpsx":["jdredd","jdreddb","nbajamex"],"airlbios":["alpiltdx"],"aleck64":["11beat","doncdoon","hipai","kurufev","mayjin3","mtetrisc","srmvs","starsldr","twrshaft","vivdolls"],"alg_bios":["aplatoon","crimep2","crimep2_11","crimepat","crimepat_12","crimepat_14","fastdraw","fastdraw_130","gallgall","gallgall_21","lastbh","lastbh_006","maddog","maddog2","maddog2_100","maddog2_110","maddog2_202","maddog_202","maddoga","spacepir","spacepir_14","wsjr","wsjr_15","zortonbr"],"allied":["circa33","disco79","erosone","foathens","heartspd","hoedown","royclark","starshot","suprpick","takefive","thndbolt"],"ar_bios":["ar_airh","ar_airh2","ar_argh","ar_blast","ar_bowl","ar_dart","ar_dart2","ar_dlta","ar_fast","ar_fasta","ar_ldrb","ar_ldrba","ar_ldrbb","ar_ninj","ar_ninj2","ar_pm","ar_rdwr","ar_sdwr","ar_sdwr2","ar_socc","ar_spot","ar_sprg","ar_xeon"],"aristmk5":["goldprmd","magicmsk"],"aristmk6":["50lions","50lionsa","antcleo","choysun","crystals","indianmm","thaiprin","whalecsh","wheregld","wildways"],"atarisy1":["indytemp","indytemp2","indytemp3","indytemp4","indytempc","indytempd","marble","marble2","marble3","marble4","marble5","peterpak","roadblst","roadblst1","roadblst2","roadblst3","roadblstc","roadblstc1","roadblstcg","roadblstg","roadblstg1","roadblstg2","roadrunn","roadrunn1","roadrunn2"],"atluspsx":["hvnsgate"],"atpsx":["primrag2"],"awbios":["anmlbskt","basschal","claychal","demofist","dirtypig","dolphin","fotns","ftspeed","ggisuka","ggx15","kofnw","kofnwj","kofxi","kov7sprt","maxspeed","mslug6","ngbc","rangrmsn","rumblef","rumblef2","salmankt","samsptk","sprtshot","vfurlong","xtrmhnt2","xtrmhunt"],"cdibios":["quizard","quizrd12","quizrd17","quizrd18","quizrd22","quizrd23","quizrd34","quizrr40","quizrr41","quizrr42"],"chihiro":["ccfboxa","crtaxihr","ghostsqo","ghostsqu","gundamos","hotd3","mj2","mj2c","mj2f","mj3","mj3d","ollie","outr2","outr2st","scg06nt","vcop3","wangmd2b","wangmid","wangmid2"],"cpzn1":["glpracr","glpracrj","sfex","sfexa","sfexj","sfexp","sfexpj","sfexpj1","sfexpu1","sfexu","starglad","stargladj","ts2","ts2a","ts2j"],"cpzn2":["jgakuen","kikaioh","plsmaswd","plsmaswda","rvschool","rvschoola","rvschoolu","sfex2","sfex2a","sfex2h","sfex2j","sfex2p","sfex2pa","sfex2ph","sfex2pj","shiryu2","stargld2","strider2","strider2a","techromn","techromnu","tgmj"],"crysbios":["crysking","evosocc","topbladv"],"cubo":["cndypuzl","haremchl","lasstixx","lsrquiz","lsrquiz2","mgnumber","mgprem11","odeontw2"],"decocass":["cadanglr","castfant","cbdash","cbnj","cbtime","cburnrub","cburnrub2","cdiscon1","cdsteljn","cexplore","cfghtice","cfishing","cflyball","cgraplop","cgraplop2","chamburger","chwy","clapapa","clapapa2","clocknch","cluckypo","cmanhat","cmissnx","cnightst","cnightst2","coozumou","cppicf","cppicf2","cprobowl","cprogolf","cprogolf18","cprogolfj","cpsoccer","cpsoccerj","cptennis","cscrtry","cscrtry2","csdtenis","cskater","csuperas","csweetht","cterrani","ctisland","ctisland2","ctisland3","ctornado","ctsttape","czeroize"],"f355bios":["f355twin","f355twn2"],"f355dlx":["f355"],"galgbios":["galgame2"],"gp_110":["blvelvet","camlight","chucklck","foxylady","real","rio"],"gq863":["bmiidx","bmiidx2","bmiidx3","bmiidx3a","bmiidx4","bmiidx5","bmiidx6","bmiidx6a","bmiidx7","bmiidx8","bmiidxa","bmiidxc","bmiidxc2","bmiidxca","bmiidxs"],"gts1":["astannie","buckrgrs","charlies","cleoptra","closeenc","countdwn","dragon","geniep","hexagone","hulk","jokrpokr","pinpool","roldisco","sinbad","sinbadn","solaride","sys1test","torch","totem"],"gts1s":["astannie","buckrgrs","geniep","hexagone","hulk","roldisco","torch","totem"],"hikaru":["airtrix","braveff","pharrier","podrace","sgnascar"],"hng64":["bbust2","buriki","fatfurwa","roadedge","sams64","sams64_2","xrally"],"hod2bios":["hotd2","hotd2o","hotd2p"],"isgsm":["shinfz","tetrbx"],"iteagle":["carnking","gtfore02","gtfore02o","gtfore04","gtfore05"],"konamigv":["btchamp","hyperath","kdeadeye","lacrazyc","nagano98","naganoj","powyak96","simpbowl","susume","tmosh","tmoshs","tmoshsp","tmoshspa","weddingr"],"konamigx":["crzcross","daiskiss","dragoona","dragoonj","fantjour","fantjoura","ggreats2","gokuparo","le2","le2j","le2u","opengolf","opengolf2","puzldama","racinfrc","racinfrcu","rungun2","rushhero","salmndr2","salmndr2a","sexyparo","sexyparoa","slamdnk2","soccerss","soccerssa","soccerssj","soccerssja","tbyahhoo","tkmmpzdm","tokkae","vsnetscr","vsnetscra","vsnetscreb","vsnetscrj","vsnetscru","winspike","winspikej"],"kviper":["boxingm","code1d","code1db","gticlub2","gticlub2ea","jpark3","mfightc","mfightcc","mocapb","mocapbj","mocapglf","p911","p9112","p911e","p911j","p911kc","p911uc","popn9","ppp2nd","sogeki","sscopex","thrild2","thrild2a","thrild2c","tsurugi","tsurugij","wcombat","wcombatj","wcombatk","xtrial"],"lindbios":["2spicy","abclimax","ghostsev","hotd4","hotdex","hummerxt","initiad4","initiad4c","lbvbiosu","letsgoju","outr2sdx","primevah","psmash3","rambo","segartv","vf5","vtennis3"],"macsbios":["cultname","kisekaeh","kisekaem","yujan","yuLine truncated
+1
View File
@@ -0,0 +1 @@
{"bios":{"3dobios":["orbatak"],"acpsx":["jdredd","jdreddb","nbajamex"],"airlbios":["alpiltdx"],"aleck64":["11beat","doncdoon","hipai","kurufev","mayjin3","mtetrisc","srmvs","starsldr","twrshaft","vivdolls"],"alg_bios":["aplatoon","crimep2","crimep2_11","crimepat","crimepat_12","crimepat_14","fastdraw","fastdraw_130","gallgall","gallgall_21","lastbh","lastbh_006","maddog","maddog2","maddog2_100","maddog2_110","maddog2_202","maddog_202","maddoga","spacepir","spacepir_14","wsjr","wsjr_15","zortonbr"],"allied":["circa33","disco79","erosone","foathens","heartspd","hoedown","royclark","starshot","suprpick","takefive","thndbolt"],"ar_bios":["ar_airh","ar_airh2","ar_argh","ar_blast","ar_bowl","ar_dart","ar_dart2","ar_dlta","ar_fast","ar_fasta","ar_ldrb","ar_ldrba","ar_ldrbb","ar_ninj","ar_ninj2","ar_pm","ar_rdwr","ar_sdwr","ar_sdwr2","ar_socc","ar_spot","ar_sprg","ar_xeon"],"aristmk5":["adonise","adonishl","badbog","blackpnt","bootscot","bootsctn","bumblbug","bumbugs","butdeli","cashcham","cashchm","cashcroa","chickena","chkmatar","chkrun","coralr2v","cshcat","cuckoo","cuckooa","dmddove","dolphtre","dolptra","drawpka","dstblom","dyjack","eldora","enchfore","fortela","genmagi","gnomatw","goldphm","goldprmd","goldpyra","goldra","hrttrhb","incanz","incsun","kgalah","koalamnt","kookabk","locoloot","loconz","lonwolf","magicmsk","magicmska","marmagic","mgarden","mgctouc","minemine","monmous","moutmon","mtreenz","mystgrd","orchidms","oscarar","pantmag","partygrs","peaflut","pengpay","pengpays","penpay20","penpaya","petshopa","prtygras","przfigha","qncsh","qonilea","qonileb","qotna","retrsam","retsam20","retsam5","rwarhl","sbucks3","sumospin","supbuk2","swheart2","thgamblr","topbana","tretrva","triptrea","trjhrs","trpdlght","unicornd","wildbill","wldamz1","wldamz3","wldcoug","wldcougr","wldthg"],"aristmk6":["100lions","50lionr","50lions","50lionsa","5draga","5dragons","5koipp","adonis6","adustcm","aftrhrs","alchemya","amzqnar","amzteml","antcleo","apocof","arbngts","arbnigar","arish","arwincm","asnprinc","atkgld","beavbuk","bigbenpj","bigheart","bigredjp","birredar","blkbrum","blkmust","bluetigr","bmbgs","bobdolu1","brazilar","bucanar","buckstop","buffalar","buffaloa","bushrngr","cactusco","callwlar","calyking","canary2","choysun","cinhell","cmtonig","corrida","crspru","cryspri","crystals","csd","csdce","csret","davedrag","dblagus","deepfrz","deltqn","di4ever","dinoaris","dltbel","dmneye2","dmneyeus","doubdolp","drfz6us","drgnlrd","drgnmgic","drmred","eaglrock","eaglst1","eaglst2","eastland","efcashex","eggsrich","encspela","ent6r","eott","firedan","flamolus","flamolusa","flamoly","flamoly2","followst","fortath","foxred","frogwld","gambjack","gbmoney","geishaar","gldgong","gldincas","gldnflc","goapear","gogreen","goldarjp","goldinc","goldpym","goldsam","grizzly","gwnn","heartgar","heartveg","highnoar","hlntry","hlsunmon","holdrma","houhear","incachf","indce","inddrma","indgcm","indianmm","indll","indprncs","indsc","indsrp","jailbrar","jefffox","jewelunc","kingnep","kingnile","kingnilea","koalmnt","kohinoor","lemrol","lgfish","lineking","lordleo","lougcm","lovestrk","luck88","luigball","mchilli","metestrm","mgforst","mgmerm","milkin","mnytree","moonwalt","mrwoo","mskitty","mystarr","mysteycm","mysteyec","mystmerm","nerdwild","opsesam","orbeaut","outbackj","outbackja","owlcap","pandpays","pariligh","parilius","pcbydem","pcfav","pcfava","pcmostw","pcnewz","pcockmag","pelipete","pelpetus","pennyar","penpayus","pharpor","phxfant","piratar","pompeiar","pompus2","prideaf","prophar","pwsp1","qnile","qnilecj","qnilese","qsheeba","raprichs","rdtripus","redbarar","rubmgc","rumbrel","ruprup","sbuk5","scatmg2","sealdeal","shamnmg","sharmnm","shorthng","silgolar","silkrdus","skydncra","smtmar","spgcarn","stardrft","stardrfu","starqst","statsymb","sunmon2","sunqndol","swh2ld","swhr2us","tahitim","thaiprar","thaiprin","thndrhrt","tigresar","tikitalk","tiktoru","timwolfu","topshtar","torgods","torpdice","trkinus","turtrea","venetian","venicus","werewild","wgorilla","whalecsh","wheregld","whlcshus","whlcshusa","whtwtr","wikwin2","wildways","wingolus","witgold","witgoldd","wldafus","wldcatsa","wldgoos","wldpan2","wldpntus","wldstal2","wtepee","wwaratah","wwaysar","wwaysll","zorrojp","zorrous"],"atarisy1":["indytemp","indytemp2","indytemp3","indytemp4","indytempc","indytempd","marble","marble2","marble3","marble4","marble5","peterpak","roadblst","roadblst1","roadblst2","roadblst3","roadblstc","roadblstc1","roadblstcg","roadblstg","roadblstg1","roadblstg2","roadblstgu","roadrunn","roadrunn1","roadrunn2"],"atluspsx":["hvnsgate"],"atpsx":["primrag2"],"awbios":["anmlbskt","basschal","basschalo","claychal","demofist","dirtypig","dolphin","fotns","ftspeed","ggisuka","ggx15","kofnw","kofnwj","kofxi","kov7sprt","maxspeed","mslug6","ngbc","ngbcj","rangrmsn","rumblef","rumblef2","rumblefp","rumblf2p","salmankt","samsptk","sprtshot","sushibar","vfurlong","xtrmhnt2","xtrmhunt"],"cdibios":["quizard","quizard2","quizard2_22","quizard3","quizard3_32","quizLine truncated
+92
View File
@@ -0,0 +1,92 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://abdess.github.io/retrobios/api/v1/schemas/arcade-parents.schema.json",
"title": "RetroBIOS arcade parent index of one core version",
"description": "games/arcade/<profile>.json, written by python -m scripts.scraper.arcade_parent_importer from the DAT the profile pins. A game set needs every BIOS set it is listed under (the romof chain read to its end) and, for the MAME family, every device set with ROMs it is listed under.",
"type": "object",
"additionalProperties": false,
"required": ["schema", "profile", "core_version", "source", "bios", "devices", "chd", "missing_parents", "counts"],
"$defs": {
"set_name": { "type": "string", "minLength": 1, "pattern": "^[^\\s/\\\\]+$" },
"set_list": {
"type": "array",
"items": { "$ref": "#/$defs/set_name" },
"uniqueItems": true
},
"sets_by_archive": {
"type": "object",
"propertyNames": { "$ref": "#/$defs/set_name" },
"additionalProperties": { "$ref": "#/$defs/set_list", "minItems": 1 }
},
"sha1": { "type": "string", "pattern": "^[0-9a-f]{40}$" },
"count": { "type": "integer", "minimum": 0 }
},
"properties": {
"schema": { "const": 1 },
"profile": { "type": "string", "pattern": "^[a-z0-9][a-z0-9_.-]*$" },
"core_version": { "type": ["string", "null"] },
"source": {
"oneOf": [
{
"type": "object",
"description": "A MAME -listxml release asset.",
"additionalProperties": false,
"required": ["release", "url", "sha256"],
"properties": {
"release": { "type": "string", "minLength": 1 },
"url": { "type": "string", "pattern": "^https://" },
"sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }
}
},
{
"type": "object",
"description": "Files of a repository at a commit, each with its git blob sha.",
"additionalProperties": false,
"required": ["repo", "commit", "files"],
"properties": {
"repo": { "type": "string", "pattern": "^https://" },
"commit": { "$ref": "#/$defs/sha1" },
"files": {
"type": "object",
"minProperties": 1,
"additionalProperties": { "$ref": "#/$defs/sha1" }
}
}
}
]
},
"bios": {
"$ref": "#/$defs/sets_by_archive",
"description": "BIOS set -> the game sets whose romof chain reaches it."
},
"devices": {
"$ref": "#/$defs/sets_by_archive",
"description": "Device set with ROMs -> the game sets that instantiate it."
},
"chd": {
"$ref": "#/$defs/set_list",
"description": "Game sets that also need a CHD, which is game content and is not installed."
},
"missing_parents": {
"type": "object",
"description": "Game set -> the romof target the DAT does not carry.",
"propertyNames": { "$ref": "#/$defs/set_name" },
"additionalProperties": { "$ref": "#/$defs/set_name" }
},
"counts": {
"type": "object",
"additionalProperties": false,
"required": ["sets", "games", "games_with_bios", "bios_sets", "bios_roots_used", "games_with_devices", "chd_games", "missing_parents"],
"properties": {
"sets": { "$ref": "#/$defs/count" },
"games": { "$ref": "#/$defs/count" },
"games_with_bios": { "$ref": "#/$defs/count" },
"bios_sets": { "$ref": "#/$defs/count" },
"bios_roots_used": { "$ref": "#/$defs/count" },
"games_with_devices": { "$ref": "#/$defs/count" },
"chd_games": { "$ref": "#/$defs/count" },
"missing_parents": { "$ref": "#/$defs/count" }
}
}
}
}
+586
View File
@@ -0,0 +1,586 @@
"""Import, per arcade core version, which BIOS set each game set loads.
A game set names its parent through ``romof``: a clone reads its parent game,
and the chain ends at a BIOS set (``neogeo``, ``naomi``, ``pgm``). The archive
the installer has to provide for a game on the card is every BIOS set on that
chain, plus, for the MAME family, the device sets with ROMs the machine
instantiates (``device_ref``), which a split or merged romset reads from their
own archive. The DAT that answers is the one of the core version that runs
the game, read at the revision its profile pins:
python -m scripts.scraper.arcade_parent_importer --all
python -m scripts.scraper.arcade_parent_importer --profile mame2003_plus
python -m scripts.scraper.arcade_parent_importer --all --offline
A BIOS set is ``isbios="yes"`` (MAME 0.139 and later, FBNeo) or, in the
MAME 0.78 lineage (mame2003, mame2003-plus), which has no such attribute, a
set marked ``runnable="no"``. flycast keeps its NAOMI and Atomiswave table in
code: ``Games[].bios`` names the BIOS, ``naomi`` when it is null
(core/hw/naomi/naomi_cart.cpp:195-197), and ``BIOS[].filename`` the archive
when it differs from the name (naomi_cart.cpp:75).
Output: games/arcade/<profile>.json. CHD games are listed, not resolved: a
CHD is game content. A ``romof`` pointing outside the DAT is listed with its
target, never guessed.
"""
from __future__ import annotations
import argparse
import hashlib
import io
import json
import re
import sys
import urllib.parse
import zipfile
from collections.abc import Callable, Iterator
from dataclasses import dataclass, field
from pathlib import Path
from ..common import load_emulator_profiles
from .romset_dat_importer import (
LISTXML_CHUNK,
MAME_LISTXML_URL,
BlobMismatchError,
_api_json,
_download,
_listxml_opener,
_machine_roms,
fetch_pack,
git_blob_sha,
)
SCHEMA_VERSION = 1
DEFAULT_OUTPUT = Path("games") / "arcade"
DEFAULT_CACHE = Path(".cache") / "dats"
# How each profile's DAT is found. "listxml": the -listxml release asset of
# the MAME version core_version names. "fbneo": every DAT of libretro/FBNeo's
# dats/ at source_commit. "repo": files of the profile's source repository at
# source_commit (a ZIP member when the file is an archive). "flycast": the
# BIOS and Games tables of naomi_roms.cpp at source_commit.
PROFILES: dict[str, dict] = {
"mame": {"kind": "listxml", "marker": "isbios"},
"mame0258": {"kind": "listxml", "marker": "isbios"},
"mame0278": {"kind": "listxml", "marker": "isbios"},
"fbneo": {"kind": "fbneo", "marker": "isbios"},
"mame2016": {
"kind": "repo",
"marker": "isbios",
"paths": ["metadata/MAME 0.174 Arcade XML DAT.zip", "metadata/MAME 0.174 Home XML DAT.zip"],
},
"mame2015": {"kind": "repo", "marker": "isbios", "paths": ["metadata/mame2014-xml.zip"]},
"mame2010": {"kind": "repo", "marker": "isbios", "paths": ["metadata/mame2010.xml"]},
"mame2003_plus": {"kind": "repo", "marker": "runnable", "paths": ["metadata/mame2003-plus.xml"]},
"mame2003": {"kind": "repo", "marker": "runnable", "paths": ["metadata/mame2003.xml"]},
"mame2000": {"kind": "repo", "marker": "isbios", "paths": ["metadata/MAME 0.37b5 XML.dat"]},
"flycast": {"kind": "flycast", "paths": ["core/hw/naomi/naomi_roms.cpp"]},
}
# About 95 % of what the pinned DATs gave on 2026-11-03: a build that
# resolves fewer games to a BIOS set has read a DAT of another shape.
FLOORS: dict[str, int] = {
"mame": 2600,
"mame0258": 2490,
"mame0278": 2580,
"fbneo": 9800,
"mame2016": 1840,
"mame2015": 1470,
"mame2010": 1060,
"mame2003_plus": 650,
"mame2003": 420,
"mame2000": 120,
"flycast": 330,
}
@dataclass
class SetEntry:
"""What the index needs from one machine or game element."""
name: str
romof: str | None = None
isbios: bool = False
isdevice: bool = False
runnable: bool = True
has_roms: bool = False
disks: bool = False
devices: list[str] = field(default_factory=list)
_ELEMENT = re.compile(rb"<(machine|game)\b([^>]*)>")
_ATTR = re.compile(rb'([\w:-]+)="([^"]*)"')
_DEVICE_REF = re.compile(rb'<device_ref\b[^>]*\bname="([^"]+)"')
_DISK = re.compile(rb"<disk\b([^>]*)>")
# Longer than any start tag the DATs write, shorter than any element.
_TAG_TAIL = 4096
def _attrs(raw: bytes) -> dict[str, str]:
return {k.decode(): v.decode() for k, v in _ATTR.findall(raw)}
def iter_sets(stream: io.BufferedIOBase) -> Iterator[SetEntry]:
"""Every machine or game element of a DAT or -listxml, streamed.
Attributes come in any order (mame2003 writes ``<game runnable="no"
name="neogeo">``), and the document is never held whole: the window
keeps only the element being closed.
"""
buffer = b""
while True:
chunk = stream.read(LISTXML_CHUNK)
buffer += chunk
position = 0
# Where the next read has to resume: the start of an element still
# open, else a tail long enough to hold a start tag cut in two.
keep_from = max(0, len(buffer) - _TAG_TAIL)
while True:
match = _ELEMENT.search(buffer, position)
if not match:
break
closing = b"</" + match.group(1) + b">"
end = buffer.find(closing, match.end())
if end == -1:
keep_from = match.start()
break
attrs = _attrs(match.group(2))
body = buffer[match.end() : end]
disks = [_attrs(d) for d in _DISK.findall(body)]
yield SetEntry(
name=attrs.get("name", ""),
romof=attrs.get("romof") or None,
isbios=attrs.get("isbios") == "yes",
isdevice=attrs.get("isdevice") == "yes",
runnable=attrs.get("runnable", "yes") != "no",
has_roms=bool(_machine_roms(body)),
disks=any(d.get("status") != "nodump" for d in disks),
devices=[d.decode() for d in _DEVICE_REF.findall(body)],
)
position = end + len(closing)
keep_from = max(position, len(buffer) - _TAG_TAIL)
if not chunk:
return
buffer = buffer[keep_from:]
def is_bios(entry: SetEntry, marker: str) -> bool:
if entry.isbios:
return True
return marker == "runnable" and not entry.runnable and not entry.isdevice
def build_index(sets: dict[str, SetEntry], marker: str) -> dict:
"""BIOS sets per game, device sets per game, CHD games, broken chains."""
bios_names = {n for n, e in sets.items() if is_bios(e, marker)}
device_roms = {n for n, e in sets.items() if e.isdevice and e.has_roms}
games = {
n: e for n, e in sets.items()
if n not in bios_names and not e.isdevice and e.runnable and (e.has_roms or e.disks)
}
bios: dict[str, set[str]] = {}
devices: dict[str, set[str]] = {}
missing: dict[str, str] = {}
for name, entry in games.items():
seen = {name}
current = entry.romof
while current and current not in seen:
seen.add(current)
parent = sets.get(current)
if parent is None:
missing[name] = current
break
if current in bios_names:
bios.setdefault(current, set()).add(name)
current = parent.romof
for device in set(entry.devices) & device_roms:
devices.setdefault(device, set()).add(name)
with_bios = {g for members in bios.values() for g in members}
return {
"bios": {k: sorted(v) for k, v in sorted(bios.items())},
"devices": {k: sorted(v) for k, v in sorted(devices.items())},
"chd": sorted(n for n, e in games.items() if e.disks),
"missing_parents": dict(sorted(missing.items())),
"counts": {
"sets": len(sets),
"games": len(games),
"games_with_bios": len(with_bios),
"bios_sets": len(bios_names),
"bios_roots_used": len(bios),
"games_with_devices": len({g for v in devices.values() for g in v}),
"chd_games": sum(1 for e in games.values() if e.disks),
"missing_parents": len(missing),
},
}
# --- C table reader (flycast naomi_roms.cpp) ---------------------------------
_TOKEN = re.compile(r'"(?:\\.|[^"\\])*"|[{},]|[^\s{},"]+')
def _strip_comments(text: str) -> str:
"""C source with comments removed, string literals kept intact."""
out: list[str] = []
i, n = 0, len(text)
while i < n:
c = text[i]
if c == '"':
j = i + 1
while j < n and text[j] != '"':
j += 2 if text[j] == "\\" else 1
out.append(text[i : j + 1])
i = j + 1
elif text.startswith("//", i):
i = text.find("\n", i)
i = n if i == -1 else i
elif text.startswith("/*", i):
i = text.find("*/", i + 2)
i = n if i == -1 else i + 2
else:
out.append(c)
i += 1
return "".join(out)
def _initializer(text: str, array: str) -> list:
"""The brace initializer of ``const <type> <array>[] = {...}`` as nested
lists of tokens, each element split on its top-level commas."""
match = re.search(rf"\b{re.escape(array)}\s*\[\s*\]\s*=\s*\{{", text)
if not match:
raise ValueError(f"no initializer for {array}[]")
tokens = _TOKEN.findall(text[match.end() - 1 :])
stack: list[list] = []
item: list = []
for token in tokens:
if token == "{":
stack.append(item)
item = []
stack.append([])
elif token == "}":
elements = stack.pop()
if item:
elements.append(item if len(item) > 1 else item[0])
item = stack.pop()
item.append(elements)
if not stack:
return item[0]
elif token == ",":
elements = stack[-1]
if item:
elements.append(item if len(item) > 1 else item[0])
item = []
else:
item.append(token)
raise ValueError(f"unterminated initializer for {array}[]")
def _string(token: object) -> str | None:
if isinstance(token, str) and token.startswith('"'):
return token[1:-1]
return None
def flycast_index(source: str) -> dict:
"""BIOS archive per NAOMI/Atomiswave game from naomi_roms.cpp."""
text = _strip_comments(source)
archive_of: dict[str, str] = {}
for element in _initializer(text, "BIOS"):
if not isinstance(element, list) or not element:
continue
name = _string(element[0])
if name is None:
continue
# BIOS_t: name, blobs[], filename (naomi_roms.h:53-66).
filename = _string(element[2]) if len(element) > 2 else None
archive_of[name] = filename or name
bios: dict[str, set[str]] = {}
chd: list[str] = []
unknown: dict[str, str] = {}
games: set[str] = set()
for element in _initializer(text, "Games"):
if not isinstance(element, list) or not element:
continue
name = _string(element[0])
if name is None:
continue
# shors2k2 is declared twice at e88b0ced: a name counts once.
games.add(name)
# Game: name, parent_name, description, size, key, bios, cart_type,
# rotation, blobs[], gdrom_name, ... (naomi_roms.h:71-94).
named = _string(element[5]) if len(element) > 5 else None
if named is not None and named not in archive_of:
unknown[name] = named
continue
bios.setdefault(archive_of[named or "naomi"], set()).add(name)
if len(element) > 9 and _string(element[9]):
chd.append(name)
return {
"bios": {k: sorted(v) for k, v in sorted(bios.items())},
"devices": {},
"chd": sorted(chd),
"missing_parents": dict(sorted(unknown.items())),
"counts": {
"sets": len(games) + len(archive_of),
"games": len(games),
"games_with_bios": len({g for v in bios.values() for g in v}),
"bios_sets": len(archive_of),
"bios_roots_used": len(bios),
"games_with_devices": 0,
"chd_games": len(chd),
"missing_parents": len(unknown),
},
}
# --- Fetching ----------------------------------------------------------------
def _repo_slug(url: str) -> str:
parts = [p for p in urllib.parse.urlsplit(url).path.split("/") if p]
if len(parts) < 2:
raise ValueError(f"not a repository URL: {url}")
return f"{parts[0]}/{parts[1].removesuffix('.git')}"
def fetch_repo_file(slug: str, commit: str, path: str, cache_dir: Path) -> tuple[Path, str]:
"""One file of a GitHub repository at a commit, checked against the blob
sha the repository lists for it, and that sha."""
folder, _, name = path.rpartition("/")
listing = _api_json(
f"https://api.github.com/repos/{slug}/contents/{urllib.parse.quote(folder)}?ref={commit}"
)
item = next(
(i for i in listing if isinstance(i, dict) and i.get("name") == name), None
) if isinstance(listing, list) else None
if item is None:
raise FileNotFoundError(f"{slug}@{commit[:8]} has no {path}")
destination = cache_dir / "repo" / slug / commit / path
if destination.is_file() and git_blob_sha(destination.read_bytes()) != item["sha"]:
destination.unlink()
url = (
f"https://raw.githubusercontent.com/{slug}/{commit}/"
f"{urllib.parse.quote(path)}"
)
_download(url, destination)
if git_blob_sha(destination.read_bytes()) != item["sha"]:
destination.unlink()
raise BlobMismatchError(f"{url} is not blob {item['sha']}")
return destination, item["sha"]
def _cached_repo_file(slug: str, commit: str, path: str, cache_dir: Path) -> tuple[Path, str]:
"""The cached copy, offline. Its blob sha is recomputed from the bytes."""
destination = cache_dir / "repo" / slug / commit / path
if not destination.is_file():
raise FileNotFoundError(f"{slug}@{commit[:8]}:{path} is not in {cache_dir}")
return destination, git_blob_sha(destination.read_bytes())
def _xml_streams(path: Path) -> Iterator[Callable[[], io.BufferedIOBase]]:
"""Openers for the XML documents a file holds: itself, or each XML or
DAT member of a ZIP."""
if path.suffix.lower() != ".zip":
yield lambda: path.open("rb")
return
with zipfile.ZipFile(path) as archive:
names = sorted(
n for n in archive.namelist() if n.lower().endswith((".xml", ".dat"))
)
for member in names:
def opener(member: str = member) -> io.BufferedIOBase:
archive = zipfile.ZipFile(path)
stream = archive.open(member)
stream.close_archive = archive # keep the archive alive
return stream
yield opener
def read_sets(openers: list[Callable[[], io.BufferedIOBase]]) -> dict[str, SetEntry]:
"""Every set of several documents. A set read twice keeps its first
reading: FBNeo's Arcade and Neogeo DATs both carry the neogeo BIOS."""
sets: dict[str, SetEntry] = {}
for opener in openers:
with opener() as stream:
for entry in iter_sets(stream):
if entry.name:
sets.setdefault(entry.name, entry)
return sets
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for block in iter(lambda: handle.read(1 << 20), b""):
digest.update(block)
return digest.hexdigest()
def import_profile(
profile: str, data: dict, cache_dir: Path, offline: bool = False
) -> dict:
"""The index document for one profile, from the DAT its pin names."""
spec = PROFILES[profile]
commit = str(data.get("source_commit") or "")
source_url = str(data.get("source") or "")
kind = spec["kind"]
if kind == "listxml":
tag = "mame" + str(data["core_version"]).replace(".", "").zfill(4)
if offline:
pack = cache_dir / f"{tag}lx.zip"
if not pack.is_file():
raise FileNotFoundError(f"{pack} is not cached")
else:
pack, _ = fetch_pack("mame", tag, cache_dir)
source = {
"release": f"mamedev/mame {tag}",
"url": MAME_LISTXML_URL.format(tag=tag),
"sha256": _sha256(pack),
}
index = build_index(read_sets([_listxml_opener(pack)]), spec["marker"])
elif kind == "fbneo":
if offline:
folder = cache_dir / "fbneo-dats"
blobs = {p.name: git_blob_sha(p.read_bytes()) for p in sorted(folder.glob("*.dat"))}
if not blobs:
raise FileNotFoundError(f"{folder} is not cached")
else:
folder, blobs = fetch_pack("fbneo", commit, cache_dir)
source = {
"repo": source_url,
"commit": commit,
"files": {f"dats/{name}": sha for name, sha in sorted(blobs.items())},
}
openers = [
(lambda p=folder / name: p.open("rb")) for name in sorted(blobs)
]
index = build_index(read_sets(openers), spec["marker"])
else:
slug = _repo_slug(source_url)
files: dict[str, str] = {}
paths: list[Path] = []
for path in spec["paths"]:
local, sha = (
_cached_repo_file(slug, commit, path, cache_dir)
if offline
else fetch_repo_file(slug, commit, path, cache_dir)
)
files[path] = sha
paths.append(local)
source = {"repo": source_url, "commit": commit, "files": files}
if kind == "flycast":
index = flycast_index(paths[0].read_text(encoding="utf-8"))
else:
openers = [opener for p in paths for opener in _xml_streams(p)]
index = build_index(read_sets(openers), spec["marker"])
return {
"schema": SCHEMA_VERSION,
"profile": profile,
"core_version": data.get("core_version"),
"source": source,
**index,
}
# --- Checks against the collection -------------------------------------------
def declared_archives(profile: dict) -> set[str]:
"""Archive names a profile declares, lowercased: file names, aliases and
the archive a member lives in."""
names: set[str] = set()
for entry in profile.get("files") or []:
for value in [entry.get("name"), entry.get("archive"), *(entry.get("aliases") or [])]:
if isinstance(value, str) and value:
names.add(value.rsplit("/", 1)[-1].lower())
return names
def collection_names(database: Path) -> set[str]:
if not database.is_file():
return set()
with database.open(encoding="utf-8") as handle:
db = json.load(handle)
return {n.lower() for n in db.get("indexes", {}).get("by_name", {})}
def bios_roots(doc: dict) -> list[str]:
return sorted(doc["bios"])
def dump(doc: dict) -> str:
return json.dumps(doc, sort_keys=True, separators=(",", ":"), ensure_ascii=False) + "\n"
def write_if_changed(path: Path, text: str) -> bool:
if path.is_file() and path.read_text(encoding="utf-8") == text:
return False
path.parent.mkdir(parents=True, exist_ok=True)
scratch = path.with_suffix(path.suffix + ".part")
scratch.write_text(text, encoding="utf-8")
scratch.replace(path)
return True
def main() -> int:
parser = argparse.ArgumentParser(allow_abbrev=False, description=__doc__.splitlines()[0])
which = parser.add_mutually_exclusive_group(required=True)
which.add_argument("--profile", action="append", choices=sorted(PROFILES))
which.add_argument("--all", action="store_true")
parser.add_argument("--emulators-dir", default="emulators")
parser.add_argument("--database", default="database.json")
parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT)
parser.add_argument("--cache-dir", type=Path, default=DEFAULT_CACHE)
parser.add_argument("--offline", action="store_true", help="read the download cache only")
parser.add_argument("--dry-run", action="store_true", help="print coverage, write nothing")
args = parser.parse_args()
profiles = load_emulator_profiles(args.emulators_dir, skip_aliases=False)
names = collection_names(Path(args.database))
failed = False
for profile in sorted(PROFILES) if args.all else args.profile:
if profile not in profiles:
print(f"FAIL {profile}: no emulators/{profile}.yml", file=sys.stderr)
failed = True
continue
try:
doc = import_profile(profile, profiles[profile], args.cache_dir, args.offline)
except (OSError, ValueError, KeyError) as exc:
print(f"FAIL {profile}: {exc}", file=sys.stderr)
failed = True
continue
counts = doc["counts"]
roots = bios_roots(doc)
declared = declared_archives(profiles[profile])
undeclared = [r for r in roots if f"{r}.zip" not in declared]
absent = [r for r in roots if names and f"{r}.zip" not in names]
print(
f"{profile}: {counts['games']} games, {counts['games_with_bios']} with a BIOS set, "
f"{counts['bios_roots_used']} BIOS sets used, "
f"{counts['games_with_devices']} with device sets, {counts['chd_games']} CHD, "
f"{counts['missing_parents']} parents absent from the DAT"
)
if absent:
print(f" BIOS sets absent from the collection: {', '.join(absent)}")
if undeclared:
print(f" BIOS sets the profile does not declare: {', '.join(undeclared)}")
floor = FLOORS.get(profile, 0)
if counts["games_with_bios"] < floor:
print(
f"FAIL {profile}: {counts['games_with_bios']} games with a BIOS set, "
f"floor {floor}",
file=sys.stderr,
)
failed = True
continue
if not args.dry_run:
target = args.output_dir / f"{profile}.json"
if write_if_changed(target, dump(doc)):
print(f" wrote {target}")
return 1 if failed else 0
if __name__ == "__main__":
sys.exit(main())
+56
View File
@@ -0,0 +1,56 @@
<?xml version="1.0"?>
<!DOCTYPE mame [
<!ELEMENT mame (machine+)>
]>
<mame build="0.000 (fixture)">
<machine name="neogeo" sourcefile="neogeo/neogeo.cpp" isbios="yes">
<description>Neo-Geo</description>
<rom name="sp-s2.sp1" size="131072" crc="9036d879" sha1="4f5ed7105b7128794654ce82b51723e16e389543"/>
<device_ref name="ym2610"/>
</machine>
<machine name="naomi" sourcefile="sega/naomi.cpp" isbios="yes">
<description>Naomi</description>
<rom name="epr-21576h.ic27" size="2097152" crc="d4895685"/>
</machine>
<machine name="naomigd" sourcefile="sega/naomi.cpp" isbios="yes" romof="naomi">
<description>Naomi GD-ROM</description>
<rom name="epr-21576h.ic27" merge="epr-21576h.ic27" size="2097152" crc="d4895685"/>
</machine>
<machine name="mslug" sourcefile="neogeo/neogeo.cpp" romof="neogeo">
<description>Metal Slug</description>
<rom name="201-p1.p1" size="2097152" crc="08d8daa5"/>
<device_ref name="ym2610"/>
<device_ref name="m68705p3"/>
</machine>
<machine name="mslugh" sourcefile="neogeo/neogeo.cpp" cloneof="mslug" romof="mslug">
<description>Metal Slug (clone)</description>
<rom name="201-p1h.p1" size="2097152" crc="11111111"/>
<device_ref name="ym2610"/>
</machine>
<machine name="ikaruga" sourcefile="sega/naomi.cpp" romof="naomigd">
<description>Ikaruga</description>
<rom name="317-0323-jpn.pic" size="7665" crc="22222222"/>
<disk name="gdl-0010" sha1="0000000000000000000000000000000000000000"/>
</machine>
<machine name="orphan" sourcefile="misc/x.cpp" romof="gonebios">
<description>Orphan</description>
<rom name="o.bin" size="16" crc="33333333"/>
</machine>
<machine name="pacman" sourcefile="namco/pacman.cpp">
<description>Pac-Man</description>
<rom name="pacman.6e" size="4096" crc="c1e6ab10"/>
<device_ref name="ym2610"/>
</machine>
<machine name="nodumponly" sourcefile="misc/x.cpp">
<description>Nothing dumped</description>
<rom name="u1" size="16" status="nodump"/>
<disk name="d" status="nodump"/>
</machine>
<machine name="ym2610" sourcefile="sound/ym2610.cpp" isdevice="yes" runnable="no">
<description>YM2610</description>
</machine>
<machine name="m68705p3" sourcefile="cpu/m6805.cpp" isdevice="yes" runnable="no">
<description>M68705P3</description>
<rom name="m68705p3.bin" size="2048" crc="44444444"/>
</machine>
</mame>
+99
View File
@@ -0,0 +1,99 @@
// Fixture in the shape of flycast core/hw/naomi/naomi_roms.cpp
const BIOS_t BIOS[] =
{
{
"naomi",
{
{ 1, "epr-21576h.ic27", 0x000000, 0x200000, 0xd4895685 },
}
},
{
"naomi-jp-d",
{
{ 0, "epr-21576d.ic27", 0x000000, 0x200000, 0x3b2afa7b },
},
"naomi"
},
{
"awbios",
{
/* "commented", { 9, "x" } */
{ 1, "bios0.ic23", 0x000000, 0x020000, 0x719b2b0b },
}
},
{ nullptr }
};
const Game Games[] =
{
// "not a game", nullptr,
{
"gram2000",
nullptr,
"Giant Gram 2000",
0x0b000000,
0x7f805c3f,
nullptr,
M1,
ROT0,
{
{ "epr-23377.ic11", 0x0000000, 0x0400000, 0x4ca3149c },
},
nullptr,
&giant_gram_inputs,
},
{
"kofnw",
nullptr,
"The King of Fighters Neowave",
0x08000000,
0,
"awbios",
AW,
ROT0,
{
{ "ax2201p01.ic12", 0x0000000, 0x0800000, 0xb1fff0c8 },
},
},
{
"jpgame",
"gram2000",
"Japan only, a brace } in a string",
0x08000000,
0,
"naomi-jp-d",
M1,
ROT0,
{
{ "x.ic11", 0x0000000, 0x0800000, 0x00000001 },
},
},
{
"ikaruga",
nullptr,
"Ikaruga",
0x8000000,
0,
nullptr,
GD,
ROT270,
{
{ "317-0323-jpn.pic", 0, 0x4000 },
},
"ikaruga",
},
{
"bad",
nullptr,
"Unknown BIOS name",
0x8000000,
0,
"nosuchbios",
M1,
ROT0,
{
{ "b.ic11", 0, 0x4000 },
},
},
{ nullptr }
};
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0"?>
<!DOCTYPE mame [
<!ELEMENT mame (game+)>
]>
<mame build="0.78 (fixture)">
<game name="kof98" sourcefile="neogeo.c" romof="neogeo">
<description>KOF 98</description>
<rom name="242-p1.bin" size="2097152" crc="61ac868a"/>
</game>
<game runnable="no" name="neogeo">
<description>Neo-Geo</description>
<rom name="neo-geo.rom" size="131072" crc="9036d879"/>
</game>
<game runnable="no" name="psarc95x">
<description>A root no game names</description>
<rom name="x.bin" size="16" crc="55555555"/>
</game>
<game name="brvblade" sourcefile="zn.c" romof="psarc95">
<description>Brave Blade</description>
<rom name="b.bin" size="16" crc="66666666"/>
</game>
</mame>
+179
View File
@@ -0,0 +1,179 @@
"""Arcade parent index: game set to BIOS sets and device sets, per core."""
from __future__ import annotations
import io
import json
import tempfile
import unittest
import zipfile
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
TMP_ROOT = ROOT / "tmp" / "tests"
TMP_ROOT.mkdir(parents=True, exist_ok=True)
FIXTURES = ROOT / "tests" / "fixtures" / "arcade_parents"
COMMITTED = ROOT / "games" / "arcade"
from scripts.scraper import arcade_parent_importer as api # noqa: E402
try:
from jsonschema import Draft202012Validator
except ImportError: # validate_schemas.py is the only tool that needs it
Draft202012Validator = None
COMMIT = "a" * 40
def _sets(name: str, chunk: int | None = None) -> dict:
data = (FIXTURES / name).read_bytes()
saved = api.LISTXML_CHUNK
if chunk:
api.LISTXML_CHUNK = chunk
try:
return api.read_sets([lambda: io.BytesIO(data)])
finally:
api.LISTXML_CHUNK = saved
class ListxmlIndex(unittest.TestCase):
def setUp(self) -> None:
self.index = api.build_index(_sets("listxml.xml"), "isbios")
def test_clone_reaches_bios_through_its_parent(self) -> None:
self.assertEqual(self.index["bios"]["neogeo"], ["mslug", "mslugh"])
def test_every_bios_on_the_chain_is_listed(self) -> None:
self.assertIn("ikaruga", self.index["bios"]["naomigd"])
self.assertIn("ikaruga", self.index["bios"]["naomi"])
def test_device_with_roms_only(self) -> None:
self.assertEqual(self.index["devices"], {"m68705p3": ["mslug"]})
def test_chd_and_missing_parent(self) -> None:
self.assertEqual(self.index["chd"], ["ikaruga"])
self.assertEqual(self.index["missing_parents"], {"orphan": "gonebios"})
def test_sets_without_a_dump_are_not_games(self) -> None:
counts = self.index["counts"]
# mslug, mslugh, ikaruga, orphan, pacman
self.assertEqual(counts["games"], 5)
self.assertEqual(counts["games_with_bios"], 3)
self.assertEqual(counts["bios_sets"], 3)
def test_small_window_reads_the_same(self) -> None:
small = api.build_index(_sets("listxml.xml", chunk=64), "isbios")
self.assertEqual(api.dump(small), api.dump(self.index))
class RunnableMarker(unittest.TestCase):
def test_runnable_no_marks_the_bios(self) -> None:
index = api.build_index(_sets("runnable_no.dat"), "runnable")
self.assertEqual(index["bios"], {"neogeo": ["kof98"]})
self.assertEqual(index["missing_parents"], {"brvblade": "psarc95"})
self.assertEqual(index["counts"]["bios_sets"], 2)
def test_isbios_marker_ignores_runnable(self) -> None:
index = api.build_index(_sets("runnable_no.dat"), "isbios")
self.assertEqual(index["bios"], {})
class FlycastTable(unittest.TestCase):
def setUp(self) -> None:
self.index = api.flycast_index((FIXTURES / "naomi_roms.cpp").read_text())
def test_null_bios_is_naomi_and_filename_names_the_archive(self) -> None:
self.assertEqual(self.index["bios"]["naomi"], ["gram2000", "ikaruga", "jpgame"])
self.assertEqual(self.index["bios"]["awbios"], ["kofnw"])
self.assertNotIn("naomi-jp-d", self.index["bios"])
def test_gdrom_game_needs_a_chd(self) -> None:
self.assertEqual(self.index["chd"], ["ikaruga"])
def test_unknown_bios_is_reported(self) -> None:
self.assertEqual(self.index["missing_parents"], {"bad": "nosuchbios"})
class OfflineImport(unittest.TestCase):
"""import_profile reads the cache only, and two runs write the same bytes."""
def setUp(self) -> None:
self.temp = tempfile.TemporaryDirectory(dir=TMP_ROOT)
self.cache = Path(self.temp.name)
dat = self.cache / "repo" / "libretro" / "mame2003-libretro" / COMMIT / "metadata"
dat.mkdir(parents=True)
(dat / "mame2003.xml").write_bytes((FIXTURES / "runnable_no.dat").read_bytes())
with zipfile.ZipFile(self.cache / "mame0000lx.zip", "w") as archive:
archive.write(FIXTURES / "listxml.xml", "mame0000.xml")
def tearDown(self) -> None:
self.temp.cleanup()
def test_repo_profile_is_deterministic(self) -> None:
profile = {
"source": "https://github.com/libretro/mame2003-libretro",
"source_commit": COMMIT,
"core_version": "0.78",
}
first = api.dump(api.import_profile("mame2003", profile, self.cache, offline=True))
second = api.dump(api.import_profile("mame2003", profile, self.cache, offline=True))
self.assertEqual(first, second)
doc = json.loads(first)
self.assertEqual(doc["bios"], {"neogeo": ["kof98"]})
self.assertEqual(list(doc["source"]["files"]), ["metadata/mame2003.xml"])
def test_listxml_release_from_the_cache(self) -> None:
doc = api.import_profile("mame", {"core_version": "0.000"}, self.cache, offline=True)
self.assertEqual(doc["source"]["release"], "mamedev/mame mame0000")
self.assertEqual(doc["devices"], {"m68705p3": ["mslug"]})
@unittest.skipIf(Draft202012Validator is None, "jsonschema not installed")
def test_fixture_output_matches_the_schema(self) -> None:
profile = {
"source": "https://github.com/libretro/mame2003-libretro",
"source_commit": COMMIT,
"core_version": "0.78",
}
doc = api.import_profile("mame2003", profile, self.cache, offline=True)
schema = json.loads((ROOT / "schemas" / "arcade-parents.schema.json").read_text())
self.assertEqual(list(Draft202012Validator(schema).iter_errors(doc)), [])
class DeclaredArchives(unittest.TestCase):
def test_names_aliases_and_archives(self) -> None:
profile = {"files": [
{"name": "neogeo.zip"},
{"name": "sp-s2.sp1", "archive": "neogeo.zip"},
{"name": "pgm.zip", "aliases": ["PGMBIOS.zip"]},
]}
self.assertEqual(api.declared_archives(profile), {"neogeo.zip", "sp-s2.sp1", "pgm.zip", "pgmbios.zip"})
class CommittedIndex(unittest.TestCase):
def test_every_profile_has_its_file_above_the_floor(self) -> None:
for profile in api.PROFILES:
with self.subTest(profile=profile):
path = COMMITTED / f"{profile}.json"
self.assertTrue(path.is_file(), path)
doc = json.loads(path.read_text(encoding="utf-8"))
self.assertEqual(doc["profile"], profile)
self.assertGreaterEqual(doc["counts"]["games_with_bios"], api.FLOORS[profile])
def test_committed_files_are_written_canonically(self) -> None:
for path in sorted(COMMITTED.glob("*.json")):
with self.subTest(path=path.name):
text = path.read_text(encoding="utf-8")
self.assertEqual(api.dump(json.loads(text)), text)
@unittest.skipIf(Draft202012Validator is None, "jsonschema not installed")
def test_committed_files_match_the_schema(self) -> None:
schema = json.loads((ROOT / "schemas" / "arcade-parents.schema.json").read_text())
validator = Draft202012Validator(schema)
for path in sorted(COMMITTED.glob("*.json")):
with self.subTest(path=path.name):
errors = list(validator.iter_errors(json.loads(path.read_text(encoding="utf-8"))))
self.assertEqual(errors, [])
if __name__ == "__main__":
unittest.main()