<div align="center">
  <img src="static/logo-dark.svg" height="200px" width="auto" alt="igir logo">
</div>
<br>

<p align="center"><b>Pronounced "eager," Igir is a zero-setup ROM collection manager that sorts, filters, extracts or archives, patches, and reports on collections of any size on any OS.</b></p>

<p align="center">
  <a href="https://www.npmjs.com/package/igir"><img alt="npm: version" src="https://img.shields.io/npm/v/igir?color=%23cc3534&label=version&logo=npm&logoColor=white"></a>
  <a href="https://www.npmjs.com/package/igir"><img alt="npm: downloads" src="https://img.shields.io/npm/dt/igir?color=%23cc3534&logo=npm&logoColor=white"></a>
  <a href="https://github.com/emmercm/igir/releases"><img alt="GitHub: downloads" src="https://img.shields.io/github/downloads/emmercm/igir/total?color=%236e5494&logo=github&logoColor=white"></a>
  <a href="https://github.com/emmercm/igir"><img alt="GitHub: stars" src="https://img.shields.io/github/stars/emmercm/igir?style=flat&logo=github&logoColor=white&color=%236e5494"></a>
  <a href="https://github.com/emmercm/igir/discussions"><img alt="GitHub: discussions" src="https://img.shields.io/github/discussions/emmercm/igir?logo=github&logoColor=white"></a>
</p>
<p align="center">
  <a href="https://snyk.io/test/npm/igir"><img alt="Snyk: vulnerabilities" src="https://snyk.io/test/npm/igir/badge.svg"></a>
  <a href="https://codecov.io/gh/emmercm/igir"><img alt="codecov: coverage" src="https://img.shields.io/codecov/c/github/emmercm/igir?logo=codecov&logoColor=white"></a>
  <a href="https://qlty.sh/gh/emmercm/projects/igir"><img src="https://qlty.sh/gh/emmercm/projects/igir/maintainability.svg" alt="Maintainability" /></a>
  <a href="https://github.com/emmercm/igir/blob/main/LICENSE"><img alt="license" src="https://img.shields.io/github/license/emmercm/igir?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NDAgNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNS4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjQgRm9udGljb25zLCBJbmMuLS0% %3D%3D&color=blue"></a>
</p>

<p align="center"><i>See the <a href="https://igir.io/">project website</a> for complete documentation, installation & usage instructions, and examples!</i></p>

<br>

## What does Igir do?

A video of an example use case:

<a href="https://asciinema.org/a/xE6kMquCPFYtpvhsiQfS0eS7c" target="_blank"><img src="https://asciinema.org/a/xE6kMquCPFYtpvhsiQfS0eS7c.svg" alt="asciicast" style="width:100%" /></a>

With Igir you can manage a ROM collection of any size:

