This document describes the tileset archive format used by the Wasteland 1 files ALLHTDS1 and ALLHTDS2.
It covers:
The compression and tile post-processing used by these files are documented separately:
ALLHTDS1 and ALLHTDS2 use the same binary format. The only practical difference is which tilesets they contain.
All multi-byte integer fields are little-endian.
Each tile is a packed 4-bit image:
16 pixels16 pixels8 bytes8 * 16 = 128 bytes (0x80)An ALLHTDS file is a sequence of compressed tileset blocks concatenated until EOF.
There is:
Each block represents exactly one tileset.
The ALLHTDS files themselves do not contain an internal offset table, but the Wasteland 1 executable keeps external lookup tables for ALLHTDS1 and ALLHTDS2.
These tables allow direct random access to tilesets by ID without first walking the archive sequentially.
In the unpacked WL.EXE, the tables are stored at these file offsets:
0x18E1C (9 * u32)0x18E0A (9 * u16)Wasteland 1 uses global tileset IDs:
0..3 for ALLHTDS14..8 for ALLHTDS2The executable stores these cumulative start offsets:
| Tileset ID | File | Local offset | Cumulative offset |
|---|---|---|---|
0 |
ALLHTDS1 |
0x0000 |
0x0000 |
1 |
ALLHTDS1 |
0x1402 |
0x1402 |
2 |
ALLHTDS1 |
0x3EE8 |
0x3EE8 |
3 |
ALLHTDS1 |
0x69FC |
0x69FC |
4 |
ALLHTDS2 |
0x0000 |
0x8603 |
5 |
ALLHTDS2 |
0x222C |
0xA82F |
6 |
ALLHTDS2 |
0x3C97 |
0xC29A |
7 |
ALLHTDS2 |
0x5676 |
0xDC79 |
8 |
ALLHTDS2 |
0x70EB |
0xF6EE |
To get the local offset in ALLHTDS2 for tilesets >= 4 simply subtract the cumulative offset of tileset 4.
The executable also stores the compressed block sizes:
| Tileset ID | Compressed size |
|---|---|
0 |
0x1402 |
1 |
0x2AE6 |
2 |
0x2B14 |
3 |
0x1C07 |
4 |
0x222C |
5 |
0x1A6B |
6 |
0x19DF |
7 |
0x1A75 |
8 |
0x2853 |
These EXE tables are not required for parsing, but they are useful when implementing true random access.
Each tileset block has this layout:
| Offset | Type | Meaning |
|---|---|---|
+0x00 |
u32 |
Decoded payload size in bytes |
+0x04 |
char[3] |
ASCII signature, always "msq" |
+0x07 |
u8 |
Disk byte |
+0x08 |
bitstream | Huffman-coded payload |
Important details:
u32 size is the size after Huffman decodingIn the shipped Wasteland 1 tileset archives, the disk byte is constant per file:
ALLHTDS1 uses 0ALLHTDS2 uses 1The decoded block payload is just the raw tile data for one tileset.
There is:
The payload is therefore a flat concatenation of fixed-size tile records.
The number of tiles in a block is derived entirely from the decoded payload size:
tileCount = decodedSize / 128
So a valid block size must be a multiple of 128.
Each tile occupies exactly 128 consecutive bytes in the decoded payload.
The bytes of one tile are still vertical-XOR encoded. To obtain the final tile image, each 128-byte tile record must be vertical-XOR decoded independently with a row stride of 8 bytes.
After that step, the tile data is a packed 16x16 image:
If decoded is the fully Huffman-decoded payload, tile n starts at:
tileOffset = n * 128
and occupies:
decoded[tileOffset .. tileOffset + 127]
After vertical-XOR decoding with stride 8, the tile can be interpreted as 16 rows of 8 bytes each.
A parser can read one ALLHTDS file like this:
"msq" signature.u32.tileCount = decodedSize / 128.tileCount records of 128 bytes each.8.Useful consistency checks when implementing a reader:
"msq" signature128128 bytes long8 bytes per tile