All the text of a help file is in the |TOPIC internal file. It is a sequence
of fixed-size blocks. Read in order, the blocks hold one long stream of records:
TOPICLINKs, which may cross from one block into the next.
Blocks
A block is 2,048 or 4,096 bytes, as |SYSTEM says. It starts with a 12-byte header, and the rest is data, stored or LZ77-compressed (compression).
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0 | 4 | u32 | LastTopicLink | The position of the last TOPICLINK of the block before, or −1 |
| 4 | 4 | u32 | FirstTopicLink | The position of the first TOPICLINK that starts in this block |
| 8 | 4 | u32 | LastTopicHeader | The position of the last topic header before the end of this block |
A compressed block expands to at most 16,384 bytes. Blocks do not always fill it: when a block stops short, a record that crosses it continues at the first byte of the next block’s data. Reading the records means joining each block’s real output end to end.
Positions
Records are addressed by a TOPICPOS. Windows 3.1 and later divide it by 16,384 whatever the block size:
position − 12 = block number × 16,384 + offset into the block's data
The first TOPICLINK of a file is at TOPICPOS 12. A
TOPICOFFSET (used by keywords, contexts and the
contents topic) counts differently:
offset = block number × 0x8000 + characters so far in that block
The character count takes each topic record’s TopicLength in turn. Links to
topics use this offset (or a hash that |CONTEXT turns into one).
Windows 3.0 files (Minor 16 and below) number the bytes of the data, counting a 12-byte
block header for every 2,048-byte block. The first record is at position 12, and the
record after one at position p is at p plus its
NextBlock field, a distance in those counted bytes.
TOPICLINK
Every record starts with this 21-byte header.
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0 | 4 | u32 | BlockSize | The size of this record: header, LinkData1 and the stored LinkData2 |
| 4 | 4 | u32 | DataLen2 | The length of LinkData2 once decompressed |
| 8 | 4 | u32 | PrevBlock | The position of the previous record (Windows 3.0: distance back) |
| 12 | 4 | u32 | NextBlock | The position of the next record, or −1 at the end (Windows 3.0: distance forward, 0 at the end) |
| 16 | 4 | u32 | DataLen1 | Where LinkData2 starts, counted from the start of this header |
| 20 | 1 | u8 | RecordType | What the record is |
LinkData1 runs from byte 21 to DataLen1, and
LinkData2 from DataLen1 to BlockSize.
| RecordType | Meaning |
|---|---|
| 1 | Displayable text, Windows 3.0 |
| 2 | A topic header |
| 0x20 | Displayable text, Windows 3.1 and later |
| 0x23 | A table, Windows 3.1 and later |
Text records are explained on their own page.
LinkData2 is compressed when DataLen2 is greater than the bytes
stored (BlockSize − DataLen1), and stored as it is otherwise.
Topic header
Record type 2 starts a topic. Its LinkData2 is NUL-terminated strings: the
title, then any macros run on entry. In Windows 3.1 and later LinkData1 holds
these fields. Windows 3.0’s shorter header holds the topic’s size and the numbers of its
neighbours in the browse sequence.
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0 | 4 | u32 | BlockSize | The size of the topic, including its records |
| 4 | 4 | u32 | BrowseBck | The previous topic in the browse sequence (TOPICOFFSET), or −1 |
| 8 | 4 | u32 | BrowseFor | The next topic in the browse sequence, or −1 |
| 12 | 4 | u32 | TopicNum | The topic’s number |
| 16 | 4 | u32 | NonScroll | Position of the non-scrolling region, or −1 |
| 20 | 4 | u32 | Scroll | Position of the scrolling region |
| 24 | 4 | u32 | NextTopic | Position of the next topic header |