Table of Contents

Bodu.IO.Biff - Record reference

The catalogue of every record the codec decodes and writes, with its identifier, the differences between the BIFF5 and BIFF8 layouts, the decoded type, and the reader accessor and writer method that pair with it. Read Core concepts first for the vocabulary (record, substream, CONTINUE, code page, RK); this page is the lookup table you return to while coding against the API.

Every record starts with the four-byte BiffRecordHeader - a 16-bit identifier and a 16-bit payload length. The tables below describe the payload only. A cell prefix is the six bytes that open every cell record: the zero-based row, the zero-based column, and the extended-format (XF) index, each 16 bits. A string is the version's text structure described under Strings: a BIFF8 Unicode string or a BIFF5 code-page byte string, with an 8-bit or 16-bit length prefix as noted.

Workbook globals

Records that appear in the first substream and describe the workbook as a whole.

Record Identifier Payload Decoded type Reader Writer
BOF 0x0809 Version marker, substream type, build, year - 8 bytes in BIFF5; BIFF8 adds file-history flags and the lowest saving version for 16. Any payload of at least 4 bytes is decoded, with zero for absent fields. BiffBofRecord GetBof() WriteBof
EOF 0x000A Empty. Closes the substream the last BOF opened. - (Eof) RecordType WriteEof()
CODEPAGE 0x0042 One 16-bit value. Reading it updates CodePage; the private markers 0x8000 (Apple Roman) and 0x8001 (legacy ANSI) are normalized to Windows code pages. BiffCodePageRecord GetCodePage() WriteCodePage
DATEMODE 0x0022 One 16-bit value; non-zero selects the 1904 date system. BiffDateModeRecord GetDateMode() WriteDateMode
FILEPASS 0x002F BIFF5: the XOR key and hash (4 bytes). BIFF8: a type word (BiffEncryptionType), then the XOR pair for Xor or an uninterpreted RC4 header. The codec reports the scheme; it does not decrypt. BiffFilePassRecord GetFilePass() WriteRecord (raw)
FONT 0x0031 14 fixed bytes (height, attribute flags, color index, weight, escapement, underline, family, character set, reserved) then the face name as an 8-bit-length string. BiffFontRecord GetFont() WriteFont
FORMAT 0x041E The 16-bit format index, then the code - a 16-bit-length Unicode string in BIFF8, an 8-bit-length byte string in BIFF5. BiffFormatRecord GetFormat() WriteFormat
XF 0x00E0 16 bytes in BIFF5, 20 in BIFF8. The codec decodes the first six - font index, format index, and the type-and-protection word - and tolerates a record carrying only the two indices. BiffXfRecord GetXf() WriteXf
BOUNDSHEET 0x0085 The 32-bit absolute stream offset of the sheet's BOF, a visibility byte (BiffSheetState, low two bits), a sheet-type byte (BiffSheetType), then the name as an 8-bit-length string. BiffBoundSheetRecord GetBoundSheet() WriteBoundSheet
SST 0x00FC BIFF8 only. The total reference count and unique string count (two 32-bit values), then the strings, which overflow into CONTINUE records by the rules under Shared string table. BiffSstHeader (counts) and BiffSstReader (strings) GetSstHeader(), then new BiffSstReader(ref reader) WriteSst
CONTINUE 0x003C The overflow of the record before it. Reported as a physical record with IsContinuation set. - TryReadContinuation or BiffSstReader WriteContinuedRecord or WriteSst

Sheet substream

Records that describe a sheet's extent and rows.

Record Identifier Payload Decoded type Reader Writer
DIMENSIONS 0x0200 The used range as first row, one-past-last row, first column, one-past-last column. Rows are 16-bit in BIFF5 (10 bytes with the reserved word) and 32-bit in BIFF8 (14 bytes); the version selects the layout. BiffDimensionsRecord GetDimensions() WriteDimensions
ROW 0x0208 16 bytes in both versions: row, first column, one-past-last column, the height field, two reserved words, the option flags, and the format field. Derived properties mask the height, outline level, hidden and custom-height flags, and the 12-bit XF index. BiffRowRecord GetRow() WriteRow

Cell records

Every cell record opens with the six-byte cell prefix. The layouts are identical in BIFF5 and BIFF8 except where the value is text.

Record Identifier Payload after the prefix Decoded type Reader Writer
NUMBER 0x0203 A little-endian IEEE 754 double (14 bytes total). BiffNumberRecord GetNumber() WriteNumber
RK 0x027E A 32-bit RK value (10 bytes total), decoded by BiffRk. BiffRkRecord GetRk() WriteRk
MULRK 0x00BD No prefix: the row and first column, then six bytes per cell (XF index and RK value), then the declared last column. Cells are exposed by index as BiffRkCell values. BiffMulRkRecord GetMulRk() WriteMulRk
BLANK 0x0201 Nothing (6 bytes total): a formatted cell with no value. BiffBlankRecord GetBlank() WriteBlank
MULBLANK 0x00BE No prefix: the row and first column, then a 16-bit XF index per cell, then the declared last column. BiffMulBlankRecord GetMulBlank() WriteMulBlank
BOOLERR 0x0205 A value byte and a kind byte (8 bytes total): kind zero is a boolean, non-zero an error code. BiffBoolErrRecord GetBoolErr() WriteBoolean, WriteError, WriteBoolErr
LABEL 0x0204 An inline 16-bit-length string; bytes after the string are ignored. BiffLabelRecord GetLabel() WriteLabel
LABELSST 0x00FD BIFF8 only. A 32-bit index into the shared string table (10 bytes total). The writer rejects it under BIFF5. BiffLabelSstRecord GetLabelSst() WriteLabelSst
RSTRING 0x00D6 BIFF5 rich text: a 16-bit-length byte string, then a run count byte and two-byte runs (character index, font index). Decoded as a byte string in either version. BiffRStringRecord GetRString() WriteRecord (raw)
FORMULA 0x0006 The eight-byte cached result, the option flags, a reserved 32-bit field, the token length, and the parsed-expression tokens (22 bytes plus tokens). A record shorter than 22 bytes decodes with empty tokens. BiffFormulaRecord (with BiffCachedResultKind) GetFormula() WriteFormula (numeric and special-result overloads)
STRING 0x0207 No prefix: a 16-bit-length string holding the cached text of the FORMULA immediately before it. BiffStringRecord GetString() WriteString

