A modern replacement for the MS-DOS LOAD2.EXE / LOADW.EXE (Williams Electronics Games Inc., 1993–1995) toolchain. Reads .lod script files and .img container files from arcade hardware and outputs .tbl/.asm/.glo/.irw files targeting the TMS34020 GSP DMA2 graphics co-processor.
Supported games: Mortal Kombat 2, NBA Jam, NBA Jam Tournament Edition, Hangtime, Trog (/OLD mode), Narc (/OLD mode).
Smash TV / Total Carnage (
ROBOY.LOD): Theloadsmashbranch has ROM-verified dedup that makes SMASH TBLs pass. Switch to it withgit checkout loadsmash, build, and use/OLD /Hflags.
- Version History
- What It Does
- Build Instructions
- Usage
- Output Files
- The LOD/IMG Pipeline
- Test Suite
- Known Issues & Quirks
- Reverse Engineering
- Credits
- 15 pass, 7 fail — stable baseline. All modern (LOADW) tests pass except pre-existing MK7, BB7.
- GLO file rework — Single
g.glo_fphandle replaces dualglo_fp+main_glo_fp(fixes interleaved-write corruption — killed theS_SZline). Palette globals follow the current GLO (IMGTBL.GLO by default, redirected byGLO>), with no duplicate entries.ASM>directive resets GLO back to IMGTBL.GLO. FRM entries no longer emit.globl(game code uses TBL directly, no palette data). ENDMARKER removed from modern-mode GLO output. All MK IMGTBL.GLO/IMGPAL.ASM/IMGTBL.ASM now match refs and included in test suite. PAL>directive added — redirects palette color data output, symmetrical withGLO>andASM>. Opens new PAL file, writes header if empty. Hangtime worked around the lack of this with batch-filerencommands per-LOD./Aappend mode fixes —g.asm_fpopens with"a"(was"w", always truncated).g.pal_fpopens with"a+"(was"w+", truncated). IMGTBL.ASM wrapper opens with"a"and skips header. Correctly appends to existing TBL/PAL/GLO/ASM files across multiple LOD runs.- /OLD2 CARN 10/13 — Total Carnage: ORCUS, JET, SHAWN, RACKUP, HBASE, JEEP, TITLE, WARP, FINGRNT, PLYR, THROW, AKHBBOSS, MUTEXP all pass. JET/TEXT/TITLE have cosmetic SIZX diffs.
- memcmp-verified dedup — Byte-for-byte verification catches false checksum collisions. Per-mode: modern (checksum-only), /OLD1 (off by default), /OLD2 (ANIX-only check with memcmp).
- IRW header format standardized —
0x00640194magic at 0x20, data-only size at 0x30, uint16 LE checksum at 0x34 (trailing odd byte discarded),0x00000004at 0x38. Seeirw-header.md. - Consistent trailer —
.TEXT+0x1afor modern and /OLD2;.TEXTonly for /OLD1 (matches original LOAD.EXE 4.50 behavior). - Smash TV — Separate
loadsmashbranch with ROM-verified dedup (VHIT1R dedup against VHIT1 confirmed by u105+u89 ROM de-interleave). Switch:git checkout loadsmash. - BIGSREC.EXE compatibility — IRW header now accepted by BIGSREC tool (handles COFF-like header format).
- Full regression testing —
bash test.shafter every change ensures 15 pass baseline. - Trog: 12/15 — IMGTBL.GLO and BGNDTBL.GLO now pass. Motorola hex for BGNDTBL/BGNDPAL/BLKS sections. Tab separators (always
\t.word\tnot/OLD-specific spaces). Removed GLO blank lines. Skip.TEXTtrailer for ASM/GLO in/OLDmode. Insert.include BGNDTBL.GLOin IMGTBL.ASM at BBB handler start. BLKS hdr_idx uses LOADW word-slot indexing (di * 2). - NARC: 20/21 — NARCLOAF fixed. Duplicate IMG records: LOADW uses LAST match. NARCMUGS ref was generated by buggy LOAD.EXE (
while run > 0), accepted deviation. - POF>-based SAG+PAL combined/separate detection —
/OLDmode usesPOF>presence in LOD to select combined SAG+PAL (NARC) or separate lines (Trog).
- Trog: 9/9 TBLs + IMGPAL.ASM pass — BBB compression fixed (8bpp, no CMP, consistent sizx_a for 2nd pass), OUT_STRIDE minimum 1 for /OLD, file headers in IMGTBL.ASM, separate SAG/PAL
.longlines. - /OLD2 flag for LOAD.EXE 4.65 (Total Carnage) with
"4.65 9/3/91"IRW header. - NARC1: 18/21 — RLC encoder improvements.
- Fixed
/OLDmode IRW header format: stores***>base address at offset 0x2C. - Added
section_base_bittracking for continuous IRW across multiple***>sections (fixes Trog SAG with two address ranges). - Made
+METAsuffix cache global (static) matching LOAD.EXE behavior; stores final TBL SAG for correct cross-section cache hits. - Fixed PTTBL shared-entry pointer arithmetic (
pttblnum-2/pttblnum-3): uses signedptrdiff_toffset to avoid UB. Resolves BB5 CMP=1 cascade (3/7→6/7). - Added
cascade.mddocumenting remaining encoder issues,sentinel.mdfor PT0X investigation.
- Fixed old-format IMG palette offset computation (42-byte → 50-byte conversion was computing
pal_ofs = old_oset + n*92instead ofold_oset + n*42). Fixes palette lookups in WWF WrestleMania font files (TROGF15.IMG,TROGF7.IMG). - Made
write_image_tblfully dynamic: IHDR> directive now controls field order, grouping, and formatting exactly. Removed hardcoded special case forCTRLthat broke.wordline grouping whenPAL:Lwas skipped viaPOF>. - Added WWF test data (
MAIN.LOD,MISC.LOD) with LOADW reference outputs and verbose logs.
- Fixed
BBMUG16-bit checksum collisions causing false deduplication matches by adding asum2byte-sum dual-hash mechanism. - Fixed
BB6 CHEERbpp selection by properly scanning the stride-width pixel buffer (matching LOADW's handling of padding artifacts). - Fixed
BB7cascade failures by gating the PTTBLseq/scroffset skip to< v0x654and enforcing a minimum SIZX threshold of 10. - Added
-vand--versionCLI flags.
The Midway arcade toolchain stored game sprites and backgrounds in IMG container files — a format with palette records, image headers (position, size, animation anchors), and 8-bit indexed pixel data. A LOD script tells the loader which images to extract, how to compress them (zero-run, uncompressed, or raw movie footage), and where to place them in the GSP's memory space.
loadimg reads these inputs and produces:
| File | Content |
|---|---|
.TBL |
Assembly image tables — per-image SIZX, SIZY, SAG (bit offset), CTRL (DMA2 control word), palette reference, animation points |
IMGTBL.ASM |
Master table assembly (modern: .include-ing all per-game TBL files; /OLD: direct image entries) |
IMGPAL.ASM |
Colour palette data as .word arrays (RGB565) with .globl declarations in current GLO |
IMGTBL.GLO |
Global symbol declarations — palette globals (modern) or all image globals (/OLD mode). Redirected by GLO> directive |
.IRW |
Bit-packed compressed (or raw) image binary ready for DMA transfer |
BGNDTBL.ASM / BGNDPAL.ASM / BGNDEQU.H |
Background layer tables (from BBB> directive, background data, stages, levels.) |
The output is assembled by the TI TMS34010 toolchain (gspa / gsplnk) and GFX are burned into EPROMs for the arcade hardware.
Dependencies: C99 compiler, CMake ≥ 3.10. No external libraries (only libm on Linux).
mkdir build && cd build
cmake ../src
makeA toolchain file is provided:
mkdir build_win && cd build_win
cmake ../src -DCMAKE_TOOLCHAIN_FILE=../src/toolchain-mingw.cmake
makeNative Windows builds with MSVC also work (set CMAKE_C_STANDARD 99).
loadimg <lod_file> [flags]
| Flag | Effect |
|---|---|
/R[=dir] |
Headerless raw IRW — no 0x44-byte header (optionally to directory) |
/T[=dir] |
Build TBL/ASM/GLO table files (optionally to directory) |
/F[=dir] |
Build IRW binary (optionally to directory) |
/X |
Skip IRW generation (TBL only) |
/I[=dir] |
Source image directory for .IMG files |
/D[=dir] |
LOD file directory |
/V |
Verbose output |
/E |
Dual-banked image memory (ED adjustment) |
/P |
Pad images to 4-bit boundary (stride alignment) |
/L |
Align images to 16-bit boundaries |
/B |
Derive bpp from pixel data, not palette size |
/3 |
Limit LOD scales to 3 (full, half, quarter, no eighth) |
/A |
Append mode — open existing GLO/TBL/ASM/PAL files and append new entries instead of truncating |
/OLD |
Legacy LOAD.EXE 4.50 mode — old-style output for Narc, Trog (and Smash TV /ROBOY). No CTRL field, SAG+PAL on same .long line, RLC> run-length encoding. Use /H for Intel hex format. |
/OLD2 |
Legacy LOAD.EXE 4.65 mode — Total Carnage. Same as /OLD but uses IMG_STRIDE(w) for SIZX in TBL. Use /H for Intel hex format. |
/H |
Intel hex format (0XXXXH) instead of Motorola hex (>XXXX). Required for CARN and SMASH reference matching. |
Output filenames derive from the LOD base name (uppercased):
loadimg MK2MIL.LOD /P /T
→ MK2MILIMG.TBL, IMGTBL.ASM, IMGTBL.GLO, IMGPAL.ASM
loadimg MK2MIL.LOD /F
→ MK2MIL.IRW
.DATA
.word 2 ; scale count
IMAGE_NAME:
.word SIZX
.word SIZY
.long 0<SAG>H ; bit offset into IRW data section
.word 0<CTRL>H ; DMA2 control word
.long PALNAME ; palette label
.long 0<SAG_S1>H ; scale 1 SAG
.word 0<CTRL_S1>H ; scale 1 CTRL
| Offset | Size | Field |
|---|---|---|
0x00 |
8 | ASCII date string, NUL-padded (03/14/95) |
0x08 |
24 | Reserved (zero-filled) |
0x20 |
4 | Magic — always 0x00640194 (LE) |
0x24 |
8 | Reserved (zero-filled) |
0x2C |
4 | ROM address (LE) |
0x30 |
4 | Data size — payload byte count, header-exclusive (LE) |
0x34 |
4 | Checksum — sum of uint16 LE words in payload, truncated 32-bit; trailing odd byte discarded |
0x38 |
4 | Constant — always 0x00000004 |
0x3C |
2 | Type/flags |
0x3E |
6 | Reserved (zero-filled) |
0x44 |
— | Bit-packed image data (LSB-first) |
SAG values in the TBL are bit offsets from the start of the IRW data section (plus the base address from the ***> directive).
Bits [15:12] PIX — pixel size (0=8bpp, 1-7=bpp)
Bits [11:10] TM — trailing-zero multiplier (0=×1, 1=×2, 2=×4, 3=×8)
Bits [9:8] LM — leading-zero multiplier (0=×1, 1=×2, 2=×4, 3=×8)
Bit [7] CMP — 1=ZON compression, 0=ZOF uncompressed
Each compressed row: 1 header byte [trail_n:4][lead_n:4] + stored pixels at bpp bits each, LSB-first. The hardware skips lead_n × lm_mult leading zeros and trail_n × tm_mult trailing zeros per row.
-
LOD Parsing — Text script with directives (
ZON>,ZOF>,CON>,COF>,PPP>,XON>,ASM>,GLO>,PAL>,***>,FRM>,BBB>,--->) controlling compression mode, dedup, bpp, and image selection. -
IMG Loading — Binary container with
LIB_HDR(28 bytes),IMG_RECrecords (50 bytes each, 42 for old format),PAL_RECrecords (26 bytes), optional SEQ/SCR blocks, and PTTBL (point table) entries (40 bytes each). Pixel data is 8-bit indexed, row-stride aligned to 4 bytes. -
Two-Pass Encoding:
- Pass 1 (
scan_bpp): scans all--->image lists to determine global bpp (max pixel value), respectingPPP>override. - Pass 2 (
process_lod): for each image, analyzes the compression window (SIZX from PTTBL or image width), selects LM/TM multipliers via error-minimizing analysis (reverse-engineered fromFUN_1000_6f20), encodes rows, writes TBL entries and palette data, and handles dedup (CON>/COF>) andFRM>/BBB>directives.
- Pass 1 (
-
IRW Writing — Flushes the bit-packed header and data to disk.
Dimensions observed across the full dataset (sprites + backgrounds), excluding VDA development artifacts and !-prefixed special records:
| Category | Max Width | Max Height | Example |
|---|---|---|---|
| Sprite (IMG) | 414px | 255px | TRANSBAR.IMG (transition bar) |
| BDD background tile | 212px | 221px | TEAMSTAT.BDD (team stats) |
| BDB scrolling world | 2000px | 1500px | Playfield coordinate system |
Run make test from the build directory. Compares all TBL/ASM/GLO/IRW output against LOADW reference files.
cd build && make test| Test | Mode | Result | Notes |
|---|---|---|---|
| MK2MIL | ZON + ZOF | PASS (8/8) | + IMGTBL.ASM, IMGPAL.ASM, IMGTBL.GLO |
| MK3MIL | ZOF | PASS (8/8) | + globals |
| MK4MIL | ZON | PASS (9/9) | + globals |
| MK5MIL | ZON | PASS (10/10) | + globals |
| MK6MIL | ZON/ZOF | PASS (20/20) | + globals |
| MK7MIL | Mixed | FAIL (13/14) | All globals pass; pre-existing TBL failure |
| MK8MIL | FRM | PASS (4/4) | + globals |
| BB | ZOF+XON | PASS (2/2) | |
| BB2 | ZOF+XON | PASS (3/3) | |
| BB3 | ZOF+PT | PASS (2/2) | |
| BB4 | ZOF+XON | PASS (1/1) | |
| BB5 | Mixed | PASS (7/7) | |
| BB6 | Mixed | PASS (6/6) | |
| BB7 | Mixed | FAIL (15/16) | OUTDOOR LEAF8 dedup (unused image, inconsequential) |
| CARN | /OLD2 | PASS (13/13) | All TBLs pass with memcmp-verified dedup |
| ROBOY | /OLD | FAIL (0/6) | Smash TV — font atlas encoding order differs from reference |
| BB8 | XON | PASS (3/3) | |
| BBMUG | ZOF+XON | PASS (2/2) | |
| BBVDA | VDA | PASS (1/1) | |
| TROG | /OLD | FAIL (12/15) | 9 TBLs + IMGPAL.ASM + IMGTBL.GLO + BGNDTBL.GLO pass; BBB handler remaining |
| NARC1 | /OLD | FAIL (20/21) | NARCMUGS: NARCMUGS compresses better. (see RLC.md) |
| CARN | /OLD2 | FAIL (10/13) | 10 TBLs pass. JET/TEXT/TITLE cosmetic SIZX diffs. |
| MISC | Dual-bank | FAIL (18/21) | 18 TBLs pass; global files differ from ref |
| ROBOY | /OLD | FAIL (0/6) | Smash TV — use loadsmash branch for ROM-verified dedup |
Format: loadimg <LOD> /P /T for LOADW tests, loadimg ... /OLD /T for LOAD.EXE.
Reference files can be regenerated via make regen (requires DOSBox).
| Issue | Scope | Root Cause |
|---|---|---|
| IMGTBL.ASM TROGMOUTH/MOUTHBACK | Trog | Entries not in any source file |
| BGNDTBL.ASM HDRS SAG values | Trog | BDD images at different IRW positions vs LOADW |
| BGNDPAL.ASM missing FACEPALS | Trog | BBB handler missing .long palette aliases |
| BB7 OUTDOOR LEAF8 dedup | BB7 | LEAF8 unused image; ref has wrong dedup |
| NARC NARCMUGS SAG offset | Narc | Ref generated by buggy LOAD.EXE (while run > 0 loop). Accepted. Our output is slightly better compressed and accepted by the game. |
| ROBOY font atlas order | Smash | Font images encoded in LOD order but reference uses different order. Affects all 6 TBLs. |
| MISC global files differ | Hangtime | IMGTBL.ASM/IMGPAL.ASM/IMGTBL.GLO differ from ref (ref has inline data, tool generates wrapped includes) |
The Williams/Midway arcade toolchain evolved through several tool versions, each with different features and output formats. This project supports all of them.
The original tool, used for Narc (1988) and Trog (1990). Pre-DMA2, targets DMA1 hardware.
| Feature | Behavior |
|---|---|
| Compression | None (raw 8bpp, stride-aligned rows) |
| Sprite dedup | Off (no CON>/COF> support) |
| TBL format | SAG+PAL on same .long line, no CTRL field |
| SIZX in TBL | Raw rec->w (not stride-aligned) |
| RLC encoding | RLC> directive for 2bpp run-length (DMA1 4-color images) |
| RLC bug | while run > 0 emits malformed runs for remainders 1-3 (see RLC.md) |
| IRW magic [0x20] | 0x0064024B |
| Trailer | .TEXT only (no 0x1a EOF byte) |
| IRW version string | "4.50 4/27/90" |
Games: Narc (/OLD), Trog (/OLD)
A later build of 4.50 used for Smash TV / Total Carnage prototypes. Same base tool but with sprite checksum dedup added.
| Feature | Behavior |
|---|---|
| Sprite dedup | Checksum-based (ROM-confirmed: VHIT1R dedup against VHIT1) |
| Anix/aniy in dedup | Not checked — dedup purely by pixel content |
| SIZX in TBL | IMG_STRIDE(w) (stride-aligned) |
| IRW magic [0x20] | 0x0064024B |
Games: Smash TV (/OLD, loadsmash branch)
Updated for Total Carnage. Adds checksum dedup with ANIX comparison.
| Feature | Behavior |
|---|---|
| Sprite dedup | Checksum + memcmp + ANIX-only check |
| SIZX in TBL | IMG_STRIDE(w) (stride-aligned) |
| IRW magic [0x20] | 0x00640194 |
| Trailer | .TEXT + 0x1a\x0d\x0a\x1a |
| IRW version string | "4.65 9/3/91" |
Games: Total Carnage (/OLD2)
Two intermediate builds between LOAD.EXE 4.65 and LOADW. IRW files with these dates exist for NBA Jam / NBA Jam TE, but the original EXEs have not been found. Feature set likely transitional — probably DMA2-era with compression.
The final known tool, used for Mortal Kombat 2. Full DMA2 compression, CTRL words, point tables, and checksum dedup. This is the only modern-mode EXE we have (290 KB, Borland C++ 4.5), decompiled with Ghidra. The IRW date string "5/25/94" matches the binary timestamp.
| Feature | Behavior |
|---|---|
| Compression | ZON (zero-run) / ZOF (uncompressed), DMA2 hardware decode |
| Sprite dedup | Checksum-only (CON>/COF> toggles), no anix/aniy check |
| TBL format | Full: SIZX, SIZY, ANIX, ANIY, SAG, CTRL, PAL, PWRD1-3, PT3Y |
| Scales | Multi-scale encoding (full, ½, ¼, ⅛) via *N suffix |
| SIZX in TBL | OUT_STRIDE(w) (depends on /P flag) |
| IRW magic [0x20] | 0x00640194 |
| Trailer | .TEXT + 0x1a (single byte) |
| IRW version string | "03/14/95" |
Games: MK2, NBA Jam, NBA Jam TE, Hangtime
| Feature | LOAD 4.50 | LOAD 4.50v | LOAD 4.65 | LOADW |
|---|---|---|---|---|
| CLI flag | /OLD |
/OLD |
/OLD2 |
(none) |
| Build dates | 4/27/90 | ~1990 | 9/3/91 | 11/13/92, 11/11/93, 5/25/94 |
| Games | Narc, Trog | Smash TV | Total Carnage | NBA Jam, MK2 |
| EXE we have | ✗ | ✗ | ✗ | 5/25/94 (LOADW.EXE) |
| Sprite dedup | ✗ | ✓ (pixel) | ✓ (ANIX) | ✓ (checksum) |
| RLC encoding | ✓ | ✓ | ✗ | ✗ |
| ZON compression | ✗ | ✗ | ✗ | ✓ |
| CTRL/PWRD fields | ✗ | ✗ | ✗ | ✓ |
| SIZX stride | raw w |
IMG_STRIDE |
IMG_STRIDE |
OUT_STRIDE |
| IRW magic | 0x024B |
0x024B |
0x0194 |
0x0194 |
| IRW trailer | .TEXT |
.TEXT |
.TEXT+0x1a×2 |
.TEXT+0x1a |
| Test status | 20/21 | 0/6 (loadsmash) | 10/13 | ~63/66 |
| Function | Address | Purpose |
|---|---|---|
_packbits |
1000:5b64 |
Main compression — lead/trail analysis, row encoding |
FUN_1000_6f20 |
1000:6f20 |
Error-minimizing LM/TM multiplier selector |
_do_zcom |
100a:fa02 |
Zero-compression row writer |
_load_bits |
1000:737c |
Bitstream I/O |
_compute_bpp |
1000:35a1 |
Bits-per-pixel calculation |
_create_point_table |
1000:75e1 |
PT pair output for TBL |
FUN_1854_35fc |
1854:35fc |
Checksum computation (dedup) |
FUN_1854_1a49 |
1854:1a49 |
BBB background processor |
Extracted via strings from the binary:
_packbits,_do_zcom,_load_bits,_compute_bpp,_create_point_table_write_palette,_write_row,_write_tbl_do_sclpad,_do_pad,_dataword,_do_table,_ctrl_word_zero_pad,_do_align0,_do_cksum,_do_superbpp,_color_average
See ghidra.md for the full analysis guide and compression.md for the complete algorithm reference.
The DMA2 compression format is documented in DMA2.DOC (Keep Enterprises, January 1992, Rev 1.5) — the official hardware specification for the Williams/Midway TMS34010 DMA co-processor.
wmpstruc.inc— Midway assembly struct definitions for IMG/PTTBL/SEQSCRagents.md— Full IMG container format specification, TBL output, PT pair computation, implementation status, and all known bug fixesbbb.md/bdd.md— BLIMP background format (BDB/BDD) specificationirw-header.md— IRW binary file header specificationcompression.md— DMA2 ZON/ZOF compression algorithm, verified against Ghidra decompilation and LOADW verbose output
The original LOADIMG was created by Warren B. Davis around 1988 for Y-Unit hardware. Possibly other developers updated it for Wolf Unit/DMA2. Thank you! You've made our lives better with the tools that helped build games that excited and shaped our life during the 80's and 90's. Without you, this would not exist!
- Based on reverse-engineering of LOADW.EXE (Williams Electronics Games Inc., 1993–1995, Borland C++ 4.5)
- DMA2 hardware documentation: Keep Enterprises, Jan 1992
- Original struct definitions: Midway Manufacturing (
wmpstruc.inc,itimg.asm) - Test data extracted from Mortal Kombat 2, NBA Jam, NBA Jam Tournament Edition, and Hangtime arcade ROMs