diff --git a/emulators/xemu-chihiro.yml b/emulators/xemu-chihiro.yml index 1c4f81db5..dd9604372 100644 --- a/emulators/xemu-chihiro.yml +++ b/emulators/xemu-chihiro.yml @@ -117,6 +117,25 @@ files: description: "Base board configuration EEPROM" source_ref: "source hw/xbox/chihiro/chihiro.c:2336-2401, source system/vl.c:3221-3223" + - name: chihiro_eeprom.bin + system: sega-chihiro + required: false + size: 256 + validation: [size] + unsourceable: "generated with the debug key in the data directory when absent and written back by the machine" + description: "Chihiro EEPROM" + note: "A file carrying the retail key is renamed aside and a debug one generated in its place. Any other size than 256 bytes stops the machine with a message." + source_ref: "source system/vl.c:2978-3067" + + - name: chihiro_snapshots.qcow2 + system: sega-chihiro + required: false + config_key: chihiro.roms.snapshot_store_path + unsourceable: "created empty when missing, then holds the VM snapshots" + description: "Chihiro VM snapshot store" + note: "Defaults to the data directory. A store that exists and cannot be opened leaves snapshots unavailable." + source_ref: "source system/vl.c:2051-2110, source config_spec.yml:916-921" + - name: ver1305.bin system: sega-chihiro required: false diff --git a/platforms/retrobat.yml b/platforms/retrobat.yml index a05769063..c37a16eb3 100644 --- a/platforms/retrobat.yml +++ b/platforms/retrobat.yml @@ -4069,6 +4069,69 @@ launchers: depends_on_option: core in capsimgCore, rom extension ".ipf", the target does not exist source_ref: "emulatorLauncher/Generators/LibRetro.Generator.cs:1018-1027, 2750" +- emulator: chihiro + build: standalone + kind: passes_path + names: [] + search_dir: "" + passes_to: "Settings::GetDataLocation, the folder holding settings.ini, which the\ + \ launcher writes beside the binary" + fails_when_missing: false + source_ref: "emulatorLauncher/Generators/Cxbx.Generator.cs:99-101, 158" +- emulator: chihiro-gun + build: standalone + kind: passes_path + names: [] + search_dir: "" + passes_to: "Settings::GetDataLocation, the folder holding settings.ini, which the\ + \ launcher writes beside the binary" + fails_when_missing: false + source_ref: "emulatorLauncher/Generators/Cxbx.Generator.cs:104-106, 158" +- emulator: cxbx-reloaded + build: standalone + kind: passes_path + names: [] + search_dir: "" + passes_to: "Settings::GetDataLocation, the folder holding settings.ini, which the\ + \ launcher writes beside the binary" + fails_when_missing: false + depends_on_option: "neither core nor emulator is \"chihiro\" or \"chihiro-gun\"\ + , when \"cxbx-reloaded\" is not set" + source_ref: "emulatorLauncher/Generators/Cxbx.Generator.cs:111-115, 158" +- emulator: chihiro, chihiro-gun, cxbx-reloaded + build: standalone + kind: fixed_name + names: + - xbox-iso-vfs.exe + case_insensitive: true + search_dir: + passes_to: exec + fails_when_missing: xbox-iso-vfs is required and is not installed + depends_on_option: rom extension ".iso" + source_ref: "emulatorLauncher/Generators/Cxbx.Generator.cs:25-30" +- emulator: chihiro, chihiro-gun, cxbx-reloaded + build: standalone + kind: fixed_name + names: + - dokan2.dll + case_insensitive: true + search_dir: '%DokanLibrary2%' + passes_to: "nothing passed: checked before xbox-iso-vfs.exe mounts the image" + fails_when_missing: Dokan 2 is required and is not installed + depends_on_option: rom extension ".iso" + source_ref: "emulatorLauncher/Generators/Cxbx.Generator.cs:25-26, 33-45" +- emulator: cxbx-reloaded + build: standalone + kind: fixed_name + names: + - EEPROM.bin + case_insensitive: true + search_dir: "" + passes_to: "written from the launcher's eeprom resource when absent, then its language\ + \ set: the emulator reads it from its data folder" + fails_when_missing: "never missing: the launcher writes it" + depends_on_option: system == "xbox" + source_ref: "emulatorLauncher/Generators/Cxbx.Generator.cs:216-223" - emulator: demul build: standalone kind: passes_path @@ -5590,6 +5653,43 @@ launchers: fails_when_missing: "warning '[Generator] Chihiro media board flash missing next\ \ to the BIOS.'" source_ref: "emulatorLauncher/Generators/Xemu.Generator.cs:438-441" +- emulator: xemu + build: standalone + kind: fixed_name + names: + - eeprom.bin + - xbox_hdd.qcow2 + case_insensitive: true + search_dir: saves/ + passes_to: "xemu.toml [sys.files] eeprom_path, hdd_path; written from the launcher's\ + \ eeprom and xbox_hdd_qcow2 resources when absent, the disk image unzipped" + fails_when_missing: "never missing: the launcher writes both" + depends_on_option: "the saves folder is set and exists, not (emulator == \"xemu-chihiro\"\ + \ or system == \"chihiro\")" + source_ref: "emulatorLauncher/Generators/Xemu.Generator.cs:48, 82-106, 278, 281" +- emulator: xemu, xemu-chihiro + build: standalone + kind: copies + names: + - chihiro_eeprom.bin + case_insensitive: true + search_dir: saves/chihiro + passes_to: /chihiro_eeprom.bin, restored when the saved copy is newer + or the emulator folder holds none, copied back after the run + fails_when_missing: false + depends_on_option: emulator == "xemu-chihiro" or system == "chihiro" + source_ref: "emulatorLauncher/Generators/Xemu.Generator.cs:48, 480-520" +- emulator: xemu, xemu-chihiro + build: standalone + kind: fixed_name + names: + - chihiro_snapshots.qcow2 + case_insensitive: true + search_dir: saves/chihiro + passes_to: "xemu.toml [chihiro.roms] snapshot_store_path, created by the emulator" + fails_when_missing: false + depends_on_option: emulator == "xemu-chihiro" or system == "chihiro" + source_ref: "emulatorLauncher/Generators/Xemu.Generator.cs:48, 446-449" - emulator: xm6pro build: standalone kind: copies diff --git a/scripts/cross_reference.py b/scripts/cross_reference.py index fcbb0de1c..6a82613f9 100644 --- a/scripts/cross_reference.py +++ b/scripts/cross_reference.py @@ -1097,6 +1097,11 @@ LAUNCHER_READS_KEY = "_launcher_reads_not_held" # A package record names no file: it records what a package builds. _NAMELESS_KINDS = frozenset({"package"}) +# The folder of the platform's own launcher, as its scraper spells it: a +# program the launcher runs from there (RetroBat's xbox-iso-vfs.exe) is part +# of the platform, never a file to collect. +PLATFORM_OWN_FOLDERS = frozenset({""}) + def launcher_file_names(record: dict) -> list[str]: """The concrete file names a launcher record reads, in code order. @@ -1221,7 +1226,8 @@ def launcher_reads_not_held( amiga-os-130.rom that exists (Amiberry.Generator.cs:674-699). A copy whose source lies outside the BIOS directory brings a file the platform installs itself, from its own package or written by its own script - (RetroPie's lr-vecx copies $md_inst/system.bin). A file handed to the + (RetroPie's lr-vecx copies $md_inst/system.bin), and so does a program + the launcher runs from its own folder. A file handed to the setting a reader's profile entry is read from is that entry's file, which the pack places under the launcher's name. """ @@ -1250,6 +1256,8 @@ def launcher_reads_not_held( base = str(config.get("base_destination") or "bios") if record.get("kind") == "copies" and not launcher_directories(record, base): continue + if str(record.get("search_dir") or "") in PLATFORM_OWN_FOLDERS: + continue settings = name_settings(record, names) def by_setting(name: str) -> bool: diff --git a/scripts/scraper/retrobat_launchers.py b/scripts/scraper/retrobat_launchers.py index 768a1061d..6124e7b90 100644 --- a/scripts/scraper/retrobat_launchers.py +++ b/scripts/scraper/retrobat_launchers.py @@ -26,6 +26,7 @@ SOURCE_FILES = ( f"{GENERATORS}/Bizhawk.Generator.cs", f"{GENERATORS}/Blastem.Generator.cs", f"{GENERATORS}/Citron.Generator.cs", + f"{GENERATORS}/Cxbx.Generator.cs", f"{GENERATORS}/Demul.Generator.cs", f"{GENERATORS}/Desmume.Generator.cs", f"{GENERATORS}/Dolphin.Generator.cs", @@ -1358,6 +1359,287 @@ def xemu(sources: dict[str, Source]) -> list[Record]: return records +def xemu_saves(sources: dict[str, Source]) -> list[Record]: + """The files the xemu generator keeps below the saves folder. + + An Xbox gets eeprom.bin and xbox_hdd.qcow2 in saves/, written + from the launcher's own resources when absent, and their paths in + xemu.toml. A Chihiro keeps chihiro_eeprom.bin there, copied into the + emulator folder before the run and back after it, and its snapshot + store. + """ + src = sources[f"{GENERATORS}/Xemu.Generator.cs"] + chihiro = src.find(r'(_chihiro) = \(emulator == "([\w-]+)" \|\| system == "(\w+)"\);', "Chihiro switch") + chihiro_when = f'emulator == "{chihiro.group(2)}" or system == "{chihiro.group(3)}"' + saves = src.find( + r'if \(!string\.IsNullOrEmpty\(AppConfig\["saves"\]\) && Directory\.Exists\(AppConfig\.GetFullPath\("saves"\)\)\)' + r'\s*\{\s*string savePath = Path\.Combine\(AppConfig\.GetFullPath\("saves"\), system\);', + "Xbox save folder", + ) + eeprom = src.find( + r'if \(!File\.Exists\(Path\.Combine\(savePath, "([^"]+)"\)\)\)\s*\{[^{}]*?' + r'File\.WriteAllBytes\(Path\.Combine\(savePath, "\1"\), Properties\.Resources\.(\w+)\);\s*\}', + "EEPROM written from resources", + saves.end(), + ) + hdd = src.find( + r'if \(!File\.Exists\(Path\.Combine\(savePath, "([^"]+)"\)\)\)\s*\{[^{}]*?' + r'File\.WriteAllBytes\(zipFile, Properties\.Resources\.(\w+)\);[^{}]*?Zip\.Extract\(zipFile, savePath\);[^{}]*?\}', + "hard disk image written from resources", + eeprom.end(), + ) + eeprom_key = src.find( + r'ini\.WriteValue\("([\w.]+)", "(eeprom_path)", "\'" \+ eepromPath', "eeprom_path write" + ) + hdd_key = src.find(r'ini\.WriteValue\("([\w.]+)", "(hdd_path)", "\'" \+ hddPath', "hdd_path write") + records = [ + _record( + "xemu", + "standalone", + "fixed_name", + [eeprom.group(1), hdd.group(1)], + source_ref( + ( + src.path, + [ + src.lines(chihiro.start(), chihiro.end()), + src.lines(saves.start(), hdd.end()), + src.lines(eeprom_key.start(), eeprom_key.end()), + src.lines(hdd_key.start(), hdd_key.end()), + ], + ) + ), + case_insensitive=True, + search_dir="saves/", + passes_to=( + f"xemu.toml [{eeprom_key.group(1)}] {eeprom_key.group(2)}, {hdd_key.group(2)};" + f" written from the launcher's {eeprom.group(2)} and {hdd.group(2)} resources" + " when absent, the disk image unzipped" + ), + fails_when_missing="never missing: the launcher writes both", + depends_on_option=_joined("the saves folder is set and exists", f"not ({chihiro_when})"), + ) + ] + + setup = src.find(r"private void SetupChihiroEeprom\(string path\)", "SetupChihiroEeprom") + body = src.block_at(setup.end()) + folder = src.find( + r'string savePath = Path\.Combine\(AppConfig\.GetFullPath\("saves"\), "(\w+)"\);', + "Chihiro save folder", + body[0], + body[1], + ) + pair = src.find( + r'_chihiroEeprom = Path\.Combine\(path, "([^"]+)"\);\s*' + r'_chihiroEepromSave = Path\.Combine\(savePath, "\1"\);', + "Chihiro EEPROM pair", + folder.end(), + body[1], + ) + save = src.find(r"private void SaveChihiroEeprom\(\)", "SaveChihiroEeprom") + save_body = src.block_at(save.end()) + records.append( + _record( + "xemu, xemu-chihiro", + "standalone", + "copies", + [pair.group(1)], + source_ref( + ( + src.path, + [ + src.lines(chihiro.start(), chihiro.end()), + src.statement_span(body), + src.statement_span(save_body), + ], + ) + ), + case_insensitive=True, + search_dir=f"saves/{folder.group(1)}", + passes_to=( + f"/{pair.group(1)}, restored when the saved copy is newer or the" + " emulator folder holds none, copied back after the run" + ), + fails_when_missing=False, + depends_on_option=chihiro_when, + ) + ) + + store = src.find( + r'string savePath = Path\.Combine\(AppConfig\.GetFullPath\("saves"\), "(\w+)"\);\s*' + r'if \(!Directory\.Exists\(savePath\)\) try \{ Directory\.CreateDirectory\(savePath\); \} catch \{ \}\s*' + r'if \(Directory\.Exists\(savePath\)\)\s*' + r'ini\.WriteValue\("([\w.]+)", "(\w+)", "\'" \+ Path\.Combine\(savePath, "([^"]+)"\)', + "Chihiro snapshot store", + ) + records.append( + _record( + "xemu, xemu-chihiro", + "standalone", + "fixed_name", + [store.group(4)], + source_ref((src.path, [src.lines(chihiro.start(), chihiro.end()), + src.lines(store.start(), store.end())])), + case_insensitive=True, + search_dir=f"saves/{store.group(1)}", + passes_to=f"xemu.toml [{store.group(2)}] {store.group(3)}, created by the emulator", + fails_when_missing=False, + depends_on_option=chihiro_when, + ) + ) + return records + + +# What the Cxbx generator hands each build: the data folder is the one its +# settings.ini sits in (Cxbx-Reloaded Settings::FindSettingsLocation), which +# the launcher writes beside the binary. +CXBX_DATA_FOLDER = "Settings::GetDataLocation" + + +def cxbx(sources: dict[str, Source]) -> list[Record]: + """The folder each Cxbx build runs from, and the EEPROM written there. + + The generator picks the emulator folder by core or emulator name, opens + settings.ini in it, and for an Xbox writes EEPROM.bin into the + cxbx-reloaded folder from its own resource when absent. + """ + src = sources[f"{GENERATORS}/Cxbx.Generator.cs"] + named = src.find_all( + r'(?:else )?if \(\(core != null && core == "([\w-]+)"\) \|\| ' + r'\(emulator != null && emulator == "\1"\)\)\s*\{\s*path = AppConfig\.GetFullPath\("([\w-]+)"\);', + "emulator folder by name", + ) + fallbacks = src.find_all( + r'if \(string\.IsNullOrEmpty\(path\)\)\s*path = AppConfig\.GetFullPath\("([\w-]+)"\);', + "default emulator folder", + ) + settings = src.find( + r'IniFile\.FromFile\(Path\.Combine\(path, "(settings\.ini)"\)', "settings.ini beside the binary" + ) + settings_span = src.lines(settings.start(), settings.end()) + passes_to = ( + f"{CXBX_DATA_FOLDER}, the folder holding {settings.group(1)}," + " which the launcher writes beside the binary" + ) + records = [] + for match in named: + records.append( + _record( + match.group(1), + "standalone", + "passes_path", + [], + source_ref((src.path, [src.lines(match.start(), match.end()), settings_span])), + search_dir=f'', + passes_to=passes_to, + fails_when_missing=False, + ) + ) + first, *rest = fallbacks + others = "neither core nor emulator is " + " or ".join(f'"{m.group(1)}"' for m in named) + records.append( + _record( + first.group(1), + "standalone", + "passes_path", + [], + source_ref( + ( + src.path, + [src.lines(first.start(), first.end()), + *(src.lines(m.start(), m.end()) for m in rest), settings_span], + ) + ), + search_dir=f'', + passes_to=passes_to, + fails_when_missing=False, + depends_on_option=_joined( + others, + *(f' when "{first.group(1)}" is not set' + for m in rest), + ), + ) + ) + records.extend(_cxbx_mount(src, [*(m.group(1) for m in named), first.group(1)])) + eeprom = src.find( + r'if \(system == "(\w+)"\)\s*\{\s*string (\w+) = Path\.Combine\(AppConfig\.GetFullPath\("([\w-]+)"\), "([^"]+)"\);' + r'\s*if \(!File\.Exists\(\2\)\)\s*File\.WriteAllBytes\(\2, Properties\.Resources\.(\w+)\);' + r'\s*WriteXboxEEPROM\(\2\);\s*\}', + "EEPROM written from resources", + ) + records.append( + _record( + eeprom.group(3), + "standalone", + "fixed_name", + [eeprom.group(4)], + source_ref((src.path, [src.lines(eeprom.start(), eeprom.end())])), + case_insensitive=True, + search_dir=f'', + passes_to=( + f"written from the launcher's {eeprom.group(5)} resource when absent," + " then its language set: the emulator reads it from its data folder" + ), + fails_when_missing="never missing: the launcher writes it", + depends_on_option=f'system == "{eeprom.group(1)}"', + ) + ) + return records + + +def _cxbx_mount(src: Source, emulators: list[str]) -> list[Record]: + """What the generator needs to mount an .iso before Cxbx opens it: its + own xbox-iso-vfs.exe, run from the launcher's folder, and the Dokan 2 + runtime installed on the system.""" + iso = src.find( + r'if \(Path\.GetExtension\(rom\)\.ToLowerInvariant\(\) != "(\.\w+)"\)\s*return rom;', + "mount for disc images only", + ) + vfs = src.find( + r'string (\w+) = Path\.Combine\(Path\.GetDirectoryName\(typeof\(\w+\)\.Assembly\.Location\), "([^"]+)"\);' + r'\s*if \(!File\.Exists\(\1\)\)\s*throw new ApplicationException\("([^"]+)"\);', + "mount tool beside the launcher", + iso.end(), + ) + dokan = src.find( + r'string (\w+) = Environment\.GetEnvironmentVariable\("(\w+)"\);.*?' + r'\1 = Path\.Combine\(\1, "([^"]+)"\);\s*if \(!File\.Exists\(\1\)\)\s*\{[^{}]*?' + r'throw new ApplicationException\("([^"]+)"\);\s*\}', + "Dokan runtime check", + vfs.end(), + flags=re.S, + ) + iso_span = src.lines(iso.start(), iso.end()) + when = f'rom extension "{iso.group(1)}"' + emulator = ", ".join(emulators) + return [ + _record( + emulator, + "standalone", + "fixed_name", + [vfs.group(2)], + source_ref((src.path, [iso_span, src.lines(vfs.start(), vfs.end())])), + case_insensitive=True, + search_dir="", + passes_to="exec", + fails_when_missing=vfs.group(3), + depends_on_option=when, + ), + _record( + emulator, + "standalone", + "fixed_name", + [dokan.group(3)], + source_ref((src.path, [iso_span, src.lines(dokan.start(), dokan.end())])), + case_insensitive=True, + search_dir=f"%{dokan.group(2)}%", + passes_to=f"nothing passed: checked before {vfs.group(2)} mounts the image", + fails_when_missing=dokan.group(4), + depends_on_option=when, + ), + ] + + def openmsx(sources: dict[str, Source]) -> list[Record]: src = sources[f"{GENERATORS}/OpenMSX.Generator.cs"] table = src.find( @@ -3521,6 +3803,7 @@ EXTRACTORS: tuple[Callable[[dict[str, Source]], list[Record]], ...] = ( blastem, bsyndrome, capsimg, + cxbx, demul, desmume, dolphin, @@ -3551,6 +3834,7 @@ EXTRACTORS: tuple[Callable[[dict[str, Source]], list[Record]], ...] = ( value_folders, winuae, xemu, + xemu_saves, xm6pro, xroar, ) diff --git a/tests/test_launcher_gaps.py b/tests/test_launcher_gaps.py index 245654938..e1bdac934 100644 --- a/tests/test_launcher_gaps.py +++ b/tests/test_launcher_gaps.py @@ -77,6 +77,8 @@ LAUNCHERS = [ _record("setemu", "copies", ["user.sf2"]), _record("caseemu", "fixed_name", ["mixed.rom"]), _record("foldemu", "fixed_name", ["MIXED.rom"], case_insensitive=True), + _record("toolemu", "fixed_name", ["tool.exe"], search_dir="", + passes_to="exec"), _record("labelemu", "core_option", ["labelled.rom (CDZ)"]), ] @@ -160,6 +162,11 @@ class LauncherGapTest(unittest.TestCase): ) return {(r["platform"], r["name"]): r for r in rows} + def test_a_program_of_the_launcher_itself_is_not_reported(self) -> None: + """RetroBat runs its own xbox-iso-vfs.exe to mount an Xbox disc + image: part of the platform, no file to collect.""" + self.assertNotIn("tool.exe", {name for _, name in self._rows()}) + def test_a_held_name_is_not_reported(self) -> None: names = {name for _, name in self._rows()} self.assertNotIn("held.bin", names) diff --git a/tests/test_launchers_retrobat.py b/tests/test_launchers_retrobat.py index 5d9c7bafd..b2d8ded7a 100644 --- a/tests/test_launchers_retrobat.py +++ b/tests/test_launchers_retrobat.py @@ -1103,6 +1103,150 @@ class FilesNeededTogether(unittest.TestCase): ) +XEMU_SAVES = """ + public override ProcessStartInfo Generate(string system, string emulator, string core, string rom) + { + _chihiro = (emulator == "xemu-chihiro" || system == "chihiro"); + if (!string.IsNullOrEmpty(AppConfig["saves"]) && Directory.Exists(AppConfig.GetFullPath("saves"))) + { + string savePath = Path.Combine(AppConfig.GetFullPath("saves"), system); + if (!_chihiro) + { + if (!File.Exists(Path.Combine(savePath, "eeprom.bin"))) + { + File.WriteAllBytes(Path.Combine(savePath, "eeprom.bin"), Properties.Resources.eeprom); + } + if (!File.Exists(Path.Combine(savePath, "xbox_hdd.qcow2"))) + { + string zipFile = Path.Combine(savePath, "xbox_hdd.qcow2.zip"); + File.WriteAllBytes(zipFile, Properties.Resources.xbox_hdd_qcow2); + Zip.Extract(zipFile, savePath); + File.Delete(zipFile); + } + } + } + } + + private void SetupTOMLConfiguration(string eepromPath, string hddPath) + { + if (!string.IsNullOrEmpty(eepromPath)) + ini.WriteValue("sys.files", "eeprom_path", "'" + eepromPath + "'"); + if (!string.IsNullOrEmpty(hddPath)) + ini.WriteValue("sys.files", "hdd_path", "'" + hddPath + "'"); + } + + private void SetupChihiroConfiguration() + { + string savePath = Path.Combine(AppConfig.GetFullPath("saves"), "chihiro"); + if (!Directory.Exists(savePath)) try { Directory.CreateDirectory(savePath); } catch { } + if (Directory.Exists(savePath)) + ini.WriteValue("chihiro.roms", "snapshot_store_path", "'" + Path.Combine(savePath, "chihiro_snapshots.qcow2") + "'"); + } + + private void SetupChihiroEeprom(string path) + { + string savePath = Path.Combine(AppConfig.GetFullPath("saves"), "chihiro"); + _chihiroEeprom = Path.Combine(path, "chihiro_eeprom.bin"); + _chihiroEepromSave = Path.Combine(savePath, "chihiro_eeprom.bin"); + } + + private void SaveChihiroEeprom() + { + if (File.Exists(_chihiroEeprom)) + File.Copy(_chihiroEeprom, _chihiroEepromSave, true); + } +""" + +CXBX = """ + private string MountIso(string rom) + { + if (Path.GetExtension(rom).ToLowerInvariant() != ".iso") + return rom; + string xboxIsoVfsPath = Path.Combine(Path.GetDirectoryName(typeof(Installer).Assembly.Location), "xbox-iso-vfs.exe"); + if (!File.Exists(xboxIsoVfsPath)) + throw new ApplicationException("xbox-iso-vfs is required and is not installed"); + string dokan = Environment.GetEnvironmentVariable("DokanLibrary2"); + dokan = Path.Combine(dokan, "dokan2.dll"); + if (!File.Exists(dokan)) + { + throw new ApplicationException("Dokan 2 is required and is not installed"); + } + } + + public override ProcessStartInfo Generate(string system, string emulator, string core, string rom) + { + string path = null; + if ((core != null && core == "chihiro") || (emulator != null && emulator == "chihiro")) + { + path = AppConfig.GetFullPath("chihiro"); + } + else if ((core != null && core == "chihiro-gun") || (emulator != null && emulator == "chihiro-gun")) + { + path = AppConfig.GetFullPath("chihiro-ds"); + } + if (string.IsNullOrEmpty(path)) + path = AppConfig.GetFullPath("cxbx-reloaded"); + if (string.IsNullOrEmpty(path)) + path = AppConfig.GetFullPath("cxbx-r"); + using (var ini = IniFile.FromFile(Path.Combine(path, "settings.ini"), IniOptions.KeepEmptyValues)) + { + } + if (system == "xbox") + { + string eeprom = Path.Combine(AppConfig.GetFullPath("cxbx-reloaded"), "EEPROM.bin"); + if (!File.Exists(eeprom)) + File.WriteAllBytes(eeprom, Properties.Resources.eeprom); + + WriteXboxEEPROM(eeprom); + } + } +""" + + +class XboxFilesTheLauncherWrites(unittest.TestCase): + def test_xemu_keeps_its_eeprom_and_disk_below_saves(self): + records = rl.xemu_saves(_sources(**{f"{G}/Xemu.Generator.cs": XEMU_SAVES})) + self.assertEqual( + [(r["emulator"], r["kind"], r["names"], r["search_dir"], r["source_ref"]) + for r in records], + [ + ("xemu", "fixed_name", ["eeprom.bin", "xbox_hdd.qcow2"], "saves/", + f"{G}/Xemu.Generator.cs:3-19, 27-29"), + ("xemu, xemu-chihiro", "copies", ["chihiro_eeprom.bin"], "saves/chihiro", + f"{G}/Xemu.Generator.cs:3, 40-51"), + ("xemu, xemu-chihiro", "fixed_name", ["chihiro_snapshots.qcow2"], "saves/chihiro", + f"{G}/Xemu.Generator.cs:3, 34-37"), + ], + ) + self.assertIn("eeprom_path, hdd_path", records[0]["passes_to"]) + self.assertIn('not (emulator == "xemu-chihiro" or system == "chihiro")', + records[0]["depends_on_option"]) + + def test_cxbx_runs_from_its_folder_and_gets_the_eeprom_there(self): + records = rl.cxbx(_sources(**{f"{G}/Cxbx.Generator.cs": CXBX})) + self.assertEqual( + [(r["emulator"], r["kind"], r["names"], r["search_dir"], r["source_ref"]) + for r in records], + [ + ("chihiro", "passes_path", [], '', + f"{G}/Cxbx.Generator.cs:19-21, 31"), + ("chihiro-gun", "passes_path", [], '', + f"{G}/Cxbx.Generator.cs:23-25, 31"), + ("cxbx-reloaded", "passes_path", [], '', + f"{G}/Cxbx.Generator.cs:27-31"), + ("chihiro, chihiro-gun, cxbx-reloaded", "fixed_name", ["xbox-iso-vfs.exe"], + "", f"{G}/Cxbx.Generator.cs:3-7"), + ("chihiro, chihiro-gun, cxbx-reloaded", "fixed_name", ["dokan2.dll"], + "%DokanLibrary2%", f"{G}/Cxbx.Generator.cs:3-4, 8-13"), + ("cxbx-reloaded", "fixed_name", ["EEPROM.bin"], + '', f"{G}/Cxbx.Generator.cs:34-41"), + ], + ) + self.assertTrue(all(r["passes_to"].startswith(rl.CXBX_DATA_FOLDER) for r in records[:3])) + self.assertEqual(records[3]["passes_to"], "exec") + self.assertEqual(records[5]["depends_on_option"], 'system == "xbox"') + + class HelpersAndCopies(unittest.TestCase): def test_blastem_one_record_per_helper_call(self): records = rl.blastem(_sources(**{f"{G}/Blastem.Generator.cs": BLASTEM}))