nx-title-id
Given a folder containing Nintendo Switch game packages (.nsp, .nsz, .xci, or .xcz files), this will look up the Title ID and, optionally, the Build ID.
If you keep a library of dumped games, you already know the problem: a single game is scattered across a base .nsp, an update, and a pile of DLC, each with a cryptic file name. nx-title-id reads the packages themselves and prints the base-game title ID they all belong to, so you can rename, sort, de-duplicate, or match against a database without opening a single file by hand.
It is fast and quiet: it reads only small parts of each package (the headers, plus the game's executable if you ask for a Build ID) and never the multi-gigabyte body, it prints clean title IDs to stdout and everything else to stderr so it drops straight into a shell pipeline, and it never needs your keys unless a package gives it no other way to answer (although keys are definitely recommended).
Prerequisites
- A package to inspect:
.nspor.nsz(an NSP archive), or.xcior.xcz(a cartridge image). - Python 3.14 or newer, if you install from source.
- Optionally (but recommended), a
prod.keysfile. It is only needed for packages that carry neither a ticket nor a[title ID]tag in their name, where the title ID has to be read from the encrypted meta NCA. It is required for-b/--build-id(see Build ID). Key values are never printed.
Installation
Run it without installing anything permanently:
uvx nx-title-id <folder>
Or install it as a tool on your $PATH:
uv tool install nx-title-id
Usage
Point it at a folder. A folder holding one game (its base, update, and DLC) prints one line:
$ uvx nx-title-id "~/Games/League Of Evil"
01009C100390E000
Give it the keys file when a package needs the meta NCA fallback.
$ uvx nx-title-id "~/Games/League Of Evil" -k "/path/to/prod.keys"
01009C100390E000
When -k is omitted, ~/.switch/prod.keys (or %USERPROFILE%\.switch\prod.keys) is used automatically if it exists.
A folder with more than one game prints every title ID (sorted), warns on stderr, and exits 3, so a script can tell one game from several by the exit code alone:
$ uvx nx-title-id ~/Games/Bundle
more than one game in the folder:
0100000000010000
0100000000020000
Build ID
Add -b (or --build-id) to print each game's Build ID after its title ID, separated by one space:
$ uvx nx-title-id "~/Games/League Of Evil" -b
01009C100390E000 1816F8A3E4F09B33
The Build ID is the first 8 bytes of the main NSO's ModuleId, as 16 uppercase hex digits. It is the form nxdumptool and Atmosphere call the Build ID. Without -b, the output is unchanged.
It is opt-in because it is the expensive path: the tool has to decrypt the game's program NCA down to its main executable (it reads that section, not the whole package), so -b always needs prod.keys:
-
header_keyis required up front. If it is missing, the run exits1with a one-line error before reading any package. -
key_area_key_application_XXand/ortitlekek_XXare needed per game. Thetitlekek_XXis for retail titlekey (rights ID) programs, which are read through the package's own common-type.tik. -
A game that cannot be resolved is skipped with a one-line message on
stderr, and the rest still print. That covers a game known only from a ticket or tag (no program NCA), a missing key, a missing ticket, or a personalized ticket.
The Build ID comes from the newest update package of the base game (the greatest readable update version; any update beats the base when versions are unreadable), otherwise from the base package. DLC is never the source.
Exit codes are the same as below, decided on the games actually printed: 0 for one game, 3 for several, 1 when nothing resolved. Key values are never printed.
How the title ID is found
For each package, in order:
-
The ticket's name. A package's ticket is named after its rights ID (
<32 hex digits>.tik), whose first 16 digits are the title ID. This wins over the file name, since tags in file names can be wrong. -
A
[title ID]tag in the file name, such asGame [0100000000010000][v0].nsp. -
The meta NCA. For a package with neither, the NCA headers are decrypted with
header_keyfromprod.keys, and every meta NCA title ID is read.
Steps 1 and 2 use only the Python standard library. Step 3 uses pycryptodome for AES.
Base games, updates, and DLC all fold to their base game, so a folder with one game prints one line. A single cartridge that holds several games contributes every one of them.
Cartridge images (.xci, .xcz) seldom carry a ticket, so they usually need a tag or the keys. Cartridge dumps with the 0x1000-byte key area at the start, and NCA0 headers from very early titles, are not read.
Exit status
| Code | Meaning |
|---|---|
0 |
One game: at least one title ID was printed and they all fold to a single base game. |
1 |
The path is not a folder, the keys file is missing or unreadable, or no package has a readable title ID. |
With -b: no usable prod.keys (or no header_key), or no game has a readable Build ID. |
|
2 |
Bad command-line arguments. |
3 |
More than one game: every title ID is printed and a warning goes to stderr. |
A keys file without header_key is a warning, not an error: packages that need the meta NCA fallback are skipped, and the rest still print.
Exit code 3 also covers a single multi-title cartridge that holds more than one base game (e.g., Final Fantasy I-VI Pixel Remaster, Super Mario Galaxy 1 & 2). You should extract each game into its own .nsp with nstool, 4NXCI, or similar.
Troubleshooting
A package is skipped for lack of a title ID
The message is skipping <file>: no ticket and no [title ID] in its name (give -k prod.keys to read its meta NCA). The package has no ticket and no tag, so it needs the keys. Pass -k path/to/prod.keys, or put the file at ~/.switch/prod.keys.
A warning about header_key
The message is warning: <keys> has no header_key; skipping packages that need it. The keys file is missing the header_key entry. Packages resolvable by ticket or tag still print; only ones that need decryption are skipped. With -b this is fatal instead (see below).
You can re-dump your latest keys for your firmware with lockpick (a.k.a., lockpick_rcm).
“cannot read keys file
The keys file exists but could not be read (for example, permission denied). Check its permissions.
-b needs prod.keys
The error is -b/--build-id needs prod.keys; pass -k or install one at <path>. -b needs your keys and none were found. Pass -k path/to/prod.keys, or put the file at the path shown (~/.switch/prod.keys by default). The run exits 1 before reading any package.
-b needs a header_key
The error is -b/--build-id needs a header_key in <keys>. The keys file -b found has no header_key line, so no program NCA can be decrypted. You will see the warning: … has no header_key line first. Re-dump your keys with lockpick (a.k.a., lockpick_rcm). The run exits 1 before reading any package.
A game is skipped under -b
The message is skipping game <title ID>: no Build ID (no readable program NCA, ticket, or key in <keys>). That game has no Build ID that could be read, and the other games still print. Common causes:
- The game is known only from a ticket or a
[title ID]tag, and none of its packages holds a program NCA. - The keys file lacks the
key_area_key_application_XXortitlekek_XXentry for that game's key generation. Re-dump your keys withlockpick(a.k.a.,lockpick_rcm). - A retail game's package has no
.tikticket, or its ticket is personalized (tied to one console), which is not supported. - The program NCA uses an encryption type the tool does not handle.
A game is skipped with an error under -b
The message is skipping game <title ID>: <error>. One of the game's packages is corrupt, truncated, or unreadable (for example, permission denied) when the program NCA was read. The error text says which. The other games still print.
Every game is skipped under -b
The error is no game with a readable Build ID in <folder>, with exit 1. Every game was skipped under -b, so nothing was printed. Read the skipping game … lines above it for the causes.
“skipping : ...”
The file is not a valid package, is a cartridge dump with the leading key area, or uses an NCA0 header. These layouts are not supported.
No output and exit 1
Nothing in the folder resolved to a Title ID. Confirm the folder holds real packages, and supply keys if the packages lack tickets and tags.
Several IDs when you expected one
The folder (or a single cartridge) genuinely holds more than one base game; the tool exits 3 in that case.
Running the tests
The suite runs with Tryke, a Python test runner written in Rust:
uv run tryke test
How it works:
-
Most tests build synthetic packages in memory (fake
PFS0/HFS0containers,NCAheaders, program NCAs with anexefsand amainexecutable, and tickets), so the suite needs no real game files and no real keys to pass. -
The hand-rolled AES-XTS routine, and the AES-CTR and AES-ECB steps that
-buses, are checked against an independent reference implementation (thecryptographypackage), so a decryption bug cannot hide behind the tool's own code. -
Five property-style tests (seeded random loops, not a property library) cover the Build ID: the ModuleId round trip, the first-8-bytes rendering, newest-update selection, DLC exclusion, and unchanged output without
-b. -
Tests that require local-only assets (real dumped NSPs, a real cartridge image, or your real
~/.switch/prod.keys) skip themselves cleanly when those assets are absent, so a fresh clone still gets a green run. They run automatically when the assets are present.- These files are named
<title-id>-<build-id>.nsp(for example01009C100390E000-1816F8A3E4F09B33.nsp), so that the title ID and the-bBuild ID are each evaluated against the values in the name; orff.xcifor the multi-title Final Fantasy I-VI Pixel Remaster. The-btests also need your real~/.switch/prod.keys.
- These files are named
-
The CLI is exercised end-to-end in a subprocess with an isolated
HOME, so a run can never read your real keys or write into the repository.
License
MIT. See LICENSE.
Metadata
Release files for nx-title-id 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| nx_title_id-1.1.0.tar.gz | 22.6 kB | Without provenance | Details for nx_title_id-1.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| nx_title_id-1.1.0-py3-none-any.whl | Python 3 | none | any | Details for nx_title_id-1.1.0-py3-none-any.whl |
Total release size: 45.2 kB
Release files / nx_title_id-1.1.0.tar.gz
| Download URL | nx_title_id-1.1.0.tar.gz |
|---|---|
| Size | 22.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
441e2022b9ab46f78f62c13708421be0046c06bca3bca9cde00f3e76d68603f9
|
|
BLAKE2b-256 checksum How to use checksums |
81641ebd6f3cabd3d0119c873d720ed228c9dd7ce8c653841710c12ed59f879e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / nx_title_id-1.1.0-py3-none-any.whl
| Download URL | nx_title_id-1.1.0-py3-none-any.whl |
|---|---|
| Size | 22.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b72ae552a0b077d533e6291afdbfa387f8535426ec6bedc9c78ce98682541eea
|
|
BLAKE2b-256 checksum How to use checksums |
dd21795b9cea51fe85f0140f37b28056aea1bef4f078154e76fa8b2399608b6f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for nx-title-id 1.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.