Skip to content

psychonic/Playnite-EmuLibrary

Repository files navigation

EmuLibrary

EmuLibrary is a library extension for Playnite, an open source video game library manager, focused on emulator ROM management.

While Playnite has had built-in support for scanning paths for installed ROMs and adding them to the library since version 9, EmuLibrary provides alternate functionality.

EmuLibrary treats one or more folders of ROMs/Disc images as a library from which you can "install" games. It can be useful if you have a large collection of emulated games and limited storage where you play them versus where you store them (HTPC vs. NAS, for example). It also is useful for keeping the list of emulated games up to date, and for being able to filter via installed/uninstalled.

Disclaimer: I created this extension for my own usage, and that is still the primary focus. Because of this, many parts of it are still tailored to my personal needs and usage patterns. Despite that, I wanted to share it with others in case it is useful to them. It is still in the process of being (slowly) adapted for more general use.

Setup

To set it up, you create mappings to combine one of each of the following:

  • Emulator - either a built-in emulator or a custom emulator manually added
  • Emulator Profile - either a built-in emulator profile or a custom one, out of those supported by the chosen emulator
  • Platform - the ROM platform/console, out of those that the emulator profile supports
  • RomType - See Rom Types below

Paths

For source and destination, only valid Windows file paths are currently supported. The intended use case is for having the source be an SMB file share (either via UNC path or mapped drive), and the destination be a local path. However, any valid file path should work for either. This means that you can get creative with the source if you have a way to mount alternate remote storage at a Windows file path.

Additionally, for destination paths, relativity to the Playnite folder is preserved if you are using a portable installation of Playnite and your destination is below that folder hierarchically. This means that, for example, if your portable installation is at D:\playnite, and you choose D:\playnite\rominstall as your destination, it will be saved internally as {PlayniteDir}\rominstall.

Rom Types

SingleFile

SingleFile is the simplest type of ROM supported. This is for source folders in which each ROM is fully contained in a single file. It's commonly used for older, non-disc-based systems where the whole ROM consists of a single file. (Ex. .nes, .sfc, .md, etc.). Archive formats are supported as well if the emulator supports them directly. (Ex. .zip)

MultiFile

With the MultiFile type, each subfolder directly within the source folder is scanned as a potential "ROM". This is for games that have multiple loose files. (Ex. one or more .bin/.cue, with optional .m3u). When installing a MultiFile game, the whole folder is copied.

To determine which file is used as the one to tell the emulator to load, all files matching the configured emulator profile's supported extensions are considered. Precedence is by configured image extension list order, and then by alphabetical order. For example, if file names are the same except for (Disc 1) versus (Disc 2), the first disc takes precedence. Similarly, if you have .cue in the extension list before .m3u (as some of the built-in profiles have at the time of writing), .cue would be chosen over .m3u, which may not be desired for multi-disc games.

Yuzu

The Yuzu type supports Nintendo Switch emulators that share Yuzu's architecture: Yuzu, Eden, and Citron-Neo. Select which emulator to use via the dropdown in the mapping settings — this controls where the plugin looks for keys and the NAND directory.

To add a functional mapping, select the appropriate emulator in the dropdown, then set the Playnite emulator entry to match (it does not need to be a built-in listing; custom entries work fine). In the source path, loose XCI/NSP/XCZ/NSZ files in the root of the path are considered.

Unlike the SingleFile/MultiFile types, this type scans by its own fixed list of formats (XCI/NSP/XCZ/NSZ) rather than the emulator profile's configured image extensions, so the profile does not need any image extensions defined.

NSP/NSZ files can also be updates and DLC, rather than just games. Unlike with Tinfoil shares, files are not required to include the title id in the filename. Additionally, while destination path must point to a folder that exists, the setting is ignored. Games install into the NAND directory configured in the selected emulator profile.

When a game is installed, the latest update and any DLC from the source will also be installed to the emulator's NAND, in that order (Game, Update if available, each available DLC). Games already installed will be imported, whether or not they exist in the source folder, and will display as installed. As expected, uninstalling a game will remove the game from the NAND. (While these emulators do not support XCZ or NSZ files for launching or installing to NAND, this plugin installs directly to the NAND, without relying on the emulator's built-in install functionality)

