A comprehensive TypeScript toolbox for managing EmulationStation
gamelist.xmlromsets with CLI and programmatic API support.
- Features
- Installation
- Quick Start
- RomSet Structure
- Configuration
- CLI Actions
- Advanced Usage
- API Usage
- Development
- License
- 🎮 Comprehensive RomSet Management - Copy, filter, transform, and optimize EmulationStation romsets
- 🖼️ Media Handling - Manage images, videos, thumbnails, and marquees with resize and conversion support
- 🔄 Format Conversion - Convert between EmulationStation, RetroArch, SimpleMenu, muOS, and Onion formats
- 🎯 Advanced Filtering - Include/exclude games by metadata, ID, or system with JSON filter files
- 🚀 Performance Optimized - Minimal data transfer overhead, lazy-loading, and efficient file operations
- 📦 Multi-System Support - Batch operations across multiple romset directories
- 🔧 TypeScript API - Full programmatic access with type safety
- ✅ Well Tested - Comprehensive test coverage with Vitest
Install globally for command-line usage:
npm install -g gamelist-utilsRun directly without installing:
npx gamelist-utils [action] [options]Install as a project dependency for programmatic usage:
npm install gamelist-utilsRequirements: Node.js ≥ 22.0.0
# Backup your gamelist.xml
gamelist backup
# Copy a romset with filtering
gamelist copy --dest /path/to/destination --filter ./filters/top35.json
# Create a collection file
gamelist collection --name favorites --filter ./my-filter.json
# Optimize for low-end systems (remove videos and descriptions)
gamelist video --remove
gamelist simplify
# Handle multiple systems at once
gamelist backup --multi=gb,gbc,gba,nes,snesThis tool expects a specific romset directory structure, typically generated by Skraper:
system/
├── gamelist.xml
├── media/
│ ├── box2d/
│ ├── box3d/
│ ├── manual/
│ ├── mixed/
│ ├── screenshot/
│ ├── snap/
│ ├── title/
│ └── wheel/
└── [rom files - .zip, .bin, .iso, etc.]
Key Points:
- Media files are organized by type in subdirectories under
./media - Each media file is named identically to its corresponding ROM (without file extension)
- Custom Skraper mixes are available in the
./skraperdirectory
Customize media directory names using environment variables or a .env file:
| Variable | Default | Description |
|---|---|---|
GAMELIST_MEDIA |
media |
Base media directory |
GAMELIST_BOX2D |
box2d |
2D box art subdirectory |
GAMELIST_BOX3D |
box3d |
3D box art subdirectory |
GAMELIST_MIXED |
mixed |
Mixed image subdirectory |
GAMELIST_SCREENSHOT |
screenshot |
Screenshot subdirectory |
GAMELIST_SNAP |
snap |
Video snap subdirectory |
GAMELIST_TITLE |
title |
Title screen subdirectory |
GAMELIST_MARQUEE |
wheel |
Marquee/wheel subdirectory |
GAMELIST_MANUAL |
manual |
Manual/PDF subdirectory |
See the included .env example for reference.
All actions support --help for detailed usage information.
| Action | Description |
|---|---|
backup |
Creates a backup copy of gamelist.xml as gamelist.xml.bak |
copy |
Copies romset to destination with optional transformations (optimized for slow media) |
duplicates |
Scans for duplicate game IDs and potential name duplicates |
collection |
Creates EmulationStation collection .cfg files with filter support |
playlists |
Generates .m3u playlists for multi-disc games |
| Action | Description |
|---|---|
image-type |
Updates game entries to use alternate image types with fallback support |
image-resize |
Resizes images to desired dimensions with various object-fit strategies |
thumbnail |
Adds/removes thumbnail entries and optionally generates thumbnail files |
marquee |
Adds/removes marquee image entries and files |
video |
Adds/removes video snap entries and files |
| Action | Description |
|---|---|
simplify |
Removes descriptions and unused media to reduce file size and load time |
simplemenu |
Converts romset to SimpleMenu format (OpenDingux) |
retroarch |
Symlinks media files for RetroArch playlists |
es-de |
Symlinks gamelist and media to EmulationStation-DE locations |
muos |
Converts romset for muOS compatibility |
onion |
Converts romset for Miyoo/Onion compatibility |
| Action | Description |
|---|---|
lock |
Sets gamelist.xml as read-only (Windows only) |
unlock |
Removes read-only attribute from gamelist.xml (Windows only) |
Use the -m / --multi option to run actions across multiple subdirectories:
# Process all subdirectories
gamelist lock --multi
# Process specific systems
gamelist backup --multi=gb,gbc,gba,nes,snes
# Copy multiple systems with filtering
gamelist copy --dest /backup --multi --filter ./filters/top35.jsonFilter files use JSON format with include and exclude arrays. Supports exact matches or Contains suffix for partial matching.
{
"include": [
{ "name": "Duck Hunt" },
{ "nameContains": "Mario" }
]
}{
"include": [
{ "id": "14526", "system": "NEC PC Engine" },
{ "id": "59382", "system": "NEC PC Engine SuperGrafx" },
{ "id": "14543", "system": "NEC PC Engine" },
{ "id": "103574", "system": "NEC PC Engine CD-Rom" },
{ "id": "59796", "system": "NEC PC Engine" }
]
}Filter Properties:
id- ScreenScraper game ID from the<game>tagsystem- System name from the<provider>tag- Any game metadata field (e.g.,
name,genre,developer,publisher) - Add
Containssuffix for partial matching (e.g.,nameContains,genreContains)
Pre-made Filters: Top 35 game filters based on Wikipedia sales statistics are available in the ./filters directory.
All CLI actions are available as programmatic APIs with full TypeScript support:
import { simplify, copy, collection } from 'gamelist-utils';
// Simplify a romset
await simplify.api(process.cwd(), { quiet: true });
// Copy with filtering
await copy.api('/source/path', {
dest: '/destination/path',
filter: './filters/top35.json',
quiet: true
});
// Create a collection
await collection.api('/romset/path', {
name: 'favorites',
filter: './my-favorites.json'
});API Features:
- Lazy-loaded modules for minimal overhead
- Promise-based async/await support
- All CLI options available programmatically
- Use
quiet: trueto suppress console output
This project uses TypeScript with strict settings, Vitest for testing, and ESLint + Prettier for code quality.
npm run build # Compile TypeScript to dist/
npm run lint # Lint with ESLint + Prettier
npm run fix # Auto-fix linting issues
npm run typecheck # Type-check without emitting
npm test # Run Vitest test suite
npm run test:coverage # Run tests with coverage report
npm run check # Run all quality gates (lint + typecheck + test)- Runtime: Node.js ≥ 22 (ESM)
- Language: TypeScript 5.x with strict settings
- Testing: Vitest with V8 coverage
- Linting: ESLint 9 (flat config) + Prettier
- CI: GitHub Actions
Contributions are welcome! Please ensure all tests pass and code meets quality standards:
npm run checkLicensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.