To properly understand Daggerfall's Book Format, there are a few other topics one must learn. First, the Book Format makes extensive use of the Text Record Format for storing the book's text, organized as a Text Record Database. Secondly, the Book Format also introduces a few new records. Assuming the reader is familiar with the Text Record Format we shall begin.
Where appropriate, this author makes use of ABNF to describe various structures.
Now we are in a position to discus the Book Header structure. The BookHeader structure defines the values and attributes which apply to the book as a whole, such as authorship or market price. The BookHeader structure is a fairly simple format.
- [Bytes 0x00-3f] 7-bit ASCII char (String) Title
- This field identifies the book's Title within the engine
- [Bytes 0x40-7f] 7-bit ASCII char (String) Author
- Identifies the book's Author within the engine
- [Bytes 0x80-87] 7-bit ASCII char (String) IsNaughty
- Valid values are the literal string "naughty " (6E 61 75 67 68 74 79 20) and a null string (00 00 00 00 00 00 00 00).
- This may be treated as a boolean, where "naughty " is considered TRUE and a null-string is considered FALSE.
- The isNaughty flag indicates if the book contains what some might view as "obscene sexual content", or otherwise inappropriate material.
- It is unknown if this is integrated with the parental controls.
- [Bytes 0x88-df] unsigned char (UInt8) Null1
- Always 0x00 each.
- [Bytes 0xe0-e3] unsigned long (UInt32) Price
- This field impacts the book's market price.
- Observed values range 0x0000000a, 0x0000012c, 0x00000190, 0x000003e8, 0x00001388, and 0x00002710.
- Most books have a price of 0x000003e8.
- [Bytes 0xe4-e5] unsigned short (UInt16) Rarity
- The more this value is, the rarer this book is. (Usually 1 to 4).
- [Bytes 0xe6-e7] unsigned short (UInt16) Unknown2
- Unknown value which is 0x04d2 (1234) in all but BOK00100.TXT, where it is 0x4d3 (1235).
- [Bytes 0xe8-e9] unsigned short (UInt16) Unknown3
- Unknown value which is 0x0929 (2345) in all but BOK00100.TXT, where it is 0x092a (2346).
- [Bytes 0xea-eb] unsigned short (UInt16) BookPageCount
- This field indicates the count of BookPageRecord structures contained within the file.
- [Bytes 0xec-(0xec + bookPageCount)] unsigned long[bookPageCount] (UInt32[bookPageCount]) BookPageOffset[bookPageCount]
- Following the BookPageCount field is an array of offsets to the BookPageRecords.
- The book is "read" from bookPageOffset through bookPageOffset[bookPageCount].
- Valid values for bookPageOffset[x] range from 0x00ec through 0xfffe.
- BookPageRecords use the same definition as TextSubrecord structures, exceptiing they must each be terminated with an EndOfPage token.
Book Page Records
Each BookPageRecord structure is virtually identical to a TextSubrecord structure, excepting that it must be terminated with an EndOfPage token. Each bookPageOffset[x] element will point to the start of a page (which could be a formatting character) and will always be terminated with the EndOfPage token, thusly no length information is required to read a BookPageRecord structure (a simple Regular Expression would suffice). In ABNF, we define the BookPageRecord as:
- Character := %x20-7f / PositionCode / FontCode
- Text := 1*Character
- TextLine := Text [ EndOfLine ]
- BlankLine := EndOfLine
- PageLine := ( TextLine [ EndOfLine ] ) / EndOfLine ; a line can be blank, or it can contain text data.
- BookPageRecord := *PageLine EndOfPage ; a pagerecord can be a blank page, or multiple lines of text (which themselves could be blank), but alwasts terminates with the EndOfPage token.
Deviations From Other Text Formats
Books differ from Text Record Databases in a few ways, but the differences are necessary. Books are meant to be read sequentially from the title page through to the end page. Therefore assigning an index number, like a TextRecordId, would be superfluous. There is no interface (with the engine as of v1.07.213) to skip to a specific page number, or open a book to a random page. Secondly, TextRecord elements are meant to be accessed "randomly". By "randomly" we mean any of the records could be displayed at any time, independdently of any other records displayed or not displayed. For example, a specific TextRecord might be written to the player's log in response to a certain event (such as agreeing to a quest) on an "if and only if" logic, and there is no way to know a priori which TextRecords would be written to the log (or displayed on the screen). Furthermore many of these might not even enter into the game. The player might decline a quest. This necessitates they have some sort of unique identifier or "key"; the TextRecordId. In this way the engine can operate on logic such as "If event #23 happens, then display in a pop-up text #65".
That being said, the data contained in either are similar enough that it is speculated there is but a single text renderer within Daggerfall's engine responsible for translating the characters into the font bitmaps and parsing the formatting instructions, regardless if the text should ultimately be displayed against an NPC dialog pop-up or against a book UI.
- The engine will not automatically break lines.
- PageLine elements which exceed 80 characters will simply "run off" the screen's edge. When editing and creating books, care should be taken to ensure that all PageRecord text is visible.
- EndOfPage token
- PositionCode token
- FontCode token
- The current source material (all the QRC files, all the book files, and the TEXT.RSC file) indicates only books should inclulde the EndOfPage, PositionCode, and FontCode tokens within their text data. Pop-ups (such as NPC dialogs) were not meant to facilitate these tokens. [Ex: there are no "previous/next page" elements in the NPC dialog pop-ups. ]