Game names and other details are enriched from an online database where possible — see Metadata Enrichment.

PS3 (Beta)

The PS3 type adds support for PlayStation 3 games run under RPCS3. It has a beta level of quality of support. Select RPCS3 as the emulator for the mapping (built-in or custom).

Like the Yuzu type, this type scans by its own format logic (.iso/.pkg/.rap) rather than the emulator profile's configured image extensions, so the profile does not need any image extensions defined — RPCS3's built-in profile declares none, and that is fine.

Unlike the other types, a PS3 "game" is a composite: a base (either a disc image or a downloadable PKG game) plus any number of update PKGs, DLC PKGs, and RAP license files. The scanner treats each immediate subfolder of the source path as one title, and the recommended layout groups a title's content together:

<SourcePath>/
  BLES01234 - Demon's Souls/
    BLES01234 - Demon's Souls.iso        # encrypted disc image
    BLES01234 - Demon's Souls.dkey       # disc key (matching basename — required by RPCS3)
    updates/  <patch>.pkg ...            # installed in APP_VER order (from each PKG, never the filename)
    dlc/      <dlc>.pkg ...
    licenses/ EP0001-BLES01234_00-DLC0001.rap   # filename is the content-id
  NPUB30001 - Some PSN Game/
    NPUB30001 - Some PSN Game.pkg        # PKG base game (no disc image)
    updates/ ...
    dlc/ ...
    licenses/ ...

The updates/, dlc/, and licenses/ subfolders are optional and case-insensitive. The scanner is tolerant of looser layouts too — PKGs are classified by their own metadata (the PKG patch flag plus PARAM.SFO fields), so a PKG dropped anywhere under the title folder is still categorized correctly; the subfolder name is used as an additional hint.

Installing a title can touch two destinations:

  • Disc bases (an encrypted .iso plus its matching .dkey, or a decrypted .iso/PS3_GAME folder) are copied to the mapping's destination path. The .dkey sidecar is copied alongside the image so RPCS3 can load the encrypted ISO directly — there is no decrypt-on-install step.
  • PKG content (a PKG base game, updates, and DLC) is decrypted and extracted natively into RPCS3's dev_hdd0/game/<TITLE_ID> (under the selected emulator's install directory). Updates are applied in ascending APP_VER order. RAP licenses are copied into dev_hdd0/home/00000001/exdata.

Both halves are handled in-process — RPCS3 is not invoked to install anything. Uninstalling removes the copied disc image/folder and/or the dev_hdd0/game/<TITLE_ID> directory; save data (which RPCS3 stores elsewhere) is left untouched.

Game names and other details are enriched from an online database where possible — see Metadata Enrichment.

Wii U (Beta)

The Wii U type adds support for Nintendo Wii U games run under Cemu. It has a beta level of quality of support. Select Cemu as the emulator for the mapping (built-in or custom).

Like the Yuzu and PS3 types, this type scans by its own format logic rather than the emulator profile's configured image extensions, so the profile does not need any image extensions defined. It recognizes every common Wii U source format found directly under the source path:

  • NUS/WUP dumps — a folder containing title.tmd, the encrypted .app contents, and a .tik ticket.
  • Decrypted "loadiine" folders — a folder with code/, content/, and meta/ subfolders.
  • Disc images.wux (or .wud) with a sibling disc key named <image>.key (or game.key).
  • Pre-made .wua archives — Cemu's single-file compressed format.

Decryption is done natively in-process (it does not shell out to other tools); the Wii U common key is read from keys.txt in the Cemu folder, and disc images additionally need their per-disc .key. The plugin never installs into Cemu's NAND — Cemu launches games directly from the installed file/folder.

Like PS3, a Wii U "game" is a composite: a base title plus its updates and DLC. Wii U updates are cumulative, so only the latest update is applied (along with any DLC); there are no separate license files. The install model is WUA-first:

  • If the source is already a self-contained, decrypted bundle — a .wua, a standalone disc image (.wux/.wud + .key), or a loadiine folder with no separate update/DLC — it is copied as-is to the mapping's destination path.
  • Otherwise (a NUS dump, or a base with separate update/DLC that must be merged), the content is decrypted and converted into a single .wua at the destination, with the latest update and DLC merged in. Merging separate updates/DLC into a pre-existing .wua base is not supported.

