This document describes the executable metadata stored in Wasteland 1 WL.EXE.
It covers:
The WL.EXE file shipped with the game is compressed with EXEPACK.
The offsets documented here refer to the unpacked executable image, not to the packed on-disk WL.EXE file.
Expected unpacked size:
169824 bytes
To reproduce the offsets in this document, first unpack WL.EXE with an EXEPACK tool such as exepack.
All offsets in this document are absolute file offsets within the unpacked executable image.
The executable contains several compressed string tables using the same format as the map string sections.
Each string table begins with:
| Field | Meaning |
|---|---|
char[60] |
Dictionary |
u16 firstPointer |
First group pointer; also determines pointer-table length |
u16[...] |
Remaining group pointers |
| bitstream | 5-bit encoded string groups |
The number of group pointers is:
numGroups = firstPointer / 2
Each group pointer is relative to the byte immediately after the 60-byte dictionary.
Each referenced group can contain up to 4 strings.
The 5-bit codes use these special values:
0x1E: uppercase the next character0x1F: select the upper half of the dictionary for the next characterNUL: end of stringThe executable contains at least these string tables:
| Name | Absolute offset | Size in bytes | Observed string count | Purpose |
|---|---|---|---|---|
| Intro strings | 0x17723 |
527 |
18 |
Title and intro text |
| Message strings | 0x17B5E |
1661 |
107 |
General messages |
| Inventory strings | 0x18290 |
1845 |
170 |
Inventory, skills, attributes |
| Character creation strings | 0x19E6B |
210 |
8 |
Character creation |
| Ending strings | 0x1A1AE |
661 |
10 |
Base Cochise ending text |
| Promotion strings | 0x1A642 |
1136 |
63 |
Promotion text |
| Library strings | 0x1AAEC |
277 |
11 |
Library text |
| Shop strings | 0x1AC18 |
229 |
10 |
Shop text |
| Infirmary strings | 0x1AD0D |
369 |
25 |
Infirmary text |
The sizes above are the raw encoded byte spans inside the executable, not decoded character counts.
The executable also contains the tables needed to parse GAME1 and GAME2.
Location:
0x18C9A
Format:
u32_le mapOffsets[42]
Interpretation:
0..19 are the 20 map offsets in GAME120..41 are the 22 map offsets in GAME2Indexing:
GAME1 map N -> mapOffsets[N]
GAME2 map N -> mapOffsets[20 + N]
Location:
0x18D42
Format:
u16_le tileMapOffsets[50]
This table is indexed by location index, not directly by (disk, map).
Each value is the offset, within a map block, of the 4-byte tileMapDecodedSize field documented in Map Format.
Location:
0x18EE9
Format:
u8 locationMapping[50]
Each byte encodes a (disk, map) pair:
0..5: map index6..7: encoded disk selectorObserved disk encoding:
2 means GAME11 means GAME20 means invalid / unused location slotSo the encoded value is:
locationCode = (diskCode << 6) | mapIndex
where:
diskCode = 2 for GAME1diskCode = 1 for GAME2This encoding is odd but intentional.
Location:
0x18F3C
Format:
u8 mapSizes[50]
This table is indexed by location index, not directly by (disk, map).
Observed values are the map edge length:
3264Location:
0x18FBC
Format:
u8 sharedLocations[125]
This table is indexed by the lower 7 bits of special transition target locations 128..252.
The loader uses it to translate a special building location into a normal shared location before consulting the normal location-to-disk/map table.
In other words:
if targetLocation >= 128 && targetLocation <= 252:
sharedLocation = sharedLocations[targetLocation - 128]
Observed values in the shipped executable:
0..63 (128..191) are all 564..124 (192..252) are all 11These resolve to the shared interior maps:
5 -> GAME1 map 511 -> GAME2 map 1The executable stores the savegame offsets as split low/high words rather than one packed u32.
| Field | Absolute offset | Type |
|---|---|---|
| Low word | 0x880C |
u16_le |
| High word | 0x880F |
u16_le |
Combined as:
savegameOffset0 = low + (high << 16)
Observed value:
0x253C5
| Field | Absolute offset | Type |
|---|---|---|
| Low word | 0x8819 |
u16_le |
| High word | 0x881C |
u16_le |
Combined as:
savegameOffset1 = low + (high << 16)
Observed value:
0x28BC7
Location:
0x8820
Format:
u16_le savegameSize
Observed value:
0x1206
Location:
0x18E40
Format:
u16_le shopListRelOffsets[4]
Observed values in the shipped executable:
0x0000, 0x02FE, 0x05FC, 0x08FA
Interpretation:
GAME1 savegame blockGAME1GAME1 and does not correspond to a real shop block in the shipped dataGAME2 shop list; that block starts immediately after the GAME2 savegame blockThe first 35 decoded inventory strings are the skill names in skill-ID order:
inventoryStrings[1..35] <-> skill IDs 0x01..0x23
The executable stores the remaining skill metadata in a table at offset 0x18a40.
Format:
struct SkillMeta {
u8 iqAndBaseCost;
u8 linkedAttributeOffset;
}
SkillMeta skillMeta[36];
Notes:
0 is a dummy entry1..35 correspond to the skills in inventoryStrings[1..35]iqAndBaseCost >> 3 gives the minimum IQiqAndBaseCost & 0x07 gives the base skill-point costlinkedAttributeOffset is a direct offset into the character record, not a small enumLinked-attribute offset mapping:
| Offset | Attribute |
|---|---|
0x0E |
Strength |
0x0F |
IQ |
0x10 |
Luck |
0x11 |
Speed |
0x12 |
Agility |
0x13 |
Dexterity |
0x14 |
Charisma |
The tables described here are the reason GAME1 and GAME2 are not standalone self-describing archives.
For the container-level view, see:
For the individual block formats, see: