A small, dependency-light validation suite and blueprint for games that ship assets in an LNK4 container (e.g. Code_18 — the title from the Snailium LNK4 write-up). The other titles validated using the suite include the Xbox 360 version of 11eyes CrossOver and Steins;Gate.
It answers two questions about a container:
- Structure — does this
.dathave the entry count and outlier layout we expect for game X? - Decode correctness — does
libmsxcaturn every compressed entry into a valid payload (TIMG texture / binary blob)?
The whole project is plain Python + the libmsxca native decoder; no Windows-only
tools (xbdecompress.exe) required. The only real requirement in a Python environment is Pillow (for the image converter).
lnk4-validate/
check_game_profile.py # CLI: check / init / extract
lnk4lib.py # LNK4 TOC parse + libmsxca classify helpers
libmsxca/ # bundled copy of the libmsxca decoder (source + built libs)
src/ CMakeLists.txt BUILDING.md README.md ...
build/libmsxca.so # prebuilt for Linux x64
runtimes/win-x64/native/msxca.dll # prebuilt for Windows x64
timg_converter/ # bundled TIMG <-> PNG converter (convert_timg.py)
game_profiles/ # one JSON per known game
elevene.json
sg.json
code18.json
README.md
- 4-byte magic
LNK4, then a little-endiandata_ptr(offset where blobs start). - Table of 8-byte entries
(offset_blocks, length_blocks), little-endian, until a(0, 0)terminator.abs_offset = data_ptr + offset_blocks * 2048length = length_blocks * 1024
- Each entry blob is either:
- an LZXNATIVE compressed blob (magic
0FF512EE) → decode withlibmsxca, usually a TIMG texture, or - raw bytes already (e.g. an uncompressed PNG).
- an LZXNATIVE compressed blob (magic
A profile is intentionally tiny — it records the parts that vary per game and assumes everything else is the standard "compressed TIMG texture" case:
{
"game_id": "code18",
"display_name": "Code_18",
"source": "Snailium LNK4 container article",
"container": {
"format": "LNK4",
"file": "to-test/code18-testdata/code18-lnk4/system.dat",
"entry_count": 25,
"size_bytes": 17426432
},
"outliers": {
"always_png": [19],
"binary_file": [24]
},
"image_entries": 23
}always_png— uncompressed entries that are already PNG (not in the container's compressed stream).binary_file— compressed entries whose decoded payload is not a TIMG (e.g. a not-yet known purpose blob saved as.decbycrosslnk4).- Everything else (
entry_count - len(always_png) - len(binary_file)) is expected to be a compressed TIMG texture.
file / size_bytes are resolved relative to LNK4_VALIDATE_ROOT (defaults to
the parent of this folder — the repo root).
A copy of libmsxca (cross-platform Xbox Compression / LZXNATIVE
decompressor, wrapping libmspack's lzxd) is bundled under libmsxca/ so this
project is self-contained — no Windows-only tools and no dependency on the rest
of the repo. A Linux x64 build is prebuilt at libmsxca/build/libmsxca.so and
is used automatically.
To rebuild (e.g. for macOS/Windows, or after editing the source):
# Linux / macOS
cmake -S libmsxca -B libmsxca/build
cmake --build libmsxca/build
# Windows (cross-compile from Linux with MinGW-w64)
cmake -S libmsxca -B libmsxca/build-win64 \
-DCMAKE_SYSTEM_NAME=Windows -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc
cmake --build libmsxca/build-win64
# -> libmsxca.dll ; drop it at libmsxca/runtimes/win-x64/native/msxca.dlllnk4lib resolves the native lib automatically per platform: the bundled
build/libmsxca.so (Linux), runtimes/win-x64/native/msxca.dll (Windows), or
LIBMSXCA_SO to override.
The loader searches (lnk4lib._candidate_libs) in this order:
LIBMSXCA_SOenv var (if set)libmsxca/build/inside this project (preferred)
Note: the bundled copy builds with default symbol visibility (no
-fvisibility=hidden) somsxca_decompress/msxca_freeare exported on ELF without extra annotations.
A convert_timg.py converter is bundled under
timg_converter/. It converts the decompressed TIMG payloads to viewable
PNGs (and back), interpreting the 8-byte little-endian header
(width, height, depth, flag; depth 8 = grayscale, 32 = ARGB) and the raw
pixel data. extract uses it to emit <n>.png alongside each <n>.timg.
This is preferred over crosslnk4/lib/timg.py: it is a standalone, portable tool
(both produce byte-identical PNGs) and keeps the project free of the Windows-only
crosslnk4 dependency.
# Validate a container against its profile (uses profile's 'file' by default)
python check_game_profile.py check code18
python check_game_profile.py check elevene testdata/lnk4/elevene-system.dat
# Auto-generate a profile stub for a NEW game, then review/tweak it
python check_game_profile.py init path/to/newgame-system.dat newgame
# -> writes game_profiles/newgame.json
# Dump every entry (raw .timg for textures, .png viewable, .bin for blobs)
python check_game_profile.py extract path/to/system.dat out_dircheck exits non-zero on any mismatch (entry count, outlier indices, image count,
or file size) — handy as a CI / regression gate.
- Drop the
system.datsomewhere under the repo. python check_game_profile.py init <dat> <game_id>→ writes a profile with the outliers auto-detected bylibmsxca.- Open the generated
game_profiles/<game_id>.jsonand fixdisplay_name/source. - (Optional) Extract and check-by-look the
.pngs /.bins to confirm the outlier classification is right. python check_game_profile.py check <game_id>should now PASS — and will catch future drift in that game's container.
- The
is_timgcheck is a heuristic (header dimensions + depth 8/32 + size fit). If a game ships textures with other depths, extendlnk4lib.is_timg. Validate decode correctness with raw.streamor.binknown-correct references from genuinexbdecompress.exeoutput when available (as done forelevene), or rely onlibmsxca's proven byte-identical output for the same LZXNATIVE format. libmsxcais the cross-platform decoder (Linux.soneeded); on Windows/macOS point anLIBMSXCA_SOenvironment variable atmsxca.dll/libmsxca.dylib.- OpenCode's Zen models were used during the development and testing phase of this project.
convert_timg.pyis based on LNK4_AIO / crosslnk4'stimg.py, made by Timo654.