Each title installs into its own destination subfolder named <name> [<title id>], so installed state and identity are recovered from disk on each scan. Uninstalling removes that subfolder.

Game names come from each title's decrypted meta.xml, and other details are enriched from an online database where possible — see Metadata Enrichment.

3DS (Beta)

The 3DS type adds support for Nintendo 3DS games run under Azahar (a Citra fork). It has a beta level of quality of support. Select Azahar as the emulator for the mapping (built-in or custom).

Like the other console types, this type scans by its own format logic rather than the emulator profile's configured image extensions, so the profile does not need any image extensions defined. It recognizes three source shapes found directly under the source path:

  • Loose bootable images.3ds, .cci, .cxi, .3dsx, and their compressed Z3DS counterparts .zcci, .zcxi, .z3dsx. These must already be decrypted dumps (Azahar rejects encrypted images).
  • CIA / Z3DS CIA.cia and .zcia files. Each is one title; base/update/DLC .cias are grouped by their shared base title id.
  • CDN folders — a folder containing a tmd.<version> plus content files named by hex content id (the layout produced by CDN downloads). Base, update, and DLC folders are grouped by the base title id read from the tmd, not by the folder-name suffix.

Like PS3 and Wii U, a 3DS "game" is a composite: a base title plus its updates and DLC. 3DS updates are cumulative, so only the latest update is applied (along with any DLC); there are no separate license files. Installing differs by source shape:

  • Loose images are copied as-is to the mapping's destination path (the Z3DS variants are copied verbatim — Azahar reads them natively, so there is no decompression on the copy path). This path is keyless.
  • CIA / Z3DS CIA / CDN content is decrypted in-process and written into Azahar's SD title store (derived from the selected emulator's data folder), the same layout Azahar's own "Install CIA" produces. Updates and DLC are written into their own title directories, where Azahar applies them automatically when the base game boots. Azahar is never invoked to install anything.

