A preservation-focused CLI and GUI tool to convert between TerraOnion
.neocontainers and MAME / Darksoft Neo Geo ROM sets, and to edit.neoheader metadata (name, manufacturer, year, genre, NGH, screenshot index) without repacking ROM data.
Use neoconv on the command line or via neoconv-gui (same core engine). It packs MAME ZIPs or ROM folders into .neo, extracts .neo back to individual ROM files, and rewrites headers when only metadata needs to change. It handles C-ROM byte-interleaving, P-ROM half-swap (default: auto-detect from the M68000 vector table, with yes / no overrides), V-ROM chunking, and TerraOnion header fields — for commercial dumps, hacks, CD conversions, and homebrew.
| Goal | Command / tab |
|---|---|
MAME ZIP or ROM folder → .neo |
neoconv pack … or GUI Pack |
.neo → MAME / Darksoft ROM files |
neoconv extract … or GUI Extract |
| Fix title, genre, NGH, … without repacking | neoconv edit … or GUI Edit |
| Inspect metadata, sizes, and MD5 hashes | neoconv info … or GUI Info |
Most Neo Geo tools only handle one direction or break on non-standard sets. neoconv focuses on correct conversion and transparent metadata edits for .neo files.
| Property | Description |
|---|---|
| Reliable | Deterministic conversions with automated tests for core roundtrip behaviour |
| Universal | Handles commercial sets, hacks, CD conversions, and homebrew |
| Transparent | C interleaving, P-ROM swap (auto or manual), and V chunking are explicit and documented |
| Configurable | On extract: --c-chip-size and --v-bank-size in bytes. neoconv falls back to 2 MB (2097152) when unset; the correct value per game comes from the MAME ROM set (e.g. 4 MB / 4194304 C chips for Neo Turf Masters), not from the .neo header alone |
| Dual interface | Same pack / extract / edit / info workflows in CLI and GUI |
Both interfaces support the same core workflows:
| Workflow | CLI | GUI |
|---|---|---|
Extract .neo to MAME / Darksoft files |
✅ | ✅ |
Pack MAME ZIP or directory to .neo |
✅ | ✅ |
Edit .neo header metadata (no repack) |
✅ | ✅ |
View .neo metadata, region sizes, and per-region MD5 |
✅ | ✅ |
| P-ROM bank swap (auto-detect + manual override) | ✅ | ✅ |
Standalone detect-swap (inspect P-ROM without packing) |
✅ | — (Pack with Auto-detect logs the same check in the GUI) |
| Diagnostic mode for unrecognized files | ✅ | ✅ |
- Python 3.9+
- GUI only: Tk (
tkinter) — usually bundled with Python; on Linux installpython3-tkvia your package manager - GUI drag & drop:
tkinterdnd2(optional, see GUI usage)
CLI-only use remains pure Python stdlib (plus optional pytest for development tests).
sudo apt-get update
sudo apt-get install -y python3 python3-pip python3-venv python3-tksudo dnf install -y python3 python3-pip python3-tkintersudo pacman -S --needed python python-pip tk- Install Python 3.9+ (python.org installer or Homebrew).
- If
tkinteris missing with Homebrew Python, install Tk:
brew install python tcl-tk- Install Python 3.9+ from python.org.
- Keep the default Tcl/Tk component enabled in the installer.
Download the latest binary from the Releases page — no Python installation required. Prebuilt assets are GUI only; install from source (below) for the neoconv CLI.
| Platform | File |
|---|---|
| Windows | neoconv-gui-x.y.z-windows.exe — run directly |
| macOS | neoconv-gui-x.y.z-macos.zip — unzip, then double-click neoconv-gui.app (first launch: right-click → Open to bypass Gatekeeper) |
| Linux | neoconv-gui-x.y.z-linux — chmod +x, then run |
git clone https://github.com/d4NY0H/neoconv
cd neoconv
python3 -m pip install .For development (editable install + test runner):
python3 -m pip install -e ".[dev]"
python3 -m pytest tests/ -vpython3 -m neoconv --version
python3 -m neoconv --help
python3 -c "import tkinter as tk; print('tkinter', tk.TkVersion)"
python3 -c "import tkinterdnd2; print('tkinterdnd2 OK')"- If the
tkintercheck fails, the CLI still works, but the GUI needs a Python build with Tk. - If the
tkinterdnd2check fails, the GUI works normally — only drag & drop onto file path fields is disabled.
Start GUI:
neoconv-gui
# or
python3 -m neoconv.guiThe window uses the bundled app icon where the platform/Tk allow it.
Each file path row supports drag & drop onto its text entry field (next to "Browse…"): drop a .neo, a MAME ZIP, or a folder where the UI expects a directory. Drag & drop requires tkinterdnd2 — install it once if needed:
pip install tkinterdnd2>=0.4.0Without it the GUI works normally; only drag & drop is disabled.
Tabs:
| Tab | Description |
|---|---|
| Pack | Build .neo from ZIP/folder with metadata; P-ROM swap via radio (auto / yes / no). Status line checks mandatory P/S/M roles and warns on V/C filename gaps before pack |
| Extract | Convert .neo to MAME or Darksoft ZIP/directory; P / C / V size dropdowns plus optional comma-separated size lists for mixed/multi-chip sets (same grammar as CLI). P default keeps one p1 |
| Edit | Load header fields from a .neo, adjust metadata (name/manufacturer truncated to header limits), write back (optional separate output path) |
| Info | Inspect metadata, ROM region sizes, and MD5 per region (P, S, M, V, C) |
neoconv <command> [options]
# or
python3 -m neoconv <command> [options]Commands: extract, pack, edit, detect-swap, info.
On failure, the CLI prints Error: … to stderr and exits with code 1 (missing ROMs, invalid options, bad .neo, and similar cases) instead of a Python traceback.
# MAME ZIP
neoconv extract input.neo --prefix game --format mame --out game_mame.zip
# Darksoft ZIP
neoconv extract input.neo --prefix game --format darksoft --out game_darksoft.zip
# Output directory
neoconv extract input.neo --prefix game --format mame --out-dir ./roms/
# Explicit C chip size (example: 4 MB chips)
neoconv extract input.neo --prefix game --c-chip-size 4194304 --out game_customc.zip
# Explicit V bank size (example: 4 MB v1/v2 chunks)
neoconv extract input.neo --prefix game --v-bank-size 4194304 --out game_v4m.zip
# Mixed C / V / P sizes (e.g. Thrash Rally / trally) — same lists in GUI Extract
neoconv extract trally.neo --prefix 038 \
--p-chip-sizes 524288,524288 \
--c-chip-sizes 1048576,1048576,524288,524288 \
--v-bank-sizes 1048576,524288 \
--out-dir ./trally_out/| Option | Default | Description |
|---|---|---|
--prefix, -p |
(input stem) | Filename prefix for output files |
--format, -f |
mame |
Output format: mame (.bin) or darksoft (.rom) |
--out, -o |
(auto) | Output ZIP path (ignored if --out-dir is set) |
--out-dir, -d |
— | Extract to directory instead of ZIP; wins over --out when both are given |
--p-chip-size |
0 (= entire as p1) |
Uniform size of each P chip in bytes. 0 keeps one p1. GUI: P Chip Size dropdown |
--p-chip-sizes |
— | Comma-separated per-file P sizes (p1,p2,…). Sum must equal the P region. Cannot combine with --p-chip-size. GUI: optional free-text under the P dropdown |
--c-chip-size |
0 (= 2097152) |
Uniform size of each C chip in bytes. 0 = 2 MB. GUI presets match common sizes. See C-ROM interleaving |
--c-chip-sizes |
— | Comma-separated per-chip sizes (c1,c2,c3,c4,…) for mixed C pairs. Cannot combine with --c-chip-size. GUI: optional free-text under the C dropdown |
--v-bank-size |
0 (= 2097152) |
Uniform size of each V file (v1, v2, …) in bytes. 0 = 2 MB. See V-ROM chunking |
--v-bank-sizes |
— | Comma-separated per-file V sizes (v1,v2,…) for mixed V ROMs. Cannot combine with --v-bank-size. GUI: optional free-text under the V dropdown |
Overwrite behaviour: Existing output files are replaced without prompting. A warning is printed (CLI: stderr; GUI: log) for each path that already exists. Directory extract updates files in place; ZIP output is replaced atomically (see Atomic file writes).
# From MAME ZIP
neoconv pack input.zip \
--name "Example Game" \
--manufacturer "Example Studio" \
--year 1995 \
--genre Sports \
--ngh 100 \
--out output.neo
# From directory
neoconv pack ./roms/ \
--name "My Game" \
--manufacturer "Example Studio" \
--year 1994 \
--genre Fighting \
--ngh 149 \
--out mygame.neo
# With P-ROM bank swap auto-detect (default — recommended)
neoconv pack input.zip --name "Example Game" --ngh 100 --out output_auto.neo
# Force swap (explicit override)
neoconv pack input.zip --name "Example Game" --ngh 100 --swap-p yes --out output_swap_yes.neo
# Never swap (opt out of auto-detect)
neoconv pack input.zip --name "Example Game" --ngh 100 --swap-p no --out output_swap_no.neo
# Diagnostic output for unrecognized files
neoconv pack ./roms/ --name "Test" --diagnostic --out test.neo| Option | Default | Description |
|---|---|---|
--name, -n |
Unknown |
Game title in the .neo header: latin-1, max 32 content bytes (33-byte field incl. NUL). The GUI truncates to this limit. |
--manufacturer, -m |
Unknown |
Manufacturer string: latin-1, max 16 content bytes (17-byte field incl. NUL). The GUI truncates to this limit. |
--year, -y |
0 |
Release year: uint32. |
--genre, -g |
Other |
Genre name or numeric uint32 id (see list below). |
--ngh |
0 |
NGH catalogue number: uint32 (non-negative integer). |
--screenshot |
0 |
TerraOnion screenshot index: uint32 (non-negative integer). |
--out, -o |
(input stem + .neo) |
Output .neo path |
--swap-p |
auto |
P-ROM half-swap mode: auto (heuristic, default), yes (always), no (never) |
--diagnostic |
off | Warn on unrecognized filenames |
Genres (--genre name or numeric id):
| ID | Name |
|---|---|
| 0 | Other |
| 1 | Action |
| 2 | BeatEmUp |
| 3 | Sports |
| 4 | Driving |
| 5 | Platformer |
| 6 | Mahjong |
| 7 | Shooter |
| 8 | Quiz |
| 9 | Fighting |
| 10 | Puzzle |
See also: P-ROM bank swap, ROM file naming.
Updates TerraOnion header fields without touching P/S/M/V/C payload. At least one metadata option is required.
# Correct title in place (overwrites the input file atomically)
neoconv edit game.neo --name "Windjammers"
# Multiple fields; write a new file (input unchanged)
neoconv edit game.neo --genre Fighting --year 1994 --ngh 65 --out game_fixed.neo| Option | Default | Description |
|---|---|---|
--out, -o |
(overwrite input) | Output .neo path |
--name, -n |
— | Same limits as pack: latin-1, max 32 content bytes |
--manufacturer, -m |
— | Same limits as pack: latin-1, max 16 content bytes |
--year, -y |
— | uint32 year |
--genre, -g |
— | Genre name or uint32 id (same set as pack) |
--ngh |
— | uint32 NGH number |
--screenshot |
— | uint32 screenshot index |
neoconv detect-swap input.zip
neoconv detect-swap input-p1.binInspects the M68000 vector table in both 1 MB halves of a 2 MB P-ROM and reports whether --swap-p yes is needed. Works with raw P-ROM files or MAME ZIPs.
Example output:
Inspecting P-ROM from ZIP: input.zip (2,097,152 bytes)
Result : --swap-p yes ← required
Reason : Second half has valid vectors (SP=0x0010F300, Reset=0x00C00402) — swap required.
See P-ROM bank swap for --swap-p modes when packing.
Prints header metadata, ROM region sizes, and MD5 per region (P, S, M, V, C) when a full .neo is loaded — useful to compare two files that differ only in the header.
neoconv info input.neoBackground on pack/extract behaviour, ROM naming, and the on-disk .neo layout.
| Section | Topics |
|---|---|
| Pack input | MAME filenames, directory layout, synthetic S-ROM |
| Extract sizing | Byte presets, P split, C interleaving, V chunking |
| P-ROM swap | --swap-p / auto-detect |
.neo format |
Header layout, V1/V2 fields |
| Notes | CRC mismatches, out-of-scope items |
| Errors | NeoConvError hierarchy for library callers |
Expected failures from neoconv.core raise subclasses of NeoConvError (not bare ValueError):
| Class | Typical cause |
|---|---|
InvalidNeoError |
Truncated or invalid .neo container |
InvalidRomLayoutError |
Missing/duplicate ROM roles, ZIP/dir layout, C/V sequence gaps |
InvalidConfigurationError |
Extract chip/bank size, P-ROM swap size rules |
UserCancelledError |
GUI worker cancelled by the user |
Each error has a stable code string and optional hint for remediation. The CLI prints hints after an em dash; the GUI logs them on the same line as the message.
The table below matches the primary rules in name_to_role: extension (e.g. .p1, .c1) or basename suffix patterns such as -p1.bin, _m1.bin, or a stem ending in -v3 / _c2.
| Role | Recognized patterns |
|---|---|
| P ROM | .p1, .p2, -p1.bin, _p1.bin, -p2.bin |
| S ROM | .s1, -s1.bin, _s1.bin |
| M ROM | .m1, -m1.bin, _m1.bin |
| V ROMs | .v1-.v8, -v1.bin--v8.bin, _v1.bin-_v8.bin |
| C ROMs | .c1-.c16, -c1.bin--c16.bin, _c1.bin-_c16.bin, and letter-split parts -c1a.bin / -c1b.bin (C9–C16 for extended / hack sets) |
pack reads ROM files in the chosen folder and one level of subfolders (typical after unzipping).
Gaps in the V or C sequence (e.g. v1 + v3 without v2) cause pack to abort with an error instead of producing a broken .neo.
Some MAME parents (e.g. PVC / encrypted boards such as mslug5, samsho5, svc, kof2003) ship without a separate text-layer s1; the emulator driver uses a zero-filled "fixed" region. If P and M are present, there is no s1, but filenames look like a Neo Geo C1 sprite set, neoconv may inject a zero-filled S region and emit a UserWarning.
That fill matches MAME softlist size heuristics so pack can complete — it is not a hardware-correct .neo. On TerraOnion / MiSTer (and similar cores) the text/fix layer will be missing or corrupt. Bootlegs that do include an s1 (e.g. ms5plus) pack normally. There is currently no path to derive real s1 tiles from C-ROM data.
| Pattern | Synthetic S size |
|---|---|
Basenames starting with kf10- (KOF2002 bootleg) |
256 KiB |
-c1r. / -c2r. sprite chip names (e.g. 269-c1r.c1) |
512 KiB |
Certain three-digit MAME set IDs in NNN-p1. / NNN-m1. / NNN-c1….c1 (see _SYNTH_S_MAME_512K_SET_IDS in neoconv/core/constants.py) |
512 KiB |
Default (e.g. mslug5 / set id 268 — do not force 512 KiB) |
128 KiB |
If synthetic S is injected but no C sprite data was collected, pack aborts (avoids a silent empty-C .neo).
To get a playable file for those parents today: start from a known-good .neo that already has real S data, or use a ROM set that includes a dedicated s1.
FBNeo Neo Geo sets use the same ROM naming conventions as MAME and are supported out of the box. BIOS-related files (000-lo.lo, sfix.sfix, sm1.sm1, sp-s2.sp1, etc.) are ignored automatically.
Sets where C-chip filenames carry a suffix in the stem but use native extensions (e.g. 263-c1d.c1, 269-c1r.c1, 268-c1c.c1) are recognised correctly — the extension .c1 / .c2 match takes priority.
Split C chips (c1a + c1b style): Hack/homebrew sets that split individual C chips across letter-suffixed .bin files are merged automatically (parts concatenated in letter order, then interleaved as usual):
| Set | Files |
|---|---|
kog, kogplus |
5232-c1a.bin + 5232-c1b.bin → c1, etc. |
kof10th and variants |
kf10-c1a.bin + kf10-c1b.bin → c1, etc. |
A single letter part alone (e.g. only kf10-c1a.bin / kf10-c2a.bin) is also accepted as the full chip. All official SNK titles and the vast majority of hacks work without any preparation.
Standard BIOS files (000-lo.lo, sfix.sfix, etc.) are ignored. Unknown files are also ignored unless --diagnostic is enabled.
The .neo header stores only the total byte count for the P-, C-, and V-ROM blocks — it does not record how many chips those blocks were split across, or how large each individual chip was. That information existed in the original MAME ROM set but is lost when the files are merged into the .neo container.
When neoconv extracts, it must re-split those blocks into individual files. The split is purely mechanical — any size that divides the total evenly is accepted without error. The question is only whether the resulting files match what MAME or Darksoft expect.
For example, 8 MB of C-ROM data could legitimately be split as c1(4 MB)+c2(4 MB) or as c1(2 MB)+c2(2 MB)+c3(2 MB)+c4(2 MB). Both extractions succeed, but only one matches the MAME neogeo.xml entry for that title. Using the wrong split means MAME's ROM verification (verifyroms) will report missing or mismatched files. The ROM data itself is intact — if you correct the XML to match the actual file layout, the game will run.
The 2 MB default covers the majority of titles. Always cross-check against the MAME neogeo.xml or FBNeo ROM database for any title you are unsure about.
The CLI flags --c-chip-size and --v-bank-size take bytes (not megabytes). The GUI dropdown labels use MB/KB; values map to the same byte counts.
| Label | CLI value (--c-chip-size / --v-bank-size) |
C GUI | V GUI |
|---|---|---|---|
| 512 KB | 524288 |
✅ | ✅ |
| 1 MB | 1048576 |
✅ | ✅ |
2 MB (unset / 0) |
2097152 |
✅ | ✅ |
| 4 MB | 4194304 |
✅ | ✅ |
| 8 MB | 8388608 |
✅ | ✅ |
| 16 MB | 16777216 |
✅ | ✅ |
| 20 MB | 20971520 |
✅ (C only) | — |
0 for either flag is neoconv’s fallback (2097152, 2 MB), not necessarily the correct size for every game. Any other positive integer is accepted on the CLI.
C-ROM graphics are byte-interleaved in .neo:
- even bytes → odd chips (
c1,c3, …) - odd bytes → even chips (
c2,c4, …)
C chips always come in pairs. Interleaved bank size is chip_size × 2.
On extract, if unset, neoconv assumes 2 MB per chip (2097152 bytes; CLI 0 / GUI “2 MB”). Larger chips (e.g. 4 MB / 4194304 for Neo Turf Masters) must match the MAME set — total C size in the .neo header does not uniquely determine per-chip size.
| When unset | GUI presets | CLI | |
|---|---|---|---|
C chip (c1, c2, …) |
2 MB (2097152) |
512 KB – 20 MB | --c-chip-size any positive byte value (0 = 2097152); or --c-chip-sizes for mixed pairs |
Choosing the right size: For a known title, open MAME’s neogeo.xml (or the FBNeo ROM database) and check each c1, c2, … entry. The size="…" attribute is the per-chip size in bytes (e.g. size="4194304" → --c-chip-size 4194304). If extract fails with “not a multiple of chip_size×2”, try another size from that list — or use --c-chip-sizes when chip pairs have different sizes.
Mixed C pair sizes: Some parents (e.g. trally) interleave a 1 MiB pair then a 512 KiB pair (C blob 3 MiB). A single --c-chip-size 524288 divides evenly but yields wrong chip boundaries. Use the list form instead:
neoconv extract trally.neo --c-chip-sizes 1048576,1048576,524288,524288 --out-dir ./outIn the GUI Extract tab, leave the dropdowns as-is and fill C chip sizes / V bank sizes with the same comma-separated byte lists.
Pack concatenates multi-chip program ROMs (p1 + p2 + …) into one P region. On extract the default is a single p1 for the whole region (most titles).
| When unset | GUI presets | CLI | |
|---|---|---|---|
P chip (p1, p2, …) |
Entire region as p1 |
Entire as p1; 512 KB – 8 MB | --p-chip-size positive bytes (0 = entire as p1); or --p-chip-sizes for unequal parts |
Equal halves (e.g. trally): --p-chip-size 524288 (or GUI 512 KB) yields p1 + p2 of 512 KiB each.
Unequal parts: use --p-chip-sizes / GUI P chip sizes; the sum must equal len(P) or extract fails hard (no silent wrong split).
V-ROM data is contiguous in .neo and split to v1, v2, … on extract. When unset, chunk size is 2 MB (2097152; --v-bank-size 0 or GUI V Bank Size).
| When unset | GUI presets | CLI | |
|---|---|---|---|
V bank (v1, v2, …) |
2 MB (2097152) |
512 KB – 16 MB | --v-bank-size any positive byte value (0 = 2097152); or --v-bank-sizes for mixed V files |
Choosing the right size: In MAME neogeo.xml, check each v1, v2, … entry — the file size is the bank size for --v-bank-size (often 2 MB / 2097152; some sets use 4 MB / 4194304). Match what the MAME ZIP contains so filenames and lengths align after extract.
If total V size is not a multiple of a uniform bank size, the last file is shorter and a warning is emitted. For mixed V sizes (e.g. 1 MiB + 512 KiB), use --v-bank-sizes so the sum matches len(V) exactly (no silent wrong split).
Some Neo Geo titles and hacks store their 2 MB P-ROM with the two 1 MB halves in reversed order relative to what TerraOnion NeoSD / MiSTer expect.
neoconv detects this automatically by inspecting the M68000 exception-vector table (initial Stack Pointer + Reset PC) in both halves. The half carrying valid values — SP in Work RAM (0x100000–0x10FFFF), Reset PC in ROM or BIOS (0x000100–0x1FFFFF or 0xC00000–0xC7FFFF) — determines whether a swap is applied.
| Mode | Behaviour |
|---|---|
auto (default) |
Detect from vector table; prints a diagnostic line |
yes |
Always swap — for titles where auto-detect is ambiguous |
no |
Never swap — to opt out explicitly |
When auto-detect is inconclusive (neither half has plausible M68k vectors), pack continues without swap but prints [WARN] and emits a warning — run neoconv detect-swap <zip> or set --swap-p yes / no explicitly.
Some .neo files split the total V ROM payload across the V1 and V2 size fields at offsets 0x010 and 0x014. neoconv reads both and merges the bytes in memory. On output (pack, build_neo, replace_neo_metadata), the header is normalised: all V length is stored in V1 and V2 is set to 0 (TerraOnion-style layout). Loading or rewriting such a file may emit a UserWarning.
Offset 0x000 Magic b'NEO\x01' (4 bytes)
Offset 0x004 P ROM size uint32 LE
Offset 0x008 S ROM size uint32 LE
Offset 0x00C M ROM size uint32 LE
Offset 0x010 V1 ROM size uint32 LE (all V data merged here on write; read accepts split V1/V2)
Offset 0x014 V2 ROM size uint32 LE (0 when written by neoconv; non-zero allowed on read)
Offset 0x018 C ROM size uint32 LE (total, interleaved)
Offset 0x01C Year uint32 LE
Offset 0x020 Genre uint32 LE
Offset 0x024 Screenshot uint32 LE
Offset 0x028 NGH number uint32 LE
Offset 0x02C Name 33 bytes, null-terminated, latin-1
Offset 0x04D Manufacturer 17 bytes, null-terminated, latin-1
Offset 0x200-0xFFF (padding, header is always 4096 bytes)
Data: P, S, M, V, C (sequentially, sizes from header)
pack (output .neo), extract (output ZIP), and edit write via a temporary file in the same directory and os.replace, so an interrupted run is less likely to leave a truncated file. Directory extract still overwrites individual ROM files in place (with warnings).
For hacks and CD conversions, MAME verifyroms may report CRC mismatches because data differs from known dumps. This is expected when the dump is intentionally different from MAME's reference set.
detect-swapon.neofiles: not useful — the P-ROM swap is applied duringpackand already encoded in the.neopayload. There is nothing left to detect.verifyCLI subcommand:verify_roundtripexists inneoconv.coreas a testing utility. Exposing it as a CLI command would only confirm that neoconv round-trips its own output correctly, which is covered by the test suite. It is not a user-facing feature.- Directory traversal deeper than one level in
pack: the one-level limit (top-level files + one subdirectory) covers all known MAME unzip layouts. Recursive traversal would risk picking up unrelated files from nested archives or build artefacts. - FBNeo as a separate input format: FBNeo Neo Geo sets use the same naming conventions as MAME and are supported out of the box — see FBNeo compatibility.
- Deriving real s1 / text-layer tiles from C-ROM for PVC parents (and CLI/
--s1injection from a reference.neo): not supported yet — see Synthetic S-ROM.
Older neoconv builds packed Year/Genre as uint16 at 0x1C/0x1E (NGH at 0x24). Current neoconv matches TerraOnion / neosdconv (four uint32). On read, headers that still look like the old layout (non-zero packed genre beside year, NGH slot at 0x28 empty) are detected, parsed correctly, and emit a UserWarning. Rewriting with edit or pack migrates them to the modern layout. Files with genre 0 under the old layout cannot be distinguished reliably and are read as modern.
MIT - see LICENSE.
