Overview
TBAFS is an older RISC OS archive format created by TBA Software and commonly stored in files of type &B21. The build environment ships a Python extractor which lists or restores these archives through riscos-tbafs.
This appendix summarises the on-disc file format as documented by the shipped extractor and by reverse-engineering notes for the original read-only TBAFS module.
The shipped extractor code is detailed enough to act as a practical format reference for the supported paths, so this appendix describes the archive at the same level: header, entry tables, directory indirection, block records, and the Squash-based compression path.
Archive structure
Archive header
The header is at least 32 bytes long, beginning with the ASCII magic TAFS. The second header word is a version check, expected to be &000000C8.
The header fields are:
| Offset | Size | Contents |
|---|---|---|
| 0 | 4 | Magic TAFS. |
| 4 | 4 | Root allocation value. |
| 8 | 4 | Secondary header or version-related value. |
| 12 | 4 | Directory header size. |
| 16 | 4 | Reserved. |
| 20 | 4 | Unused by current tooling; the local extractor does not read this word, and its purpose is not documented. |
| 24 | 4 | Offset to the first root entry table. |
| 28 | 4 | Root entry count. |
The first four words are named root_alloc, unknown1, dir_header_size, and a reserved word. The root entry-table offset and root entry count follow at offsets 24 and 28.
Directory entry records
Directory entries are fixed-size, 64 bytes each.
| Offset | Size | Contents |
|---|---|---|
| 0x00 | 4 | Entry type: 1 for file, 2 for directory, &FFFFFFFF for end marker. |
| 0x04 | 4 | RISC OS load address. |
| 0x08 | 4 | RISC OS exec address. |
| 0x0C | 4 | Uncompressed file size. |
| 0x10 | 4 | Attributes or flags. |
| 0x14 | 20 | Null-terminated filename field. |
| 0x28 | 20 | Unused by current tooling; the local extractor does not read these bytes, and their purpose is not documented. |
| 0x3C | 4 | Data block position or directory block-table position. |
The filename comparison starts at offset &14 and the data block position is at offset &3C.
Several semantic values are derived directly from those fields:
| Derived field | Meaning |
|---|---|
| filetype | Taken from the stamped load address when the upper twelve bits indicate a RISC OS stamped file. |
| quin timestamp | Built from the low byte of the load address and the exec address when the file is stamped. |
| full_path | A host-side slash-separated path built recursively from parent directories. |
| full_riscos_path | A RISC OS dotted path built from the original RISC OS names. |
Directory block tables
Directory entries for subdirectories are located through a block-table structure rather than by a single contiguous root table. That structure is:
| Offset | Size | Contents |
|---|---|---|
| 0x00 | 8 | Directory block header. |
| 0x08 | 8 pairs | Repeated (position, count) pairs for entry blocks. |
| 0x08 + 8n | 8 | A pair with position 0 terminates the table. |
Each entry block contains up to 16 directory entries.
At the root level, a single contiguous entry table is scanned instead, starting at the header's root entry-table offset and stopping either at the end marker or after the root entry-count limit.
Data blocks and compression types
File data is read through a small block header whose first word packs the compression type into the high byte and the uncompressed size into the lower 24 bits:
(compression_type << 24) | size_in_bytes
The documented compression types are:
| Type | Meaning |
|---|---|
| 0 | Raw or uncompressed data. |
| 1 | HCT1 or CompMod compressed data. |
| 2 | Squash-compressed data, using 12-bit LZW. |
For raw blocks, the payload begins immediately after the first word. For Squash-compressed blocks, the second word gives the compressed size and the compressed payload begins after an 8-byte block header.
The local extractor does not yet implement the HCT1 path, but it reports the unsupported path explicitly, so archives using compression type 1 are recognised as valid format variants even though they cannot yet be unpacked in this environment.
Squash-compressed TBAFS blocks
Squash-compressed TBAFS data is compatible with 12-bit LZW in the style of Unix compress -b 12. The compressed stream begins with the normal LZW magic bytes 1F 9D and then a flags byte. The original module handles Squash blocks by skipping the 4-byte size header and then passing the compressed data to Squash_Decompress.
The decompressor constants are:
| Constant | Meaning |
|---|---|
| 1F 9D | LZW magic bytes. |
| 12 | Maximum code width in bits. |
| 256 | Clear code in block mode. |
| 9 | Initial code width in bits. |
Multi-block files
Large files may be split across multiple compressed or raw blocks. The multi-block index has mode byte value 2, with its block-offset table beginning at offset &10.
The multi-block index begins with:
| Offset | Size | Contents |
|---|---|---|
| 0x00 | 4 | Header word whose second byte gives the number of blocks. |
| 0x04 | 4 | Expected to be zero. |
| 0x08 | 8 | Unused by current tooling; the local extractor does not read these bytes, and their purpose is not documented. |
| 0x10 | 4 per block | Table of block offsets. |
The default per-block size for large-file handling is 32KB.
Such files are resolved by reading block offsets one by one from the table until enough uncompressed data has been gathered or a zero block offset terminates the table.
Inspection and extraction workflow
Inspecting and extracting a TBAFS archive
These are the normal inspection commands for a TBAFS archive in the build environment.
riscos-tbafs info archive,b21
riscos-tbafs list -v archive,b21
riscos-tbafs extract -o restored archive,b21