- 🔍 Scan for DATs, ROMs, and ROM patches—including those in archives (see [scanning](https://igir.io/input/file-scanning) & [archive docs](https://igir.io/input/reading-archives))
- 📂 Organize ROM files by console (see [DAT docs](https://igir.io/dats/overview))
- 🪄 Name ROM files consistently, including the right extension (see [DAT docs](https://igir.io/dats/overview))
- ✂️ Filter out duplicate ROMs, or ROMs in languages you don't understand (see [filtering docs](https://igir.io/roms/filtering-preferences))
- 🗜️ Extract or archive ROMs in mass (see [archive docs](https://igir.io/output/writing-archives))
- 🔢 Create playlists for multi-disc games in mass (see [playlist docs](https://igir.io/output/playlists))
- 🩹 Patch ROMs automatically in mass (see [scanning](https://igir.io/input/file-scanning) & [patching docs](https://igir.io/roms/patching))
- 🎩 Parse ROMs with headers and optionally remove them (see [header docs](https://igir.io/roms/headers))
- ↔️ Build & re-build (un-merge, split, or merge) MAME ROM sets (see [arcade docs](https://igir.io/usage/arcade))
- 🔮 Report on what ROMs are present or missing for each console and create fixdats for missing ROMs (see [reporting](https://igir.io/output/reporting) & [DAT docs](https://igir.io/dats/overview))

## How do I run Igir?

Either download the latest version for your OS from the [releases page](https://github.com/emmercm/igir/releases/latest), or if you have Node.js installed you can use [`npx`](https://docs.npmjs.com/cli/v9/commands/npx) to always run the latest version from the command line:

```shell
npx igir@latest [commands..] [options]
```

Here is the full help message which shows all available options and a number of common use case examples:

<!-- WARN: everything below is automatically updated! Update src/modules/argumentsParser.ts instead! -->
```help
$ igir --help

   @@@@@@   @@@@@@     @@@@@@    @@@@@@@@
 @@      @@  @@      @@      @@         @@
 @@      @@  @@      @@      @@         @@   ROM collection manager
   @@@@@@   @@         @@@@@@   @@@@@@@@@    https://igir.io/
          @@@     @@@@        @@@
     @@   @@        @@   @@   @@       @@    v5.4.0 (Node.js v26.4.0)
     @@   @@        @@   @@   @@       @@
     @@   @@@@@@@@@@@@   @@   @@       @@

Usage: igir [commands..] [options]

Commands (can specify multiple):
  igir copy      Copy ROM files from the input to output directory
  igir move      Move ROM files from the input to output directory
  igir link      Create links in the output directory to ROM files in
                 the input directory
  igir extract   Extract ROM files in archives when copying or moving
  igir zip       Create zip archives of ROMs when copying or moving
  igir playlist  Create playlist files for multi-disc games
  igir test      Test ROMs for accuracy after writing them to the output
                 directory
  igir dir2dat   Generate a DAT from all input files
  igir fixdat    Generate a fixdat of any missing games for every DAT
                 processed (requires --dat)
  igir clean     Recycle unknown files in the output directory
  igir report    Generate a CSV report on the known & unknown ROM files
                 found in the input directories (requires --dat)

ROM input options:
  -i, --input                    Path(s) to ROM files or archives
                                 (supports globbing)             [array]
  -I, --input-exclude            Path(s) to ROM files or archives to
                                 exclude from processing (supports
                                 globbing)                       [array]
      --input-checksum-quick     Only read checksums from archive
                                 headers, don't decompress to calculate
                                                               [boolean]
      --input-checksum-min       The minimum checksum level to calculate
                                 and use for matching
          [choices: "CRC32", "MD5", "SHA1", "SHA256"] [default: "CRC32"]
      --input-checksum-max       The maximum checksum level to calculate
                                 and use for matching
           [choices: "CRC32", "MD5", "SHA1", "SHA256"] [default: "SHA1"]
      --input-checksum-archives  Calculate checksums of archive files
                                 themselves, allowing them to match
                                 files in DATs
                  [choices: "never", "auto", "always"] [default: "auto"]

DAT input options:
  -d, --dat                            Path(s) to DAT files or archives
                                       (supports globbing)       [array]
      --dat-exclude                    Path(s) to DAT files or archives
                                       to exclude from processing
                                       (supports globbing)       [array]
      --dat-name-regex                 Regular expression of DAT names
                                       to process                [array]
      --dat-name-regex-exclude         Regular expression of DAT names
                                       to exclude from processing[array]
      --dat-description-regex          Regular expression of DAT
                                       descriptions to process   [array]
      --dat-description-regex-exclude  Regular expression of DAT
                                       descriptions to exclude from
                                       processing                [array]
      --dat-combine                    Combine every game from every
                                       found & filtered DAT into one DAT
                                                               [boolean]
      --dat-ignore-parent-clone        Ignore any parent/clone
                                       information found in DATs
                                                               [boolean]

Patch input options:
  -p, --patch          Path(s) to ROM patch files or archives (supports
                       globbing) (supported: .aps, .bps, .dps, .ebp,
                       .ips, .ips32, .ppf, .rup, .ups, .vcdiff, .xdelta)
                                                                 [array]
  -P, --patch-exclude  Path(s) to ROM patch files or archives to exclude
                       from processing (supports globbing)       [array]
      --patch-only     Only write patched ROMs to the output directory
                                                               [boolean]

ROM output path options (processed in order):
  -o, --output                 Path to the ROM output directory
                               (supports replaceable symbols, see below)
                                                                [string]
      --dir-mirror             Use the input subdirectory structure for
                               the output directory            [boolean]
      --dir-dat-mirror         Use the DAT subdirectory structure for
                               the output directory            [boolean]
  -D, --dir-dat-name           Use the DAT name as the output
                               subdirectory                    [boolean]
      --dir-dat-description    Use the DAT description as the output
                               subdirectory                    [boolean]
      --dir-letter             Group games in an output subdirectory by
                               the first --dir-letter-count letters in
                               their name                      [boolean]
      --dir-letter-count       How many game name letters to use for the
                               subdirectory name   [number] [default: 1]
      --dir-letter-limit       Limit the number of games in letter
                               subdirectories, splitting into multiple
                               subdirectories if necessary      [number]
      --dir-letter-group       Group letter subdirectories into ranges,
                               combining multiple letters together
                               (requires --dir-letter-limit)   [boolean]
      --dir-game-subdir        Append the name of the game as an output
                               subdirectory depending on how many ROMs a
                               game has
          [choices: "never", "multiple", "always"] [default: "multiple"]
      --output-console-tokens  Path to a JSON file of custom console
                               token definitions to use instead of the
                               built-in definitions             [string]

ROM writing options:
      --fix-extension      Read files for known signatures and use the
                           correct extension (also affects dir2dat)
                  [choices: "never", "auto", "always"] [default: "auto"]
  -O, --overwrite          Overwrite any files in the output directory
                                                               [boolean]
      --overwrite-invalid  Overwrite files in the output directory that
                           are the wrong filesize, checksum, or zip
                           contents                            [boolean]
      --write-retry        Number of additional retries to attempt when
                           writing a file has failed (0 disables
                           retries)                [number] [default: 2]

move command options:
      --move-delete-dirs  Delete empty subdirectories from the input
                          directories after moving ROMs
                  [choices: "never", "auto", "always"] [default: "auto"]

clean command options:
  -C, --clean-exclude  Path(s) to files to exclude from cleaning
                       (supports globbing)                       [array]
      --clean-backup   Directory to move cleaned files to (instead of
                       being recycled)                          [string]
      --clean-dry-run  Don't clean any files and instead only print what
                       files would be cleaned                  [boolean]

zip command options:
      --zip-format    The structure format to use for written zip files
               [choices: "torrentzip", "rvzstd"] [default: "torrentzip"]
  -Z, --zip-exclude   Glob pattern of ROM filenames to exclude from
                      zipping                                   [string]
      --zip-dat-name  Group all ROMs from the same DAT into the same zip
                      archive, if not excluded from zipping (enforces
                      --dat-threads 1)                         [boolean]

link command options:
      --link-mode         File linking mode
       [choices: "hardlink", "symlink", "reflink"] [default: "hardlink"]
      --symlink-relative  Create symlinks as relative to the target
                          path, as opposed to absolute         [boolean]

ROM header options:
      --header          Glob pattern of input filenames to force header
                        detection for                           [string]
  -H, --remove-headers  Remove known headers from ROMs, optionally
                        limited to a list of comma-separated file
                        extensions (supported: .a78, .fds, .lnx, .nes,
                        .smc)                                   [string]

Trimmed ROM options:
      --trimmed-glob        Glob pattern of input filenames to force
                            trimming detection for (overriding all
                            options below)                      [string]
      --trim-scan-files     Detect trimming for uncompressed files
                  [choices: "never", "auto", "always"] [default: "auto"]
      --trim-scan-archives  Detect trimming for files within archives
                            (off by default)                   [boolean]
      --trim-add-padding    When writing matched trimmed ROMs, add back
                            their trailing padding so the output matches
                            the DAT checksum                   [boolean]

ROM set options (requires DATs):
      --merge-roms             ROM merge/split mode (requires DATs with
                               parent/clone information)
    [choices: "fullnonmerged", "nonmerged", "split", "merged"] [default:
                                                        "fullnonmerged"]
      --merge-discs            Merge multi-disc games into one game
                                                               [boolean]
      --exclude-disks          Exclude CHD disks in DATs from processing
                               & writing                       [boolean]
      --allow-excess-sets      Allow writing archives that have excess
                               files when not extracting or zipping
                                                               [boolean]
      --allow-incomplete-sets  Allow writing games that don't have all
                               of their ROMs                   [boolean]

ROM filtering options:
  -x, --filter-regex           Regular expression of game names to
                               filter to                         [array]
  -X, --filter-regex-exclude   Regular expression of game names to
                               exclude                           [array]
  -L, --filter-language        List of comma-separated languages to
                               filter to (supported: DA, DE, EL, EN, ES,
                               FI, FR, IT, JA, KO, NL, NO, PT, RU, SV,
                               ZH)                              [string]
  -R, --filter-region          List of comma-separated regions to filter
                               to (supported: ARG, ASI, AUS, BEL, BRA,
                               CAN, CHN, DAN, EUR, FRA, FYN, GER, GRE,
                               HK, HOL, ITA, JPN, KOR, MEX, NOR, NZ,
                               POR, RUS, SPA, SWE, TAI, UK, UNK, USA,
                               WORLD)                           [string]
      --filter-category-regex  Regular expression of categories to
                               filter to                         [array]
      --no-bios                Filter out BIOS files, opposite of
                               --only-bios                     [boolean]
      --no-device              Filter out MAME devies, opposite of
                               --only-device                   [boolean]
      --no-unlicensed          Filter out unlicensed ROMs, opposite of
                               --only-unlicensed               [boolean]
      --only-retail            Filter to only retail releases, enabling
                               all the following "no" options  [boolean]
      --no-debug               Filter out debug ROMs, opposite of
                               --only-debug                    [boolean]
      --no-demo                Filter out demo ROMs, opposite of
                               --only-demo                     [boolean]
      --no-beta                Filter out beta ROMs, opposite of
                               --only-beta                     [boolean]
      --no-sample              Filter out sample ROMs, opposite of
                               --only-sample                   [boolean]
      --no-prototype           Filter out prototype ROMs, opposite of
                               --only-prototype                [boolean]
      --no-program             Filter out program application ROMs,
                               opposite of --only-program      [boolean]
      --no-aftermarket         Filter out aftermarket ROMs, opposite of
                               --only-aftermarket              [boolean]
      --no-homebrew            Filter out homebrew ROMs, opposite of
                               --only-homebrew                 [boolean]
      --no-unverified          Filter out unverified ROMs, opposite of
                               --only-unverified               [boolean]
      --no-bad                 Filter out bad ROM dumps, opposite of
                               --only-bad                      [boolean]

One game, one ROM (1G1R) options (applied in order):
  -s, --single             Output only a single game per parent (1G1R)
                           (required for all options below, requires
                           DATs with parent/clone information) [boolean]
      --prefer-game-regex  Regular expression of DAT game names to
                           prefer                                [array]
      --prefer-rom-regex   Regular expression of DAT ROM filenames to
                           prefer                                [array]
      --prefer-verified    Prefer verified ROM dumps over unverified
                                                               [boolean]
      --prefer-good        Prefer good ROM dumps over bad      [boolean]
  -l, --prefer-language    List of comma-separated languages in priority
                           order (supported: DA, DE, EL, EN, ES, FI, FR,
                           IT, JA, KO, NL, NO, PT, RU, SV, ZH)  [string]
  -r, --prefer-region      List of comma-separated regions in priority
                           order (supported: ARG, ASI, AUS, BEL, BRA,
                           CAN, CHN, DAN, EUR, FRA, FYN, GER, GRE, HK,
                           HOL, ITA, JPN, KOR, MEX, NOR, NZ, POR, RUS,
                           SPA, SWE, TAI, UK, UNK, USA, WORLD)  [string]
      --prefer-revision    Prefer older or newer revisions, versions, or
                           ring codes        [choices: "older", "newer"]
      --prefer-retail      Prefer retail releases (see --only-retail)
                                                               [boolean]
      --prefer-parent      Prefer parent ROMs over clones      [boolean]

Input file preferences (applied in order):
      --prefer-filetype        Prefer input files of a type
                        [choices: "plain", "archive"] [default: "plain"]
      --prefer-filename-regex  Regular expression of filenames to prefer
                                                                 [array]

playlist command options:
      --playlist-mode        Generate playlists depending on how many
                             ROMs a game has
                   [choices: "multiple", "always"] [default: "multiple"]
      --playlist-extensions  List of comma-separated file extensions to
                             generate multi-disc playlists for
           [string] [default: ".ccd,.cdi,.chd,.cue,.gdi,.iso,.mdf,.toc"]

dir2dat command options:
      --dir2dat-output  dir2dat output directory                [string]

fixdat command options:
      --fixdat-output  Fixdat output directory                  [string]

report command options:
      --report-output  Report output file location (formatted with
                       Moment.js date/time tokens)
                [string] [default: "igir_%YYYY-%MM-%DDT%HH:%mm:%ss.csv"]

Concurrency options:
      --dat-threads     Number of DATs to process in parallel
                                                   [number] [default: 2]
      --reader-threads  Maximum number of ROMs to read in parallel per
                        disk                       [number] [default: 6]
      --writer-threads  Maximum number of ROMs to write in parallel
                                                   [number] [default: 3]

Cache & directory options:
      --temp-dir       Path to a directory for temporary files  [string]
      --disable-cache  Disable loading or saving the cache file[boolean]
      --cache-path     Location for the cache file              [string]

Help & debug options:
  -v, --verbose    Enable verbose logging, can specify up to three times
                   (-vvv)                                        [count]
      --debug-log  Generate a debug log file to attach to bug reports
                                                                [string]
  -h, --help       Show help                                   [boolean]

------------------------------------------------------------------------

Advanced usage:

  Tokens that are replaced when generating the output (--output) path of
  a ROM:
    {datName}         The name of the DAT that contains the ROM (e.g.
    "Nintendo - Game Boy")
    {datDescription}  The description of the DAT that contains the ROM
    {region}          The region of the ROM release (e.g. "USA"), each
    ROM can have multiple
    {language}        The language of the ROM release (e.g. "En"), each
    ROM can have multiple
    {type}            The type of the game (e.g. "Retail", "Demo",
    "Prototype")
    {category}        The DAT-defined category of the game (e.g.
    "Games", "Demos", "Multimedia")
    {genre}           The DAT-defined genre of the game

    {inputDirname}    The input file's dirname
    {outputBasename}  Equivalent to "{outputName}.{outputExt}"
    {outputName}      The output file's filename without extension
    {outputExt}       The output file's extension

    {adam}      The ROM's emulator-specific /ROMS/* directory for the
    'Adam' image (e.g. "GB")
    {batocera}  The ROM's emulator-specific /roms/* directory for
    Batocera (e.g. "gb")
    {crossmix}  The ROM's emulator-specific /Roms/* directory for
    CrossMix-OS (e.g. "GB")
    {es}        The ROM's emulator-specific /roms/* directory for the
    'EmulationStation' image (e.g. "gb")
    {funkeyos}  The ROM's emulator-specific /* directory for FunKey OS
    (e.g. "Game Boy")
    {minui}     The ROM's emulator-specific /Roms/* directory for MinUI
    (e.g. "Game Boy (GB)")
    {mister}    The ROM's core-specific /games/* directory for the
    MiSTer FPGA (e.g. "Gameboy")
    {miyoocfw}  The ROM's emulator-specific /roms/* directory for
    MiyooCFW (e.g. "GB")
    {onion}     The ROM's emulator-specific /Roms/* directory for
    OnionOS/GarlicOS (e.g. "GB")
    {pocket}    The ROM's core-specific /Assets/* directory for the
    Analogue Pocket (e.g. "gb")
    {retrodeck} The ROM's emulator-specific /roms/* directory for the
    'RetroDECK' image (e.g. "gb")
    {rocknix}   The ROM's emulator-specific /roms/* directory for
    ROCKNIX (e.g. "gb")
    {romm}      The ROM's manager-specific /roms/* directory for 'RomM'
    (e.g. "gb")
    {spruce}    The ROM's emulator-specific /Roms/* directory for
    SpruceOS (e.g. "GB")
    {twmenu}    The ROM's emulator-specific /roms/* directory for
    TWiLightMenu++ on the DSi/3DS (e.g. "gb")

Example use cases:

  Merge new ROMs into an existing ROM collection and delete any
  unrecognized files:
    igir copy clean --dat "*.dat" --input New-ROMs --input ROMs --output
    ROMs

  Organize and zip an existing ROM collection:
    igir move zip --dat "*.dat" --input ROMs --output ROMs

  Generate a report on an existing ROM collection, without copying or
  moving ROMs (read only):
    igir report --dat "*.dat" --input ROMs

  Produce a 1G1R set per console, preferring English ROMs from
  USA>WORLD>EUR>JPN:
    igir copy --dat "*.dat" --input "**/*.zip" --output 1G1R
    --dir-dat-name --single --prefer-language EN --prefer-region
    USA,WORLD,EUR,JPN

  Copy all Mario, Metroid, and Zelda games to one directory:
    igir copy --input ROMs --output Nintendo --filter-regex
    "/(Mario|Metroid|Zelda)/i"

  Copy all BIOS files into one directory, extracting if necessary:
    igir copy extract --dat "*.dat" --input "**/*.zip" --output BIOS
    --only-bios

  Create playlist files for all multi-disc games in an existing
  collection:
    igir playlist --input ROMs

  Create patched copies of ROMs in an existing collection, not
  overwriting existing files:
    igir copy extract --input ROMs --patch Patches --output ROMs

  Re-build a MAME ROM set for a specific version of MAME:
    igir copy zip --dat "MAME 0.258.dat" --input MAME --output
    MAME-0.258 --merge-roms split

  Copy ROMs to an Analogue Pocket and test they were written correctly:
    igir copy extract test --dat "*.dat" --input ROMs --output
    /Assets/{pocket}/common --dir-letter
```
<!-- WARN: everything above is automatically updated! Update src/modules/argumentsParser.ts instead! -->

## Feature requests, bug reports, and contributing

Feedback is a gift! Your feature requests and bug reports help improve the project for everyone. Feel free to [submit an issue](https://github.com/emmercm/igir/issues/new/choose) on GitHub using one of the templates.

Even better, if you feel comfortable writing code, please feel free to submit a pull request against the project! Please see the full [contribution guidelines](https://igir.io/contributing) for rules to follow.

<br>
<p align="center">
  <a href="https://github.com/emmercm/igir/graphs/contributors"><img alt="GitHub: contributors" src="https://img.shields.io/github/contributors/emmercm/igir?logo=github&logoColor=white"></a>
  <a href="https://github.com/emmercm/igir/issues?q=is%3Aopen+is%3Aissue+label%3Abug"><img alt="GitHub: bugs" src="https://img.shields.io/github/issues/emmercm/igir/bug?color=%23d73a4a&label=bugs&logo=github&logoColor=white"></a>
  <a href="https://github.com/emmercm/igir/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement"><img alt="GitHub: feature requests" src="https://img.shields.io/github/issues/emmercm/igir/enhancement?color=%234BBCBC&label=feature%20requests&logo=github&logoColor=white"></a>
  <a href="https://github.com/emmercm/igir/discussions"><img alt="GitHub: discussions" src="https://img.shields.io/github/discussions/emmercm/igir?logo=github&logoColor=white"></a>
  <a href="https://hacktoberfest.com/"><img alt="Hacktoberfest: participant" src="https://img.shields.io/badge/hacktoberfest-participant-orange?logo=digitalocean&logoColor=white"></a>
</p>

<p align="center">
  <a href="https://github.com/emmercm/igir/graphs/contributors">
    <img src="https://contrib.rocks/image?repo=emmercm/igir" alt="List of contributors" />
  </a>
</p>
