python tools/quest.py _work/hero-quest
Twenty-four files, 43,744 bytes, 1,648 to 2,086 each, mean 1,822.7. Diffing them against each other before parsing any one of them gives the layout almost for free, because the same structure repeats twenty-four times with different contents.
0x0000 .. 0x01EE 494 bytes 26 x 19 grid of region ids
0x01EE .. 0x0200 18 bytes zero padding to 512
0x0200 .. 0x03EE 494 bytes 26 x 19 grid of wall and door flags
0x03EE .. 0x0400 18 bytes zero padding to 1024
0x0400 .. 0x0460 96 bytes fixed-size header: four 3-byte records, then zero
0x0460 .. ~0x0470 six 16-bit fields
~0x0470 .. ten-byte placement records, then three-byte ones
then the quest title and briefing, as text
Two 494-byte layers, each padded up to the next 512-byte boundary. 26 × 19 = 494 exactly, and 26 × 19 is the HeroQuest board.
Every byte of the first layer is either 0 or one region id, and the id at a given square is the same in every quest that uses it. A quest turns regions on and off; it does not move them. So taking the union across all twenty-four files reconstructs the whole board:
01 01 01 01 02 02 02 02 02 02 03 03 03 03 03 03 04 04 04 04 04 04 05 05 05 05
01 15 15 15 15 16 16 16 16 17 17 17 03 03 18 18 18 19 19 19 19 1A 1A 1A 1A 05
01 15 15 15 15 16 16 16 16 17 17 17 03 03 18 18 18 19 19 19 19 1A 1A 1A 1A 05
01 15 15 15 15 16 16 16 16 17 17 17 03 03 18 18 18 19 19 19 19 1A 1A 1A 1A 05
01 1B 1B 1B 1B 1C 1C 1C 1C 17 17 17 03 03 18 18 18 19 19 19 19 1A 1A 1A 1A 05
06 1B 1B 1B 1B 1C 1C 1C 1C 17 17 17 08 08 18 18 18 1D 1D 1D 1D 1E 1E 1E 1E 0A
06 1B 1B 1B 1B 1C 1C 1C 1C 07 08 08 08 08 08 08 09 1D 1D 1D 1D 1E 1E 1E 1E 0A
06 1B 1B 1B 1B 1C 1C 1C 1C 07 1F 1F 1F 1F 1F 1F 09 1D 1D 1D 1D 1E 1E 1E 1E 0A
06 1B 1B 1B 1B 1C 1C 1C 1C 07 1F 1F 1F 1F 1F 1F 09 1D 1D 1D 1D 1E 1E 1E 1E 0A
06 06 06 06 07 07 07 07 07 07 1F 1F 1F 1F 1F 1F 09 09 09 09 09 09 0A 0A 0A 0A
0B 20 20 20 20 21 21 22 22 0C 1F 1F 1F 1F 1F 1F 0E 23 23 23 23 24 24 24 24 0F
0B 20 20 20 20 21 21 22 22 0C 1F 1F 1F 1F 1F 1F 0E 23 23 23 23 24 24 24 24 0F
0B 20 20 20 20 21 21 22 22 0C 0D 0D 0D 0D 0D 0D 0E 23 23 23 23 24 24 24 24 0F
0B 20 20 20 20 26 26 26 26 27 27 27 0D 0D 28 28 28 28 23 23 23 24 24 24 24 0F
0B 25 25 25 25 26 26 26 26 27 27 27 0D 0D 28 28 28 28 29 29 29 2A 2A 2A 2A 0F
10 25 25 25 25 26 26 26 26 27 27 27 12 12 28 28 28 28 29 29 29 2A 2A 2A 2A 14
10 25 25 25 25 26 26 26 26 27 27 27 12 12 28 28 28 28 29 29 29 2A 2A 2A 2A 14
10 25 25 25 25 26 26 26 26 27 27 27 12 12 28 28 28 28 29 29 29 2A 2A 2A 2A 14
10 10 10 10 11 11 11 11 11 11 12 12 12 12 12 12 13 13 13 13 13 13 14 14 14 14
Forty-two regions, and zero unassigned squares. Every one of the 494 cells
belongs to exactly one region. Ids 0x01–0x14 (1–20) form the corridor: the
outer ring plus the two cross passages that divide the board. Ids 0x15–0x2A
(21–42) are the twenty-two interior rectangles, which is the room count.
There is exactly one square in the whole board where the twenty-four files
disagree: offset 483, column 15 of row 18, which is 0x12 in twenty-three
quests and 0x13 in one. It is on the bottom corridor row, on the boundary
between two corridor segments, and it is the sort of one-cell difference a hand
edit leaves behind.
Per quest, the number of regions in play ranges from 10 (quest 10, Castle of Mystery) to 33 (quests 19 and 22), and the number of live squares from 169 to 366 of 494. Quest 1, The Maze, uses 32 regions and 343 squares — the tutorial opens most of the board.
The second 494-byte grid is a per-square flag byte. Its census across quest 1:
0x00 230 0x40 92 0x80 114 0xC0 19
0x10 7 0x41 9 0x81 11 0x20 5
0x18 2 0x60 1 0x90 1 0x21 1
0x29 1 0x68 1
Two bits dominate and their geography settles what they mean. Row 0 is a solid
run of 0x80; column 0 is a solid run of 0x40; the right-hand column carries
0x41. So 0x80 is a wall on the square's north edge and 0x40 a wall on
its west edge, which is the standard way to store a grid's walls without
double-counting them. 0xC0 is a corner.
0x20 occurs only inside runs of 0x80, and 0x10 only inside runs of
0x40 or standing alone in the interior; reading them as doors in the north
and west wall respectively fits every occurrence in quest 1 and is the
obvious complement to the two wall bits. 0x08 and 0x01 also occur, in
combinations like 0x68, 0x29, 0x81, and are not identified here —
secret doors and the stairway are the two features the board has that are not
yet accounted for. See 12-open-questions.md.
At 0x0402, four three-byte records. In quest 1 they read
(00,00,1D) (19,00,32) (00,12,35) (19,12,4A)
0x19 = 25 and 0x12 = 18, which are exactly the maximum column and row of a
26 × 19 board: the four corners. In quest 10 the same four records read
(02,0F,1D) (03,10,2F) (03,11,35) (01,0F,47) — not corners, and the third byte
changes with them. So the layout is (column, row, id) and these four are a
per-quest fixture; the recurring third bytes 0x1D and 0x35 in both quests
suggest an id rather than a coordinate.
At 0x0462, six 16-bit values, two of which (0x000C and 0x000F) are the same
in every quest examined and look like counts.
From about 0x0470 the file is a run of ten-byte records, each beginning
0x81:
81 07 02 00 00 00 01 12 01 01
81 16 06 00 00 00 01 12 01 01
81 0A 03 00 00 00 01 10 05 05 (quest 10)
81 0D 08 00 00 00 01 11 00 00 (quest 10)
81 0B 0A 00 00 00 01 13 03 03 (quest 10)
Bytes 1 and 2 are a column and row in range; byte 7 takes the values
0x10–0x13; bytes 8 and 9 covary with byte 7 (0x12 → 01 01,
0x13 → 03 03, 0x10 → 04 04/05 05/06 06, 0x11 → 00 00). A type
with a size attached, placed on the board — furniture.
Then a run of three-byte records, (column, row, id). Quest 1:
0C 01 30 0D 01 30 0B 06 30 00 08 30 11 09 30 00 0A 30
09 0A 30 0E 0C 30 0C 11 30 0D 11 30 0A 12 30
0C 08 38 0C 09 39 0D 08 3A 0D 09 3B
Eleven of one id, then four consecutive ids 0x38 0x39 0x3A 0x3B on a 2 × 2
block of squares. Quest 10 does the same thing with a different monster id
and a different 2 × 2 block:
01 10 38 01 11 39 02 10 3A 02 11 3B
0E 04 5D 09 02 37 10 02 37 16 04 37 04 06 37 0F 09 37 ...
Four consecutive ids on four adjacent squares, in every quest, is the party:
Barbarian, Dwarf, Elf and Wizard, standing in a two-by-two block at the
starting position. Everything else in that list is monsters — 0x30 in quest 1,
0x37 in quest 10, with 0x5D appearing once as the quest objective.
The three-byte records are followed immediately by the same 0x11 row col
cursor escape the language files use, and then by plain uppercase English:
QUEST1 0x04E1 11 02 10 "THE MAZE" 11 04 05 "THIS WILL BE YOUR FINAL TEST..."
QUEST10 0x054F 11 03 0B "CASTLE OF MYSTERY" 11 05 05 "LONG AGO A..."
Not compressed, not indexed, not localised. 8,500 printable characters across the twenty-four files, 227 to 547 per quest.
There is only one set of twenty-four quest files, so the briefings are English only, in every copy of this build, whatever language the player picks. That is the resolution of the contradiction the file listing raises, and it is discussed further in 06-language-files.md.
The twenty-four titles, as stored inside the quest files:
| title | file | briefing chars | |
|---|---|---|---|
| 1 | THE MAZE | 1,656 | 360 |
| 2 | THE RESCUE OF SIR RAGNAR | 1,792 | 393 |
| 3 | LAIR OF THE ORC WARLORD | 1,651 | 268 |
| 4 | PRINCE MAGNUS' GOLD | 1,660 | 296 |
| 5 | MELAR'S MAZE | 1,878 | 465 |
| 6 | LEGACY OF THE ORC WARLORD | 1,911 | 438 |
| 7 | THE STONE HUNTER | 1,688 | 300 |
| 8 | THE FIRE MAGE | 1,788 | 388 |
| 9 | RACE AGAINST TIME | 1,855 | 412 |
| 10 | CASTLE OF MYSTERY | 1,743 | 343 |
| 11 | BASTION OF CHAOS | 2,086 | 503 |
| 12 | BARAK TOR, HOME OF THE WITCHLORD | 1,946 | 547 |
| 13 | QUEST FOR THE SPIRIT BLADE | 1,975 | 480 |
| 14 | RETURN TO BARAK TOR | 1,773 | 357 |
| 15 | THE GATE OF DOOM | 1,926 | 394 |
| 16 | THE COLD HALLS | 1,648 | 227 |
| 17 | THE SILENT PASSAGES | 1,845 | 280 |
| 18 | HALLS OF VISION | 1,784 | 293 |
| 19 | THE GATE OF BELLTHOR | 1,898 | 326 |
| 20 | HALLS OF THE DEAD | 1,805 | 236 |
| 21 | THE FORGOTTEN LEGION | 1,831 | 329 |
| 22 | THE FORBIDDEN CITY | 1,885 | 369 |
| 23 | THE LAST GATE | 1,896 | 255 |
| 24 | THE COURT OF THE WITCH LORD | 1,824 | 241 |
3,421 bytes, and it is not a twenty-fifth quest. It is the quest-selection
screen: a twenty-byte header, ten 16-bit offsets, and then the twenty-four
titles with 0x11 row col positioning — five times over, once per language,
plus the two navigation strings for each.
python tools/strdump.py _work/hero-quest/EQUESTS.BIN --min 4
0x001E THE MAZE 0x028E LE LABYRINTHE
0x0031 THE RESCUE OF SIR RAGNAR 0x029F SAUVETAGE DE SIR RAGNARD
... 0x0391 BROUETTE DU SORCIER
0x025E PREVIOUS PAGE 0x03DB PAGE SUIVANTE
0x026F EXIT TO MAIN MENU 0x03EC ALLEZ AU MENU PRINCIPAL
0x052D DAS LABYRINTH 0x05D4 DER STEINJaGER
0x053E DIE RETTUNG VON SIR RAGNAR 0x0640 HuGELGRAB DES HEXENHERRN
0x055C LAGER DES KRIEGSHERRN DER ORCS 0x06A2 NaCHSTE SEITE
0x059B MELARS LABYRINTH 0x06B3 AUSGANG ZUM HAUPTMENu
PREVIOUS PAGE / PAGE SUIVANTE / NaCHSTE SEITE and EXIT TO MAIN MENU
tell you the selection screen is paged; the ten 16-bit offsets in the header
are the page starts.
The German entries are the cleanest confirmation of the accent mapping in
06-language-files.md, because this file is not
compressed: STEINJaGER, HuGELGRAB, NaCHSTE, RuCKKEHR, HAUPTMENu —
a = ä, u = ü, exactly as the font predicts.
And there is a discrepancy worth keeping. Quest 12's title inside
QUEST12.BIN is BARAK TOR, HOME OF THE WITCHLORD; the same quest in the
English section of EQUESTS.BIN is BARROW OF THE WITCHLORD. The German
index calls it HuGELGRAB DES HEXENHERRN — burial mound of the witch lord —
which follows BARROW, and the French calls it BROUETTE DU SORCIER, which is
wheelbarrow of the sorcerer: somebody translated the wrong sense of
"barrow". The mistranslation is only possible from the EQUESTS.BIN wording,
so the index was localised from the index and not from the quest files, and the
two English titles were never reconciled.