# File Formats These formats describe the SD-card cache files under `/.crosspoint/epub_/`. All POD fields are written in the ESP32 little-endian representation used by `Serialization.h`; strings are length-prefixed UTF-8. ## `book.bin` ### Version 7 `book.bin` stores EPUB metadata plus lookup tables for spine and TOC entries. The current firmware writes this version from `BookMetadataCache`. ImHex pattern: ```c++ import std.mem; import std.string; import std.core; #define EXPECTED_VERSION 7 #define MAX_STRING_LENGTH 65535 struct String { u32 length [[hidden, comment("String byte length")]]; if (length > MAX_STRING_LENGTH) { std::warning(std::format("Unusually large string length: {} bytes", length)); } char data[length] [[comment("UTF-8 string data")]]; } [[sealed, format("format_string"), comment("Length-prefixed UTF-8 string")]]; fn format_string(String s) { return s.data; }; struct Metadata { String title [[comment("Book title")]]; String author [[comment("Book author")]]; String language [[comment("Book language code")]]; String coverItemHref [[comment("Path to cover image")]]; String textReferenceHref [[comment("Path to guided first text reference")]]; }; struct SpineEntry { String href [[comment("Resource path")]]; u32 cumulativeSize [[comment("Cumulative uncompressed spine size through this entry")]]; s16 tocIndex [[comment("Index into TOC, or inherited/previous TOC index when no direct entry exists")]]; }; struct TocEntry { String title [[comment("Chapter/section title")]]; String href [[comment("Resource path")]]; String anchor [[comment("Fragment identifier")]]; u8 level [[comment("Nesting level")]]; s16 spineIndex [[comment("Index into spine (-1 if none)")]]; }; struct BookBin { u8 version; if (version != EXPECTED_VERSION) { std::error(std::format("Unsupported version: {} (expected {})", version, EXPECTED_VERSION)); } u32 lutOffset [[comment("Offset to lookup tables")]]; u16 spineCount; u16 tocCount; Metadata metadata; u32 currentOffset = $; if (currentOffset != lutOffset) { std::warning(std::format("LUT offset mismatch: expected 0x{:X}, got 0x{:X}", lutOffset, currentOffset)); } u32 spineLut[spineCount] [[comment("Spine entry offsets")]]; u32 tocLut[tocCount] [[comment("TOC entry offsets")]]; SpineEntry spines[spineCount]; TocEntry toc[tocCount]; }; BookBin book @ 0x00; u32 fileSize = std::mem::size(); u32 parsedSize = $; if (parsedSize != fileSize) { std::warning(std::format("Unparsed data detected: {} bytes remaining at offset 0x{:X}", fileSize - parsedSize, parsedSize)); } ``` ## `section.bin` ### Version 28 Each file in `sections/*.bin` stores one laid-out spine section. The header is also the cache-busting key: if any layout-affecting setting differs from the current reader settings, the section is discarded and rebuilt. Version 28 includes: - cache-busting fields for paragraph alignment, hyphenation, embedded CSS, image rendering mode, and Focus Reading - page offset LUT - anchor-to-page map for fragment and footnote navigation - paragraph and list-item LUTs used by KOReader sync page refinement - optional per-word Focus Reading split metadata - per-page footnote entries - serialized word style bits for underline, strikethrough, superscript, and subscript ImHex pattern: ```c++ import std.mem; import std.string; import std.core; #define EXPECTED_VERSION 28 #define MAX_STRING_LENGTH 65535 #define FOOTNOTE_NUMBER_LEN 32 #define FOOTNOTE_HREF_LEN 96 struct String { u32 length [[hidden, comment("String byte length")]]; if (length > MAX_STRING_LENGTH) { std::warning(std::format("Unusually large string length: {} bytes", length)); } char data[length] [[comment("UTF-8 string data")]]; } [[sealed, format("format_string"), comment("Length-prefixed UTF-8 string")]]; fn format_string(String s) { return s.data; }; enum PageElementTag : u8 { TAG_PageLine = 1, TAG_PageImage = 2, TAG_PageHorizontalRule = 3 }; enum WordStyle : u8 { REGULAR = 0, BOLD = 1, ITALIC = 2, BOLD_ITALIC = 3, UNDERLINE = 4, STRIKETHROUGH = 8, SUP = 16, SUB = 32 }; enum TextAlign : u8 { JUSTIFIED = 0, LEFT_ALIGN = 1, CENTER_ALIGN = 2, RIGHT_ALIGN = 3, NONE = 4 }; struct BlockStyle { TextAlign alignment; bool textAlignDefined; s16 marginTop; s16 marginBottom; s16 marginLeft; s16 marginRight; s16 paddingTop; s16 paddingBottom; s16 paddingLeft; s16 paddingRight; s16 textIndent; bool textIndentDefined; bool isRtl; bool directionDefined; }; struct TextBlock { u16 wordCount; String words[wordCount]; s16 wordXPos[wordCount]; WordStyle wordStyle[wordCount]; u8 hasFocus; if (hasFocus != 0) { u8 wordFocusBoundary[wordCount] [[comment("UTF-8 byte boundary between bold prefix and suffix")]]; u16 wordFocusSuffixX[wordCount] [[comment("Suffix x offset from word start")]]; } BlockStyle blockStyle; }; struct ImageBlock { String imagePath; s16 width; s16 height; }; struct PageLine { s16 xPos; s16 yPos; TextBlock block; }; struct PageImage { s16 xPos; s16 yPos; ImageBlock image; }; struct PageHorizontalRule { s16 xPos; s16 yPos; u16 width; u8 thickness; }; struct PageElement { PageElementTag pageElementType; if (pageElementType == TAG_PageLine) { PageLine pageLine [[inline]]; } else if (pageElementType == TAG_PageImage) { PageImage pageImage [[inline]]; } else if (pageElementType == TAG_PageHorizontalRule) { PageHorizontalRule horizontalRule [[inline]]; } else { std::error(std::format("Unknown page element type: {}", pageElementType)); } }; struct FootnoteEntry { char number[FOOTNOTE_NUMBER_LEN]; char href[FOOTNOTE_HREF_LEN]; }; struct Page { u16 elementCount; PageElement elements[elementCount] [[inline]]; u16 footnoteCount; FootnoteEntry footnotes[footnoteCount]; }; struct AnchorEntry { String anchor; u16 page; }; struct AnchorMap { u16 count; AnchorEntry entries[count]; }; struct ParagraphLut { u16 count; u16 paragraphIndex[count]; }; struct SectionBin { u8 version; if (version != EXPECTED_VERSION) { std::error(std::format("Unsupported version: {} (expected {})", version, EXPECTED_VERSION)); } s32 fontId; float lineCompression; bool extraParagraphSpacing; u8 paragraphAlignment; u16 viewportWidth; u16 viewportHeight; bool hyphenationEnabled; bool embeddedStyle; u8 imageRendering; bool focusReadingEnabled; u16 pageCount; u32 pageLutOffset; u32 anchorMapOffset; u32 paragraphLutOffset; u32 listItemLutOffset; Page pages[pageCount]; u32 currentOffset = $; if (currentOffset != pageLutOffset) { std::warning(std::format("Page LUT offset mismatch: expected 0x{:X}, got 0x{:X}", pageLutOffset, currentOffset)); } u32 pageLut[pageCount] [[comment("Page data offsets")]]; if (anchorMapOffset != 0) { AnchorMap anchorMap @ anchorMapOffset; } if (paragraphLutOffset != 0) { ParagraphLut paragraphLut @ paragraphLutOffset; } if (listItemLutOffset != 0 && paragraphLutOffset != 0) { u16 listItemIndex[paragraphLut.count] @ listItemLutOffset; } }; SectionBin section @ 0x00; u32 fileSize = std::mem::size(); u32 parsedSize = $; if (parsedSize != fileSize) { std::warning(std::format("Unparsed data detected: {} bytes remaining at offset 0x{:X}", fileSize - parsedSize, parsedSize)); } ```