Decryption needs the universal retail 3DS AES keys. These are read from your own Azahar — primarily its built-in key blob, with an aes_keys.txt (in Azahar's sysdata folder, beside the source, or a 3DS Keys/ folder on the source) as a fallback — so the plugin embeds no keys and requires nothing you don't already need to run Azahar. If no keys can be found, content-store install (CIA/CDN) is refused with a clear message; loose-file copy is unaffected. A CDN folder additionally needs a ticket (.tik) for its title key — <folder>.tik beside the folder, or any .tik/cetk inside it — and refuses install with a message if none is present (CIAs carry their own ticket).

Installed state is derived from Azahar's SD store (for content-store titles) or the destination folder (for loose copies) on each scan. Uninstalling removes the installed SD title directories or the copied file.

Note: this targets the default Azahar that is not linked to a real console. If you have linked a console (Azahar wraps installed content in console-unique encryption in that case), use Azahar's own GUI installer instead.

Game icons come from each title's embedded SMDH where readable, and other details are enriched from an online database where possible — see Metadata Enrichment. By default the game name is the normalized filename (overridden by the 3dsdb full name when a title-id match exists), because the SMDH's embedded title is often shortened for the handheld's home menu; enable Prefer in-file (SMDH) title over filename on the mapping to use the embedded title instead.

Xbox 360 (Beta)

The Xbox 360 type adds support for Xbox 360 games run under Xenia (Canary or master — the content layout is the same across forks). It has a beta level of quality of support. Select Xenia as the emulator for the mapping (built-in or custom).

Like the other console types, this type scans by its own format logic rather than the emulator profile's configured image extensions, so the profile does not need any image extensions defined. It is entirely keyless — there is no decryption or extraction step; every install is a plain file copy. It recognizes these source shapes found directly under the source path:

  • Disc images.iso (XGD / XDVDFS). Redump .7z dumps are presumed already extracted to .iso.
  • Digital packages — single-file STFS containers (LIVE) for XBLA, Games on Demand, and standalone apps. Zipped digital dumps are presumed already unzipped (the internal <TitleID>/<ContentType>/<ContentID> folder layout is what is scanned).
  • Title updatesTU_* STFS packages (these shares are foldered by media id).
  • DLC / add-ons — marketplace STFS packages.

Like PS3 and Wii U, an Xbox 360 "game" is a composite: a base (a disc image or a digital package) plus its updates and DLC. Updates are cumulative, so only the latest update is applied (along with any DLC); there are no separate license files.

The plugin reads each base's title id and media id to group content — from the disc's default.xex for ISOs, or from the STFS header for digital packages. Title updates are matched to a base by media id (a disc release and a Games-on-Demand release of the same game share a title id but differ by media id, and an update only applies to the matching one); DLC is matched by title id. STFS content type alone does not separate a standalone app from a DLC add-on (both can sit under 00000002), so the plugin probes whether the package contains a default.xex to tell them apart.

Installing differs by piece:

  • The base (the .iso or the digital container) is copied to the mapping's destination path and launched directly — Xenia opens it in place.
  • Updates and DLC are copied into Xenia's content folder, resolved from the selected emulator's install: content/ beside xenia.exe if a portable.txt is present there, otherwise %USERPROFILE%\Documents\Xenia\content. Xenia applies them automatically at boot by title id + media id. If the content folder can't be resolved, base install still works but update/DLC install is refused with a clear message.

Installed state is derived from disk on each scan (the base copy in the destination plus each update/DLC file in the content tree). Uninstalling removes the copied base and the specific update/DLC files that were written — it does not delete the whole title content directory, so save data (stored under the same title id) is left untouched.

Note: each disc of a multi-disc set is treated as its own game. Some demo/promo discs are plain DVD images with no Xbox game partition; these can't be identified and are skipped with a warning.

Game names and thumbnails come from each digital package's embedded STFS metadata where available; disc titles fall back to the title-id database or the No-Intro filename. See Metadata Enrichment.

PSP (Beta)

The PSP type adds support for PlayStation Portable games run under PPSSPP — either the standalone emulator or the PPSSPP libretro core in RetroArch. It has a beta level of quality of support. Select PPSSPP (or RetroArch) as the emulator for the mapping (built-in or custom).

Like the other console types, this type scans by its own format logic rather than the emulator profile's configured image extensions, so the profile does not need any image extensions defined. The source is a flat directory of disc images, freely named, in any of these formats:

  • .iso — a raw UMD image (ISO9660).
  • .chd — MAME/chdman compressed image (v5; zstd/zlib/lzma hunks). It is read directly — during a scan only the few sectors needed to identify the title are decompressed, not the whole image.
  • .cso / .zso — CISO/ZISO block-compressed images.
  • .pbp — an EBOOT.PBP (PSN / minis / homebrew).

Beside the images sit optional update/DLC payload folders, each named by the bare 9-character title id (e.g. ULUS10538). The scanner reads the DISC_ID out of each image (cached) and pairs the sibling folder of that name as the title's update/DLC content — association is by the id inside the image, not by filename, so nothing needs renaming.

Installing a title touches two destinations:

  • The base image is copied as-is to the mapping's destination path (compressed images are copied verbatim — PPSSPP opens .chd/.cso/.pbp natively, so there is no decompression on install).
  • Each update/DLC payload folder is copied verbatim into the emulator's memory stick under PSP/GAME/<title id>, where PPSSPP applies it at launch. Multiple updates are applied in ascending APP_VER order (read from each payload's PBOOT.PBP, never the folder name). There are no separate license files — PSP DRM (EDAT) is keyed to the console and is PPSSPP's job; the plugin only copies.

The memory-stick root is resolved from the selected emulator profile: for standalone PPSSPP a portable memstick/ beside the exe, else memstick_dir.txt, else Documents/PPSSPP; for the RetroArch core it is <system_directory>/PPSSPP. A base-only title installs regardless, but if a title has update/DLC content and the memory stick can't be resolved, that install is refused with a clear message.

