Last active
April 27, 2025 02:39
-
-
Save ssokolow/3fd6744f6af428c477b0f1b11b35cdac to your computer and use it in GitHub Desktop.
Script to automate adding ScummVM games to EmulationStation Desktop Edition
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# -*- coding: utf-8 -*- | |
# | |
# prep_scummvm_games.py | |
# Copyright 2025 Stephan Sokolow | |
# | |
# Helper for batch-adding ScummVM games to ES-DE. | |
# | |
# License: MIT (https://opensource.org/license/mit) | |
# | |
# Usage: | |
# 0. If you set a non-default ROMs directory, edit ESDE_SCUMMVM_ROMS_PATH | |
# 1. Copy game folders of the form ScummVM expects into ESDE_SCUMMVM_ROMS_PATH | |
# 2. Run standalone ScummVM's "Mass Add..." on ESDE_SCUMMVM_ROMS_PATH to | |
# add the required metadata to scummvm.ini | |
# 3. Run this script to read the metadata out of scummvm.ini and fix up the | |
# folders so ES-DE can use them. | |
# | |
# Note: In order to avoid the need to manually prune away demos and spurious | |
# results that ScummVM's "Mass Add.." will find inside certain games, | |
# this will ignore game folders nested inside other game folders. | |
# However, GOG.com intentionally does that with some games, where the CD | |
# version is ../GameName/ and the floppy version is ../GameName/FDD/. | |
# | |
# If you want the GOG.com floppy versions included, just move them so | |
# they're directly in ESDE_SCUMMVM_ROMS_PATH before running this script. | |
# (i.e. Beside the CD versions instead of inside of them) | |
import io, os, sys # NOQA | |
from configparser import ConfigParser | |
from typing import Optional | |
# Default path for ES-DE folder for ScummVM games | |
ESDE_SCUMMVM_ROMS_PATH = os.path.join( | |
os.path.expanduser("~"), 'ROMs', 'scummvm') | |
def get_scummvm_ini_path() -> Optional[str]: | |
"""Find a valid path for a `scummvm.ini` file or return None | |
Raises: | |
- KeyError: Environment variable which should always be set is missing | |
- NotImplementedError: Not running under a supported OS | |
References used: | |
https://docs.scummvm.org/en/latest/advanced_topics/configuration_file.html | |
""" | |
if os.name == 'nt': # Windows | |
# NOTE: Intentionally not bothering to support the Win9x ScummVM port | |
path = os.path.join( | |
os.environ['APPDATA'], 'ScummVM', 'scummvm.ini') | |
elif sys.platform == 'darwin': # macOS | |
path = os.path.expanduser( | |
"~/Library/Preferences/ScummVM Preferences") | |
elif os.name == 'posix': # Linux/*BSD | |
for path in ( | |
os.path.expanduser( # Flatpak | |
"~/.var/app/org.scummvm.ScummVM/config/scummvm/scummvm.ini"), | |
os.path.expanduser( # Snap | |
"~/snap/scummvm/current/.config/scummvm/scummvm.ini"), | |
os.path.join( # Other | |
os.environ.get('XDG_CONFIG_HOME', | |
os.path.expanduser('~/.config')), | |
'scummvm/scummvm.ini') | |
): | |
if os.path.exists(path): | |
return path | |
else: | |
raise NotImplementedError("Unspported platform") | |
if os.path.exists(path): | |
return path | |
return None | |
def fixup_game(game: str, config: ConfigParser, esde_scummvm_dir: str) -> bool: | |
"""Rename game folder if needed and augment with metadata from scummvm.ini | |
Will update ``config`` to point to the new path. | |
Returns a boolean indicating whether ``config`` now has unsaved changes. | |
""" | |
changed = False | |
# Skip ScummVM's global configuration settings | |
if game == "scummvm": | |
return False | |
# Get and normalize the game's path for reliable comparison | |
path = os.path.realpath(config[game]['path']) | |
if not os.path.exists(path): | |
print("Path does not exist. Skipping:", path) | |
return False | |
# Exclude games not directly in the ES-DE ROMs dir for ScummVM | |
if not os.path.realpath(os.path.dirname(path)) == esde_scummvm_dir: | |
print("Not in ES-DE ROM dir. Skipping:", path) | |
return False | |
# If the folder doesn't end in .scummvm, rename it and fix up the ScummVM | |
# config file so the '.scummvm' file and its containing folder will | |
# collapse together in ES-DE | |
scummvm_ext = ".scummvm" | |
game_dir_name = os.path.basename(path) | |
if not game_dir_name.lower().endswith(scummvm_ext): | |
new_path = path + scummvm_ext | |
# Don't overwrite a pre-existing target path | |
if os.path.exists(path) and not os.path.exists(new_path): | |
print("Renaming %r -> %r" % (path, new_path)) | |
os.rename(path, new_path) | |
else: | |
print("WARNING: Not renaming %r -> %r" % (path, new_path)) | |
game_dir_name += scummvm_ext | |
path = new_path | |
# Copy changed path into the in-memory copy of scummvm.ini | |
config[game]['path'] = path | |
changed = True | |
# Delete any existing .scummvm files inside the game folder | |
for fname in os.listdir(path): | |
if fname.lower().endswith('.scummvm'): | |
fpath = os.path.join(path, fname) | |
print("Removing %r" % fpath) | |
os.remove(os.path.join(path, fname)) | |
# Create the new .scummvm file | |
fpath = os.path.join(path, game_dir_name) | |
print("Creating %r" % fpath) | |
with open(fpath, 'w') as fobj: | |
fobj.write(config[game]['gameid']) | |
return changed | |
def main() -> None: | |
# Normalize path to ES-DE ROMs folder for reliable comparison | |
esde_scummvm_dir = os.path.realpath(ESDE_SCUMMVM_ROMS_PATH) | |
if not os.path.isdir(esde_scummvm_dir): | |
print("Could not find %r or is not a directory. " | |
"Quitting." % esde_scummvm_dir) | |
sys.exit(1) | |
scummvm_ini_path = get_scummvm_ini_path() | |
if scummvm_ini_path and os.path.exists(scummvm_ini_path): | |
print("Found scummvm.ini:", scummvm_ini_path) | |
else: | |
print("Could not find %r. Quitting." % scummvm_ini_path) | |
sys.exit(1) | |
# Parse ScummVM's config file to extract scraped games | |
config = ConfigParser() | |
with io.open(scummvm_ini_path, mode='r', encoding="utf8") as fobj: | |
config.read_file(fobj) | |
# Do the actual fixups | |
changed = any(fixup_game(game, config, esde_scummvm_dir) | |
for game in config.sections()) | |
# Write any changed paths to on-disk copy of scummvm.ini | |
if changed: | |
with io.open(scummvm_ini_path + ".tmp", mode='w', encoding="utf8") as fobj: | |
config.write(fobj) | |
# Almost-atomic replace since Windows doesn't allow os.rename to | |
# overwrite existing files | |
os.remove(scummvm_ini_path) | |
os.rename(scummvm_ini_path + ".tmp", scummvm_ini_path) | |
# Only run main() if not being import-ed (eg. by a unit test suite) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment