Files
ample/AmpleLinux/rom_manager.py
T
anomixer 4224a942ef feat(AmpleWin/AmpleLinux): update MAME downloader & rom_manager to v0.289 (#56)
* Fix slot persistence logic (AmpleWin/Linux) for upstream PR preparation. Implements robust None-aware slot initialization.

* Fix: Explicitly pass empty string arguments for disabled slots (None) to prevent MAME using defaults

* feat(core): update MAME to 0.288, fix compatibility bugs, add PowerBook fallbacks, and optimize download servers

* feat(AmpleWin/AmpleLinux): update MAME to 0.289, remove PowerBook EASC fallbacks, prioritize CallApple server with dragon32 auto-patching and Laser series ROMs
2026-08-07 21:44:51 -04:00

152 lines
5.9 KiB
Python

import os
import requests
import plistlib
from PySide6.QtCore import QObject, Signal, QRunnable, QThreadPool
class DownloadSignals(QObject):
progress = Signal(int, int) # current, total
finished = Signal(str, bool) # value, success
status = Signal(str)
class DownloadWorker(QRunnable):
def __init__(self, urls, dest_path, value):
super().__init__()
self.urls = urls if isinstance(urls, list) else [urls]
self.dest_path = dest_path
self.value = value
self.signals = DownloadSignals()
self._is_cancelled = False
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
def cancel(self):
self._is_cancelled = True
def run(self):
last_error = "No URLs provided"
for url in self.urls:
try:
if self._is_cancelled: return
# For small files (ROMs), direct download is much faster than streaming
response = requests.get(url, headers=self.headers, timeout=20)
response.raise_for_status()
os.makedirs(os.path.dirname(self.dest_path), exist_ok=True)
if self._is_cancelled: return
with open(self.dest_path, 'wb') as f:
f.write(response.content)
# Special patch for dragon32 to merge MDK split files if missing
if self.value == 'dragon32':
self.patch_dragon32()
self.signals.finished.emit(self.value, True)
return # Success!
except Exception as e:
last_error = str(e)
continue # Try next URL
# If we get here, all URLs failed
if os.path.exists(self.dest_path):
try: os.remove(self.dest_path)
except: pass
self.signals.status.emit(f"Error: {last_error}")
self.signals.finished.emit(self.value, False)
def patch_dragon32(self):
try:
mdk_url = "https://mdk.cab/download/split/dragon32.zip"
resp = requests.get(mdk_url, headers=self.headers, timeout=20)
if resp.status_code == 200 and len(resp.content) > 100:
import zipfile, io
existing_data = open(self.dest_path, 'rb').read()
z_existing = zipfile.ZipFile(io.BytesIO(existing_data))
z_mdk = zipfile.ZipFile(io.BytesIO(resp.content))
existing_names = set(z_existing.namelist())
mdk_names = set(z_mdk.namelist())
missing = mdk_names - existing_names
if missing:
out_buf = io.BytesIO()
with zipfile.ZipFile(out_buf, 'w', zipfile.ZIP_DEFLATED) as zout:
for item in z_existing.infolist():
zout.writestr(item, z_existing.read(item.filename))
for item in z_mdk.infolist():
if item.filename in missing:
zout.writestr(item, z_mdk.read(item.filename))
with open(self.dest_path, 'wb') as f:
f.write(out_buf.getvalue())
except Exception as e:
print(f"Warning: dragon32 patch failed: {e}")
class RomManager(QObject):
def __init__(self, resources_path, roms_dir):
super().__init__()
self.resources_path = resources_path
self.roms_dir = roms_dir
self.base_urls = [
"https://www.callapple.org/roms/",
"https://mdk.cab/download/split/"
]
self.rom_list = self.load_rom_list()
def load_rom_list(self):
path = os.path.join(self.resources_path, "roms.plist")
if not os.path.exists(path):
roms = []
else:
with open(path, 'rb') as f:
roms = plistlib.load(f)
# Compatibility fallback: Add back ROMs removed by upstream but still supported by mdk.cab
custom_roms = [
{'value': 'tk3000', 'description': 'TK3000 //e'},
{'value': 'prav8c', 'description': 'Pravetz 8C'},
{'value': 'prav82', 'description': 'Pravetz 82'},
{'value': 'prav8m', 'description': 'Pravetz 8M'},
{'value': 'prav8d', 'description': 'Pravetz 8D'},
{'value': 'las128ex', 'description': 'Laser 128EX'},
{'value': 'las128e2', 'description': 'Laser 128EX/2'},
{'value': 'laser128', 'description': 'Laser 128'},
{'value': 'laser128o', 'description': 'Laser 128 (Original)'},
{'value': 'laser2c', 'description': 'Laser 2c'}
]
existing_values = {r.get('value') for r in roms if 'value' in r}
for cr in custom_roms:
if cr['value'] not in existing_values:
roms.append(cr)
return roms
def get_rom_status(self):
status_list = []
for rom in self.rom_list:
value = rom['value']
# Check for zip, 7z or folder
found = False
for ext in ['zip', '7z']:
path = os.path.join(self.roms_dir, f"{value}.{ext}")
if os.path.exists(path):
found = True
break
if not found:
# Check for unzipped folder
path = os.path.join(self.roms_dir, value)
if os.path.isdir(path):
found = True
status_list.append({
'value': value,
'description': rom['description'],
'exists': found
})
return status_list
def get_download_url(self, value, ext='zip'):
return f"{self.base_url}{value}.{ext}"