Installed state is derived from disk on each scan (the base copy in the destination plus each payload folder under the memory stick). Uninstalling removes the copied base image and the PSP/GAME/<title id> payload directories it wrote; save data (PSP/SAVEDATA) is left untouched.

Game icons come from each image's embedded ICON0.PNG — read from /PSP_GAME/ICON0.PNG on a UMD image (.iso/.chd/.cso/.zso) or from the icon segment of a .pbp. Game names come from the image's PARAM.SFO title, falling back to the normalized filename. There is no external metadata provider for PSP.

Note: multi-disc PSP games are out of scope.

Known Issues (all types)

  • If the connection to the source folder's storage is unstable, Playnite may crash when when updating the library. This is unlikely to be able to be completely fixed until Playnite uses a newer .NET version (currently being targeted for Playnite 11). Some some mitigations are planned in the meantime, but are not yet implemented.
  • If the mapping is disabled or if EmuLibrary update is cancelled before the scan for the mapping completes, game installation for the mapping's games may result in an error message. This will be fixed in a later version of this addon.

Metadata Enrichment

For some RomTypes, EmuLibrary looks up game details from a community metadata database and uses them in preference to whatever it can read out of the game files themselves. This generally yields cleaner, properly-formatted game names plus extra details that aren't present in the files at all.

RomType Provider Matched by
Yuzu (Switch) titledb Title ID
PS3 GameTDB Disc serial (e.g. BLES01234)
Wii U GameTDB 6-char game code (e.g. AVEE0W), built from meta.xml
3DS 3dsdb Title ID
Xbox 360 x360db Title ID

When a match is found, these fields are filled in (shadowing anything derived from the files): name, description, developer, publisher, genres, and release date. Anything the provider doesn't supply falls back to what was read from the files, so you never end up worse off than without enrichment.

A few notes:

  • Language follows your Playnite language setting where the provider offers a translation, falling back to English otherwise.
  • Caching / offline use — each provider's database is downloaded once and cached under ExtensionsData\41e49490-0583-4148-94d2-940c7c74f1d9\metadata, and only re-downloaded when the cached copy is more than a day old. If a provider can't be reached, the most recent cached copy is used, and if there's no cached copy at all, enrichment is simply skipped — it never blocks or fails a library scan.
  • Applies going forward — because of how Playnite imports library games, enrichment primarily affects games as they are newly added. Re-applying it to games already in your library is not currently guaranteed.
  • GameTDB also lists Switch games, but it keys them by a cartridge serial that loose NSP/XCI dumps don't contain, so it can't be used for the Yuzu type — titledb is used there instead.
  • For Wii U, the game name always comes from the title's own meta.xml (Wii U files embed it), and GameTDB is only used for the extra fields. GameTDB's Wii U coverage is thin for eShop-only/indie titles, so many Wii U games are enriched by name alone.
  • For 3DS, the game icon is read from the title's embedded SMDH when available (3DS files carry it). The game name comes from the normalized filename by default, overridden by the 3dsdb name when a title-id match exists; the embedded SMDH title (often shortened for the handheld's home menu) is used for the name only when Prefer in-file (SMDH) title over filename is enabled on the mapping. 3dsdb also supplies publisher and region. The SMDH is read for both installed and uninstalled titles.
  • For Xbox 360, digital packages carry an embedded display name and thumbnail in their STFS header, which take precedence; x360db supplies the name (and publisher) when there's no STFS metadata — notably for disc images, which have no STFS header and otherwise fall back to the filename.

Support

To get help, check out the #extension-support channel on the Playnite Discord, linked at the top of https://playnite.link/

The following files are generally useful for troubleshooting, relative to the folder where Playnite data is stored. For a portable installation, this is the same folder that Playnite is installed to. For non-portable installations, it is in AppData.

  • playnite.log
  • extensions.log
  • library\emulators.db
  • library\platforms.db
  • ExtensionsData\41e49490-0583-4148-94d2-940c7c74f1d9\config.json

About

No description, website, or topics provided.

Resources

License

Stars

31 stars

Watchers

1 watching

Forks

Packages

 
 
 

Contributors

Languages