RISC OS Build EnvironmentROBE

Appendix56Appendix V: TBAFS File Format

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:

OffsetSizeContents
04Magic TAFS.
44Root allocation value.
84Secondary header or version-related value.
124Directory header size.
164Reserved.
204Unused by current tooling; the local extractor does not read this word, and its purpose is not documented.
244Offset to the first root entry table.
284Root 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.

OffsetSizeContents
0x004Entry type: 1 for file, 2 for directory, &FFFFFFFF for end marker.
0x044RISC OS load address.
0x084RISC OS exec address.
0x0C4Uncompressed file size.
0x104Attributes or flags.
0x1420Null-terminated filename field.
0x2820Unused by current tooling; the local extractor does not read these bytes, and their purpose is not documented.
0x3C4Data 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 fieldMeaning
filetypeTaken from the stamped load address when the upper twelve bits indicate a RISC OS stamped file.
quin timestampBuilt from the low byte of the load address and the exec address when the file is stamped.
full_pathA host-side slash-separated path built recursively from parent directories.
full_riscos_pathA 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:

OffsetSizeContents
0x008Directory block header.
0x088 pairsRepeated (position, count) pairs for entry blocks.
0x08 + 8n8A 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:

TypeMeaning
0Raw or uncompressed data.
1HCT1 or CompMod compressed data.
2Squash-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:

ConstantMeaning
1F 9DLZW magic bytes.
12Maximum code width in bits.
256Clear code in block mode.
9Initial 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:

OffsetSizeContents
0x004Header word whose second byte gives the number of blocks.
0x044Expected to be zero.
0x088Unused by current tooling; the local extractor does not read these bytes, and their purpose is not documented.
0x104 per blockTable 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