mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-24 04:39:06 -05:00
QEMU's 79 files go to QEMU/QEMU and Roland's four loose ROMs to MT-32 and CM-32L. The mt32-roms, soundcanvas-roms and sounddata trees stay as the code addresses them. Chihiro joins Sega and the Oric joins Tangerine, the names the site and libretro already use. No destination held other bytes. The three QEMU EDK2 images are release assets, and their .gitignore lines follow them. Database and manifests are regenerated together. verify --all and cross_reference give the same output once moved paths are mapped.
571 lines
20 KiB
Python
571 lines
20 KiB
Python
#!/usr/bin/env python3
|
|
"""Migrate current flat structure AND other branches to bios/Manufacturer/Console/ hierarchy.
|
|
|
|
Usage:
|
|
python scripts/migrate.py [--dry-run] [--source DIR] [--target DIR] [--include-branches]
|
|
|
|
Reads existing directories like "Sony - PlayStation" and moves files to
|
|
"bios/Sony/PlayStation/". With --include-branches, also extracts unique BIOS files
|
|
from all remote branches (RetroArch, RetroPie, Recalbox, batocera, Other).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, os.path.dirname(__file__))
|
|
from common import compute_hashes
|
|
|
|
SYSTEM_MAP = {
|
|
"3DO Company, The - 3DO": ("3DO Company", "3DO"),
|
|
"Arcade": ("Arcade", "Arcade"),
|
|
"Atari - 400-800": ("Atari", "400-800"),
|
|
"Atari - 5200": ("Atari", "5200"),
|
|
"Atari - 7800": ("Atari", "7800"),
|
|
"Atari - Lynx": ("Atari", "Lynx"),
|
|
"Atari - ST": ("Atari", "ST"),
|
|
"Coleco - ColecoVision": ("Coleco", "ColecoVision"),
|
|
"Commodore - Amiga": ("Commodore", "Amiga"),
|
|
"Fairchild Channel F": ("Fairchild", "Channel F"),
|
|
"Id Software - Doom": ("Id Software", "Doom"),
|
|
"J2ME": ("Java", "J2ME"),
|
|
"MacII": ("Apple", "Macintosh II"),
|
|
"Magnavox - Odyssey2": ("Magnavox", "Odyssey2"),
|
|
"Mattel - Intellivision": ("Mattel", "Intellivision"),
|
|
"Microsoft - MSX": ("Microsoft", "MSX"),
|
|
"NEC - PC Engine - TurboGrafx 16 - SuperGrafx": ("NEC", "PC Engine"),
|
|
"NEC - PC-98": ("NEC", "PC-98"),
|
|
"NEC - PC-FX": ("NEC", "PC-FX"),
|
|
"Nintendo - Famicom Disk System": ("Nintendo", "Famicom Disk System"),
|
|
"Nintendo - Game Boy Advance": ("Nintendo", "Game Boy Advance"),
|
|
"Nintendo - GameCube": ("Nintendo", "GameCube"),
|
|
"Nintendo - Gameboy": ("Nintendo", "Game Boy"),
|
|
"Nintendo - Gameboy Color": ("Nintendo", "Game Boy Color"),
|
|
"Nintendo - Nintendo 64DD": ("Nintendo", "Nintendo 64DD"),
|
|
"Nintendo - Nintendo DS": ("Nintendo", "Nintendo DS"),
|
|
"Nintendo - Nintendo Entertainment System": ("Nintendo", "NES"),
|
|
"Nintendo - Pokemon Mini": ("Nintendo", "Pokemon Mini"),
|
|
"Nintendo - Satellaview": ("Nintendo", "Satellaview"),
|
|
"Nintendo - SuFami Turbo": ("Nintendo", "SuFami Turbo"),
|
|
"Nintendo - Super Game Boy": ("Nintendo", "Super Game Boy"),
|
|
"Nintendo - Super Nintendo Entertainment System": ("Nintendo", "SNES"),
|
|
"Phillips - Videopac+": ("Philips", "Videopac+"),
|
|
"SNK - NeoGeo CD": ("SNK", "Neo Geo CD"),
|
|
"ScummVM": ("ScummVM", "ScummVM"),
|
|
"Sega - Dreamcast": ("Sega", "Dreamcast"),
|
|
"Sega - Game Gear": ("Sega", "Game Gear"),
|
|
"Sega - Master System - Mark III": ("Sega", "Master System"),
|
|
"Sega - Mega CD - Sega CD": ("Sega", "Mega CD"),
|
|
"Sega - Mega Drive - Genesis": ("Sega", "Mega Drive"),
|
|
"Sega - Saturn": ("Sega", "Saturn"),
|
|
"Sharp - X1": ("Sharp", "X1"),
|
|
"Sharp - X68000": ("Sharp", "X68000"),
|
|
"Sinclair - ZX Spectrum": ("Sinclair", "ZX Spectrum"),
|
|
"Sony - PlayStation": ("Sony", "PlayStation"),
|
|
"Sony - PlayStation Portable": ("Sony", "PlayStation Portable"),
|
|
"Wolfenstein 3D": ("Id Software", "Wolfenstein 3D"),
|
|
}
|
|
|
|
BIOS_FILE_MAP = {
|
|
"panafz": ("3DO Company", "3DO"),
|
|
"goldstar.bin": ("3DO Company", "3DO"),
|
|
"sanyotry.bin": ("3DO Company", "3DO"),
|
|
"3do_arcade_saot.bin": ("3DO Company", "3DO"),
|
|
"3dobios.zip": ("3DO Company", "3DO"),
|
|
"cpc464.rom": ("Amstrad", "CPC"),
|
|
"cpc664.rom": ("Amstrad", "CPC"),
|
|
"cpc6128.rom": ("Amstrad", "CPC"),
|
|
"neogeo.zip": ("SNK", "Neo Geo"),
|
|
"pgm.zip": ("Arcade", "Arcade"),
|
|
"skns.zip": ("Arcade", "Arcade"),
|
|
"bubsys.zip": ("Arcade", "Arcade"),
|
|
"cchip.zip": ("Arcade", "Arcade"),
|
|
"decocass.zip": ("Arcade", "Arcade"),
|
|
"isgsm.zip": ("Arcade", "Arcade"),
|
|
"midssio.zip": ("Arcade", "Arcade"),
|
|
"nmk004.zip": ("Arcade", "Arcade"),
|
|
"ym2608.zip": ("Arcade", "Arcade"),
|
|
"qsound.zip": ("Arcade", "Arcade"),
|
|
"ATARIBAS.ROM": ("Atari", "400-800"),
|
|
"ATARIOSA.ROM": ("Atari", "400-800"),
|
|
"ATARIOSB.ROM": ("Atari", "400-800"),
|
|
"ATARIXL.ROM": ("Atari", "400-800"),
|
|
"BB01R4_OS.ROM": ("Atari", "400-800"),
|
|
"XEGAME.ROM": ("Atari", "400-800"),
|
|
"5200.rom": ("Atari", "5200"),
|
|
"7800 BIOS (U).rom": ("Atari", "7800"),
|
|
"7800 BIOS (E).rom": ("Atari", "7800"),
|
|
"lynxboot.img": ("Atari", "Lynx"),
|
|
"tos.img": ("Atari", "ST"),
|
|
"colecovision.rom": ("Coleco", "ColecoVision"),
|
|
"coleco.rom": ("Coleco", "ColecoVision"),
|
|
"kick33180.A500": ("Commodore", "Amiga"),
|
|
"kick34005.A500": ("Commodore", "Amiga"),
|
|
"kick34005.CDTV": ("Commodore", "Amiga"),
|
|
"kick37175.A500": ("Commodore", "Amiga"),
|
|
"kick37350.A600": ("Commodore", "Amiga"),
|
|
"kick39106.A1200": ("Commodore", "Amiga"),
|
|
"kick39106.A4000": ("Commodore", "Amiga"),
|
|
"kick40060.CD32": ("Commodore", "Amiga"),
|
|
"kick40060.CD32.ext": ("Commodore", "Amiga"),
|
|
"kick40063.A600": ("Commodore", "Amiga"),
|
|
"kick40068.A1200": ("Commodore", "Amiga"),
|
|
"kick40068.A4000": ("Commodore", "Amiga"),
|
|
"sl31253.bin": ("Fairchild", "Channel F"),
|
|
"sl31254.bin": ("Fairchild", "Channel F"),
|
|
"sl90025.bin": ("Fairchild", "Channel F"),
|
|
"prboom.wad": ("Id Software", "Doom"),
|
|
"ecwolf.pk3": ("Id Software", "Wolfenstein 3D"),
|
|
"MacII.ROM": ("Apple", "Macintosh II"),
|
|
"MacIIx.ROM": ("Apple", "Macintosh II"),
|
|
"vMac.ROM": ("Apple", "Macintosh II"),
|
|
"o2rom.bin": ("Magnavox", "Odyssey2"),
|
|
"g7400.bin": ("Philips", "Videopac+"),
|
|
"jopac.bin": ("Philips", "Videopac+"),
|
|
"exec.bin": ("Mattel", "Intellivision"),
|
|
"grom.bin": ("Mattel", "Intellivision"),
|
|
"ECS.bin": ("Mattel", "Intellivision"),
|
|
"IVOICE.BIN": ("Mattel", "Intellivision"),
|
|
"MSX.ROM": ("Microsoft", "MSX"),
|
|
"MSX2.ROM": ("Microsoft", "MSX"),
|
|
"MSX2EXT.ROM": ("Microsoft", "MSX"),
|
|
"MSX2P.ROM": ("Microsoft", "MSX"),
|
|
"MSX2PEXT.ROM": ("Microsoft", "MSX"),
|
|
"syscard1.pce": ("NEC", "PC Engine"),
|
|
"syscard2.pce": ("NEC", "PC Engine"),
|
|
"syscard2u.pce": ("NEC", "PC Engine"),
|
|
"syscard3.pce": ("NEC", "PC Engine"),
|
|
"syscard3u.pce": ("NEC", "PC Engine"),
|
|
"gexpress.pce": ("NEC", "PC Engine"),
|
|
"pcfx.rom": ("NEC", "PC-FX"),
|
|
"disksys.rom": ("Nintendo", "Famicom Disk System"),
|
|
"gba_bios.bin": ("Nintendo", "Game Boy Advance"),
|
|
"gb_bios.bin": ("Nintendo", "Game Boy"),
|
|
"dmg_boot.bin": ("Nintendo", "Game Boy"),
|
|
"gbc_bios.bin": ("Nintendo", "Game Boy Color"),
|
|
"BS-X.bin": ("Nintendo", "Satellaview"),
|
|
"sgb_bios.bin": ("Nintendo", "Super Game Boy"),
|
|
"sgb_boot.bin": ("Nintendo", "Super Game Boy"),
|
|
"sgb2_boot.bin": ("Nintendo", "Super Game Boy"),
|
|
"SGB1.sfc": ("Nintendo", "Super Game Boy"),
|
|
"SGB2.sfc": ("Nintendo", "Super Game Boy"),
|
|
"bios7.bin": ("Nintendo", "Nintendo DS"),
|
|
"bios9.bin": ("Nintendo", "Nintendo DS"),
|
|
"firmware.bin": ("Nintendo", "Nintendo DS"),
|
|
"biosnds7.bin": ("Nintendo", "Nintendo DS"),
|
|
"biosnds9.bin": ("Nintendo", "Nintendo DS"),
|
|
"dsfirmware.bin": ("Nintendo", "Nintendo DS"),
|
|
"biosdsi7.bin": ("Nintendo", "Nintendo DS"),
|
|
"biosdsi9.bin": ("Nintendo", "Nintendo DS"),
|
|
"dsifirmware.bin": ("Nintendo", "Nintendo DS"),
|
|
"bios.min": ("Nintendo", "Pokemon Mini"),
|
|
"64DD_IPL.bin": ("Nintendo", "Nintendo 64DD"),
|
|
"dc_boot.bin": ("Sega", "Dreamcast"),
|
|
"dc_flash.bin": ("Sega", "Dreamcast"),
|
|
"bios.gg": ("Sega", "Game Gear"),
|
|
"bios_E.sms": ("Sega", "Master System"),
|
|
"bios_J.sms": ("Sega", "Master System"),
|
|
"bios_U.sms": ("Sega", "Master System"),
|
|
"bios_CD_E.bin": ("Sega", "Mega CD"),
|
|
"bios_CD_J.bin": ("Sega", "Mega CD"),
|
|
"bios_CD_U.bin": ("Sega", "Mega CD"),
|
|
"bios_MD.bin": ("Sega", "Mega Drive"),
|
|
"mpr-17933.bin": ("Sega", "Saturn"),
|
|
"mpr-18811-mx.ic1": ("Sega", "Saturn"),
|
|
"mpr-19367-mx.ic1": ("Sega", "Saturn"),
|
|
"saturn_bios.bin": ("Sega", "Saturn"),
|
|
"sega_101.bin": ("Sega", "Saturn"),
|
|
"stvbios.zip": ("Sega", "Saturn"),
|
|
"scph1001.bin": ("Sony", "PlayStation"),
|
|
"SCPH1001.BIN": ("Sony", "PlayStation"),
|
|
"scph5500.bin": ("Sony", "PlayStation"),
|
|
"scph5501.bin": ("Sony", "PlayStation"),
|
|
"scph5502.bin": ("Sony", "PlayStation"),
|
|
"scph7001.bin": ("Sony", "PlayStation"),
|
|
"scph101.bin": ("Sony", "PlayStation"),
|
|
"ps1_rom.bin": ("Sony", "PlayStation"),
|
|
"psxonpsp660.bin": ("Sony", "PlayStation"),
|
|
"PSXONPSP660.BIN": ("Sony", "PlayStation Portable"),
|
|
"scummvm.zip": ("ScummVM", "ScummVM"),
|
|
"MT32_CONTROL.ROM": ("Roland", "MT-32"),
|
|
"MT32_PCM.ROM": ("Roland", "MT-32"),
|
|
}
|
|
|
|
PATH_PREFIX_MAP = {
|
|
"neocd/": ("SNK", "Neo Geo CD"),
|
|
"dc/": ("Sega", "Dreamcast"),
|
|
"np2kai/": ("NEC", "PC-98"),
|
|
"quasi88/": ("NEC", "PC-98"),
|
|
"keropi/": ("Sharp", "X68000"),
|
|
"xmil/": ("Sharp", "X1"),
|
|
"fuse/": ("Sinclair", "ZX Spectrum"),
|
|
"vice/": ("Commodore", "C128"),
|
|
"bk/": ("Elektronika", "BK"),
|
|
"dragon/": ("Dragon", "Dragon"),
|
|
"oricutron/": ("Tangerine", "Oric"),
|
|
"trs80coco/": ("Tandy", "CoCo"),
|
|
"ti994a/": ("Texas Instruments", "TI-99"),
|
|
"gamecube/": ("Nintendo", "GameCube"),
|
|
"Mupen64plus/": ("Nintendo", "Nintendo 64DD"),
|
|
"ps2/": ("Sony", "PlayStation 2"),
|
|
"fmtowns/": ("Fujitsu", "FM Towns"),
|
|
"mame/": ("Arcade", "MAME"),
|
|
"fbneo/": ("Arcade", "Arcade"),
|
|
"saves/3ds/": ("Nintendo", "3DS"),
|
|
"saves/citra-emu/": ("Nintendo", "3DS"),
|
|
"saves/dolphin-emu/": ("Nintendo", "Wii"),
|
|
"saves/xbox/": ("Microsoft", "Xbox"),
|
|
"cemu/": ("Nintendo", "Wii U"),
|
|
"wsh57/": ("Other", "Misc"),
|
|
"Machines/COL - ColecoVision/": ("Coleco", "ColecoVision"),
|
|
"Machines/Shared Roms/": ("Microsoft", "MSX"),
|
|
"Sony - PlayStation 2/": ("Sony", "PlayStation 2"),
|
|
"Sony - PlayStation/": ("Sony", "PlayStation"),
|
|
}
|
|
|
|
TOS_PATTERN_MAP = {
|
|
"tos": ("Atari", "ST"),
|
|
}
|
|
|
|
SKIP_LARGE_ROM_DIRS = {"roms/"}
|
|
|
|
BRANCHES = ["RetroArch", "RetroPie", "Recalbox", "batocera", "Other"]
|
|
|
|
SKIP_FILES = {
|
|
"README.md",
|
|
".gitignore",
|
|
"desktop.ini",
|
|
"telemetry_id",
|
|
"citra_log.txt",
|
|
}
|
|
SKIP_EXTENSIONS = {".txt", ".log", ".pem", ".nvm", ".ctg", ".exe", ".bat", ".sh"}
|
|
|
|
|
|
def sha1_blob(data: bytes) -> str:
|
|
"""Compute SHA1 hash of raw bytes."""
|
|
return hashlib.sha1(data).hexdigest()
|
|
|
|
|
|
def classify_file(filepath: str) -> tuple:
|
|
"""Determine (Manufacturer, Console) for a file path from a branch.
|
|
|
|
Returns None if the file should be skipped.
|
|
"""
|
|
name = os.path.basename(filepath)
|
|
|
|
if name in SKIP_FILES:
|
|
return None
|
|
ext = os.path.splitext(name)[1].lower()
|
|
if ext in SKIP_EXTENSIONS:
|
|
return None
|
|
|
|
clean = filepath
|
|
for prefix in (
|
|
"bios/",
|
|
"BIOS/",
|
|
"roms/fba/",
|
|
"roms/fbneo/",
|
|
"roms/mame/",
|
|
"roms/mame-libretro/",
|
|
"roms/neogeo/",
|
|
"roms/naomi/",
|
|
"roms/atomiswave/",
|
|
"roms/macintosh/",
|
|
):
|
|
if clean.startswith(prefix):
|
|
clean = clean[len(prefix) :]
|
|
break
|
|
|
|
if filepath.startswith("roms/") and not any(
|
|
filepath.startswith(p)
|
|
for p in (
|
|
"roms/fba/",
|
|
"roms/fbneo/",
|
|
"roms/mame/",
|
|
"roms/mame-libretro/",
|
|
"roms/neogeo/",
|
|
"roms/naomi/",
|
|
"roms/atomiswave/",
|
|
"roms/macintosh/",
|
|
)
|
|
):
|
|
return None
|
|
|
|
for prefix, target in PATH_PREFIX_MAP.items():
|
|
if clean.startswith(prefix):
|
|
return target
|
|
|
|
if name in BIOS_FILE_MAP:
|
|
return BIOS_FILE_MAP[name]
|
|
|
|
for prefix, target in BIOS_FILE_MAP.items():
|
|
if name.lower().startswith(prefix.lower()) and len(prefix) > 3:
|
|
return target
|
|
|
|
if name.startswith("tos") and name.endswith(".img"):
|
|
return ("Atari", "ST")
|
|
|
|
if name.startswith("kick") and (name.endswith(".rom") or "." in name):
|
|
return ("Commodore", "Amiga")
|
|
|
|
if name.startswith("amiga-"):
|
|
return ("Commodore", "Amiga")
|
|
|
|
if name.upper().startswith("SCPH"):
|
|
if "70004" in name or "39001" in name or "30004" in name or "10000" in name:
|
|
return ("Sony", "PlayStation 2")
|
|
return ("Sony", "PlayStation")
|
|
|
|
if name.endswith(".zip") and filepath.startswith(("roms/", "BIOS/")):
|
|
return ("Arcade", "Arcade")
|
|
|
|
if "saves/" in filepath:
|
|
return None
|
|
|
|
if name.endswith(".chd"):
|
|
return None
|
|
|
|
if name.endswith((".img", ".lst", ".dat")) and "saves/" in filepath:
|
|
return None
|
|
|
|
return None
|
|
|
|
|
|
def get_subpath(filepath: str, manufacturer: str, console: str) -> str:
|
|
"""Get the sub-path within the console directory (for nested files like neocd/*)."""
|
|
name = os.path.basename(filepath)
|
|
|
|
clean = filepath
|
|
for prefix in ("bios/", "BIOS/"):
|
|
if clean.startswith(prefix):
|
|
clean = clean[len(prefix) :]
|
|
break
|
|
|
|
for prefix in PATH_PREFIX_MAP:
|
|
if clean.startswith(prefix):
|
|
remaining = clean[len(prefix) :]
|
|
if "/" in remaining:
|
|
return remaining
|
|
return remaining
|
|
|
|
return name
|
|
|
|
|
|
def extract_from_branches(target: Path, dry_run: bool, existing_hashes: set) -> int:
|
|
"""Extract BIOS files from all branches into the target structure."""
|
|
extracted = 0
|
|
|
|
for branch in BRANCHES:
|
|
ref = f"origin/{branch}"
|
|
|
|
try:
|
|
subprocess.run(
|
|
["git", "rev-parse", "--verify", ref], capture_output=True, check=True
|
|
)
|
|
except subprocess.CalledProcessError:
|
|
print(f" Branch {branch} not found, skipping")
|
|
continue
|
|
|
|
result = subprocess.run(
|
|
["git", "ls-tree", "-r", "--name-only", ref], capture_output=True, text=True
|
|
)
|
|
files = result.stdout.strip().split("\n")
|
|
print(f"\n Branch '{branch}': {len(files)} files")
|
|
|
|
branch_extracted = 0
|
|
for filepath in files:
|
|
classification = classify_file(filepath)
|
|
if classification is None:
|
|
continue
|
|
|
|
manufacturer, console = classification
|
|
subpath = get_subpath(filepath, manufacturer, console)
|
|
dest_dir = target / manufacturer / console
|
|
dest = dest_dir / subpath
|
|
|
|
try:
|
|
blob = subprocess.run(
|
|
["git", "show", f"{ref}:{filepath}"],
|
|
capture_output=True,
|
|
check=True,
|
|
)
|
|
content = blob.stdout
|
|
except subprocess.CalledProcessError:
|
|
continue
|
|
|
|
file_hash = sha1_blob(content)
|
|
|
|
if file_hash in existing_hashes:
|
|
continue
|
|
|
|
if dest.exists():
|
|
existing_hash = compute_hashes(dest)["sha1"]
|
|
if existing_hash == file_hash:
|
|
existing_hashes.add(file_hash)
|
|
continue
|
|
variant_dir = dest_dir / ".variants"
|
|
variant_name = f"{dest.name}.{file_hash[:8]}"
|
|
dest = variant_dir / variant_name
|
|
|
|
if dest.exists():
|
|
continue
|
|
|
|
if dry_run:
|
|
print(f" VARIANT: {filepath} -> {dest.relative_to(target)}")
|
|
else:
|
|
variant_dir.mkdir(parents=True, exist_ok=True)
|
|
with open(dest, "wb") as f:
|
|
f.write(content)
|
|
print(f" VARIANT: {filepath} -> {dest.relative_to(target)}")
|
|
existing_hashes.add(file_hash)
|
|
branch_extracted += 1
|
|
continue
|
|
|
|
if dry_run:
|
|
print(f" NEW: {filepath} -> {dest.relative_to(target)}")
|
|
else:
|
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
with open(dest, "wb") as f:
|
|
f.write(content)
|
|
print(f" NEW: {filepath} -> {dest.relative_to(target)}")
|
|
|
|
existing_hashes.add(file_hash)
|
|
branch_extracted += 1
|
|
|
|
print(f" -> {branch_extracted} new files from {branch}")
|
|
extracted += branch_extracted
|
|
|
|
return extracted
|
|
|
|
|
|
def migrate_local(source: Path, target: Path, dry_run: bool) -> tuple:
|
|
"""Migrate files from local flat structure to Manufacturer/Console hierarchy."""
|
|
moved = 0
|
|
skipped = 0
|
|
errors = []
|
|
existing_hashes = set()
|
|
|
|
for old_dir_name, (manufacturer, console) in sorted(SYSTEM_MAP.items()):
|
|
old_path = source / old_dir_name
|
|
if not old_path.is_dir():
|
|
continue
|
|
|
|
new_path = target / manufacturer / console
|
|
files = [f for f in old_path.iterdir() if f.is_file()]
|
|
|
|
if not files:
|
|
continue
|
|
|
|
print(f" {old_dir_name}/ -> bios/{manufacturer}/{console}/")
|
|
|
|
if not dry_run:
|
|
new_path.mkdir(parents=True, exist_ok=True)
|
|
|
|
for f in files:
|
|
dest = new_path / f.name
|
|
if dest.exists():
|
|
print(f" SKIP (exists): {f.name}")
|
|
skipped += 1
|
|
continue
|
|
|
|
if dry_run:
|
|
print(f" COPY: {f.name}")
|
|
else:
|
|
try:
|
|
shutil.copy2(str(f), str(dest))
|
|
except OSError as e:
|
|
errors.append((f, str(e)))
|
|
print(f" ERROR: {f.name}: {e}")
|
|
continue
|
|
|
|
file_hash = compute_hashes(f)["sha1"]
|
|
existing_hashes.add(file_hash)
|
|
moved += 1
|
|
|
|
return moved, skipped, errors, existing_hashes
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(
|
|
allow_abbrev=False,
|
|
description="Migrate BIOS files to Manufacturer/Console structure"
|
|
)
|
|
parser.add_argument(
|
|
"--dry-run",
|
|
action="store_true",
|
|
help="Show what would be done without moving files",
|
|
)
|
|
parser.add_argument("--source", default=".", help="Source directory (repo root)")
|
|
parser.add_argument(
|
|
"--target", default="bios", help="Target directory for organized BIOS files"
|
|
)
|
|
parser.add_argument(
|
|
"--include-branches",
|
|
action="store_true",
|
|
help="Also extract BIOS files from all remote branches",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
source = Path(args.source)
|
|
target = Path(args.target)
|
|
|
|
if not source.is_dir():
|
|
print(f"Error: Source directory '{source}' not found", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
print(f"Migrating from {source}/ to {target}/Manufacturer/Console/")
|
|
if args.dry_run:
|
|
print("(DRY RUN - no files will be moved)\n")
|
|
else:
|
|
print()
|
|
|
|
print("=== Phase 1: Local files (libretro branch) ===")
|
|
moved, skipped, errors, existing_hashes = migrate_local(
|
|
source, target, args.dry_run
|
|
)
|
|
action = "Would copy" if args.dry_run else "Copied"
|
|
print(f"\n{action} {moved} files, skipped {skipped}")
|
|
|
|
if args.include_branches:
|
|
print("\n=== Phase 2: Extracting from other branches ===")
|
|
branch_count = extract_from_branches(target, args.dry_run, existing_hashes)
|
|
print(f"\n{action} {branch_count} additional files from branches")
|
|
moved += branch_count
|
|
|
|
if source.is_dir():
|
|
known = set(SYSTEM_MAP.keys()) | {
|
|
"bios",
|
|
"scripts",
|
|
"platforms",
|
|
"schemas",
|
|
".github",
|
|
".cache",
|
|
".git",
|
|
"README.md",
|
|
".gitignore",
|
|
}
|
|
for d in sorted(source.iterdir()):
|
|
if d.name not in known and not d.name.startswith("."):
|
|
if d.is_dir():
|
|
print(f"\nWARNING: Unmapped directory: {d.name}")
|
|
|
|
print(f"\nTotal: {moved} files migrated, {len(existing_hashes)} unique hashes")
|
|
|
|
if errors:
|
|
print(f"Errors: {len(errors)}")
|
|
for f, e in errors:
|
|
print(f" {f}: {e}")
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
from common import run_cli
|
|
|
|
run_cli(main)
|