The cached result of a formula

The eight-byte result field of a FORMULA record holds a double unless its last two bytes are 0xFFFF; then its first byte selects the BiffCachedResultKind:

First byte Kind Value
0 String The text is in the STRING record that follows.
1 Boolean The third byte is zero or one - BooleanValue.
2 Error The third byte is the error code - ErrorCode.
3 (any other) Empty The formula produced an empty value.

The writer's special-result overload takes the kind and the value byte and lays the field out accordingly.

Strings

The version selects how every text field is stored; BiffString is the one view over both forms.

BIFF8 Unicode string BIFF5 byte string
Length prefix 8-bit in BOUNDSHEET and FONT, 16-bit elsewhere - a character count. 8-bit in BOUNDSHEET, FONT, and FORMAT, 16-bit elsewhere - a byte count.
Flags byte Follows the length: bit 0 selects 16-bit code units over compressed 8-bit characters, bit 2 announces extended (phonetic) data, bit 3 announces rich-text runs. None.
Characters UTF-16LE code units, or one low byte per character when every character fits (IsHighByte tells which). Bytes in the code page the stream's CODEPAGE record declared (Windows-1252 when none has been read).
Trailers A 16-bit run count before the characters and four bytes per run after them; a 32-bit extended size before the characters and that many bytes after the runs. None; RSTRING carries its own run table after the string.
Writer Chooses the compressed form when every character fits a byte, 16-bit code units otherwise. Encodes with the writer's code page; a character the code page cannot represent becomes its replacement character.

Shared string table

The BIFF8 SST record is the canonical continued structure. BiffSstReader reads it by these rules, and WriteSst writes it by the same:

  • A string's header - length, flags, and any run count or extended size - never straddles a record boundary; when fewer than three bytes remain, the header opens the next CONTINUE record.
  • A string's characters may straddle a boundary, but only at a character boundary - a 16-bit code unit is never split.
  • Each continued run of characters restarts with its own flags byte, which may change the character width mid-string.
  • Rich-run and extended-data trailers may straddle a boundary without a flags byte; the reader skips them and reports them as empty when they do.

A string that lies within one record is exposed as a span-backed Current view; one that straddles a boundary has IsFragmented set and is served through GetString() and CopyTo from a reusable scratch buffer.

What the version changes

Concern BIFF5 BIFF8
BOF version marker 0x0500 0x0600
Maximum record payload 2,080 bytes (Biff5MaxPayloadLength) 8,224 bytes (Biff8MaxPayloadLength)
Text Code-page byte strings Unicode strings
BOF payload 8 bytes 16 bytes
DIMENSIONS rows 16-bit (10-byte payload) 32-bit (14-byte payload)
XF payload 16 bytes 20 bytes
FILEPASS XOR key and hash Type word, then XOR pair or RC4 header
Shared strings None - every text cell is a LABEL or RSTRING SST in the globals, LABELSST cells
Rich text RSTRING cells Runs inside the Unicode string
Compound-file stream name Book Workbook

The reader establishes the version from the first BOF it reads, or from Version; the writer is created with it. The version-dependent accessors - GetBoundSheet, GetDimensions, GetLabel, GetString, GetFormat, GetFont, GetFilePass - refuse to decode until it is known.

Named identifiers without an accessor

BiffRecordType also names identifiers the codec frames but does not decode - among them INDEX, DBCELL, WINDOW1 / WINDOW2, COLINFO, DEFCOLWIDTH, DEFAULTROWHEIGHT, MERGECELLS, NAME, EXTERNSHEET, SUPBOOK, SHRFMLA, ARRAY, TABLE, NOTE, OBJ, TXO, HLINK, PALETTE, STYLE, WRITEACCESS, CODENAME, and the calculation and print settings. They exist so a switch on RecordType can name them; their payloads are read through ValueSpan and written back through WriteRecord. The three legacy identifiers Biff2Bof, Biff3Bof, and Biff4Bof are recognized so a pre-BIFF5 stream fails with BiffUnsupportedVersionException rather than being mis-parsed.

Vocabulary and configuration types

Type Role
BiffVersion Biff5, Biff8, or Unknown before a BOF has been read.
BiffRecordType The curated catalogue of record identifiers.
BiffSubstreamType The kinds of substream a BOF opens: workbook globals, worksheet, chart, macro sheet, and so on.
BiffSheetState / BiffSheetType The visibility and kind a BOUNDSHEET record declares.
BiffCachedResultKind The kind of value in a FORMULA record's result field.
BiffEncryptionType The scheme a FILEPASS record declares.
BiffReaderOptions Seeds a known version and code page so a sheet substream can be read without its BOF first.
BiffReaderState The established version and code page, carried between readers when reading incrementally.
BiffWriterOptions The version the writer emits and the BIFF5 code page it encodes text in.
BiffRecordHeader The four-byte header: TryParse and WriteTo for callers framing records themselves.
BiffLimits Header size, per-version maximum payload, the default and Unicode code pages.
BiffRk Decode and TryEncode for the RK number representation.

Where to go next