Skip to main content

brink_format/inkb/
read.rs

1//! Decoding (read) half of the `.inkb` binary format.
2
3use crate::codec::{crc32, read_def_id, read_i32, read_str, read_u8, read_u16, read_u32, read_u64};
4use crate::counting::CountingFlags;
5use crate::definition::{
6    AddressDef, AddressPath, ContainerDef, ExternalFnDef, GlobalVarDef, LineEntry, ListDef,
7    ListItemDef, ScopeLineTable, SlotInfo, SourceLocation,
8};
9use crate::id::NameId;
10use crate::line::{LineContent, LinePart, PluralCategory, SelectKey};
11use crate::opcode::DecodeError;
12use crate::story::StoryData;
13use crate::value::{ListValue, Value, ValueType};
14
15use super::{
16    CAT_FEW, CAT_MANY, CAT_ONE, CAT_OTHER, CAT_TWO, CAT_ZERO, HEADER_PREAMBLE, InkbIndex,
17    KEY_CARDINAL, KEY_EXACT, KEY_KEYWORD, KEY_ORDINAL, LINE_PLAIN, LINE_TEMPLATE, MAGIC,
18    PART_LITERAL, PART_SELECT, PART_SLOT, SECTION_ENTRY_SIZE, SectionEntry, SectionKind, VAL_BOOL,
19    VAL_DIVERT_TARGET, VAL_FLOAT, VAL_FRAGMENT_REF, VAL_INT, VAL_LIST, VAL_NULL, VAL_STRING,
20    VAL_VAR_POINTER, VERSION, safe_capacity,
21};
22
23// ── Tier 1: Full story read ─────────────────────────────────────────────────
24
25/// Decode a [`StoryData`] from `.inkb` binary format.
26pub fn read_inkb(buf: &[u8]) -> Result<StoryData, DecodeError> {
27    let index = read_inkb_index(buf)?;
28
29    // Validate checksum.
30    let header_size = index.header_size();
31    let computed = crc32(&buf[header_size..]);
32    if computed != index.checksum {
33        return Err(DecodeError::ChecksumMismatch {
34            expected: index.checksum,
35            actual: computed,
36        });
37    }
38
39    let name_table = read_section_name_table(buf, &index)?;
40    let variables = read_section_variables(buf, &index)?;
41    let list_defs = read_section_list_defs(buf, &index)?;
42    let list_items = read_section_list_items(buf, &index)?;
43    let externals = read_section_externals(buf, &index)?;
44    let containers = read_section_containers(buf, &index)?;
45    let line_tables = read_section_line_tables(buf, &index)?;
46    let addresses = read_section_addresses(buf, &index)?;
47    let list_literals = read_section_list_literals(buf, &index)?;
48    let address_paths = read_section_address_paths(buf, &index)?;
49
50    Ok(StoryData {
51        containers,
52        line_tables,
53        variables,
54        list_defs,
55        list_items,
56        externals,
57        addresses,
58        address_paths,
59        name_table,
60        list_literals,
61        source_checksum: index.checksum,
62    })
63}
64
65// ── Tier 2: Index-only parse ────────────────────────────────────────────────
66
67/// Parse the `.inkb` header and offset table without touching section data.
68pub fn read_inkb_index(buf: &[u8]) -> Result<InkbIndex, DecodeError> {
69    if buf.len() < HEADER_PREAMBLE {
70        return Err(DecodeError::UnexpectedEof);
71    }
72
73    let magic: [u8; 4] = [buf[0], buf[1], buf[2], buf[3]];
74    if &magic != MAGIC {
75        return Err(DecodeError::BadMagic(magic));
76    }
77
78    let mut off = 4;
79    let version = read_u16(buf, &mut off)?;
80    if version != VERSION {
81        return Err(DecodeError::UnsupportedVersion(version));
82    }
83
84    let section_count = read_u8(buf, &mut off)?;
85    let _reserved = read_u8(buf, &mut off)?;
86    let file_size = read_u32(buf, &mut off)?;
87    let checksum = read_u32(buf, &mut off)?;
88
89    // Validate file size.
90    if file_size as usize != buf.len() {
91        return Err(DecodeError::FileSizeMismatch {
92            expected: file_size,
93            actual: buf.len(),
94        });
95    }
96
97    let total_header = HEADER_PREAMBLE + section_count as usize * SECTION_ENTRY_SIZE;
98    if buf.len() < total_header {
99        return Err(DecodeError::UnexpectedEof);
100    }
101
102    let mut sections = Vec::with_capacity(section_count as usize);
103    for _ in 0..section_count {
104        let kind_tag = read_u8(buf, &mut off)?;
105        let kind = SectionKind::from_u8(kind_tag)?;
106        let _reserved0 = read_u8(buf, &mut off)?;
107        let _reserved1 = read_u8(buf, &mut off)?;
108        let _reserved2 = read_u8(buf, &mut off)?;
109        let offset = read_u32(buf, &mut off)?;
110        sections.push(SectionEntry { kind, offset });
111    }
112
113    // Validate structural invariants so downstream code can trust the index:
114    //   1. Every offset >= header size (sections live after the header)
115    //   2. Offsets are strictly monotonically increasing
116    //   3. Every offset <= file_size (sections live within the file)
117    // Max value: 16 + 255*8 = 2056, always fits in u32.
118    #[expect(clippy::cast_possible_truncation)]
119    let header_size = total_header as u32;
120    let mut prev_offset = header_size;
121    for entry in &sections {
122        if entry.offset < header_size || entry.offset > file_size || entry.offset < prev_offset {
123            return Err(DecodeError::InvalidSectionOffset {
124                kind: entry.kind as u8,
125                offset: entry.offset,
126            });
127        }
128        prev_offset = entry.offset;
129    }
130
131    Ok(InkbIndex {
132        version,
133        file_size,
134        checksum,
135        sections,
136    })
137}
138
139// ── Tier 3: Section-level read ──────────────────────────────────────────────
140
141/// Read the name table from a complete `.inkb` file using its index.
142pub fn read_section_name_table(buf: &[u8], index: &InkbIndex) -> Result<Vec<String>, DecodeError> {
143    let range =
144        index
145            .section_range(SectionKind::NameTable)
146            .ok_or(DecodeError::MissingSectionKind(
147                SectionKind::NameTable as u8,
148            ))?;
149    let mut off = range.start;
150    let count = read_u32(buf, &mut off)? as usize;
151    let mut names = Vec::with_capacity(safe_capacity(count, buf.len(), off, 4));
152    for _ in 0..count {
153        names.push(read_str(buf, &mut off)?);
154    }
155    Ok(names)
156}
157
158/// Read the variables from a complete `.inkb` file using its index.
159pub fn read_section_variables(
160    buf: &[u8],
161    index: &InkbIndex,
162) -> Result<Vec<GlobalVarDef>, DecodeError> {
163    let range =
164        index
165            .section_range(SectionKind::Variables)
166            .ok_or(DecodeError::MissingSectionKind(
167                SectionKind::Variables as u8,
168            ))?;
169    let mut off = range.start;
170    let count = read_u32(buf, &mut off)? as usize;
171    let mut vars = Vec::with_capacity(safe_capacity(count, buf.len(), off, 12));
172    for _ in 0..count {
173        vars.push(decode_global_var(buf, &mut off)?);
174    }
175    Ok(vars)
176}
177
178/// Read the list definitions from a complete `.inkb` file using its index.
179pub fn read_section_list_defs(buf: &[u8], index: &InkbIndex) -> Result<Vec<ListDef>, DecodeError> {
180    let range = index
181        .section_range(SectionKind::ListDefs)
182        .ok_or(DecodeError::MissingSectionKind(SectionKind::ListDefs as u8))?;
183    let mut off = range.start;
184    let count = read_u32(buf, &mut off)? as usize;
185    let mut defs = Vec::with_capacity(safe_capacity(count, buf.len(), off, 14));
186    for _ in 0..count {
187        defs.push(decode_list_def(buf, &mut off)?);
188    }
189    Ok(defs)
190}
191
192/// Read the list items from a complete `.inkb` file using its index.
193pub fn read_section_list_items(
194    buf: &[u8],
195    index: &InkbIndex,
196) -> Result<Vec<ListItemDef>, DecodeError> {
197    let range =
198        index
199            .section_range(SectionKind::ListItems)
200            .ok_or(DecodeError::MissingSectionKind(
201                SectionKind::ListItems as u8,
202            ))?;
203    let mut off = range.start;
204    let count = read_u32(buf, &mut off)? as usize;
205    let mut items = Vec::with_capacity(safe_capacity(count, buf.len(), off, 20));
206    for _ in 0..count {
207        items.push(decode_list_item(buf, &mut off)?);
208    }
209    Ok(items)
210}
211
212/// Read the externals from a complete `.inkb` file using its index.
213pub fn read_section_externals(
214    buf: &[u8],
215    index: &InkbIndex,
216) -> Result<Vec<ExternalFnDef>, DecodeError> {
217    let range =
218        index
219            .section_range(SectionKind::Externals)
220            .ok_or(DecodeError::MissingSectionKind(
221                SectionKind::Externals as u8,
222            ))?;
223    let mut off = range.start;
224    let count = read_u32(buf, &mut off)? as usize;
225    let mut exts = Vec::with_capacity(safe_capacity(count, buf.len(), off, 12));
226    for _ in 0..count {
227        exts.push(decode_external(buf, &mut off)?);
228    }
229    Ok(exts)
230}
231
232/// Read the containers from a complete `.inkb` file using its index.
233pub fn read_section_containers(
234    buf: &[u8],
235    index: &InkbIndex,
236) -> Result<Vec<ContainerDef>, DecodeError> {
237    let range =
238        index
239            .section_range(SectionKind::Containers)
240            .ok_or(DecodeError::MissingSectionKind(
241                SectionKind::Containers as u8,
242            ))?;
243    let mut off = range.start;
244    let count = read_u32(buf, &mut off)? as usize;
245    let mut containers = Vec::with_capacity(safe_capacity(count, buf.len(), off, 21));
246    for _ in 0..count {
247        containers.push(decode_container(buf, &mut off)?);
248    }
249    Ok(containers)
250}
251
252/// Read the addresses from a complete `.inkb` file using its index.
253pub fn read_section_addresses(
254    buf: &[u8],
255    index: &InkbIndex,
256) -> Result<Vec<AddressDef>, DecodeError> {
257    let Some(range) = index.section_range(SectionKind::Labels) else {
258        // Addresses section is optional for backwards compatibility.
259        return Ok(Vec::new());
260    };
261    let mut off = range.start;
262    let count = read_u32(buf, &mut off)? as usize;
263    // Each address entry: def_id(8) + container_id(8) + byte_offset(4) = 20 bytes
264    let mut addresses = Vec::with_capacity(safe_capacity(count, buf.len(), off, 20));
265    for _ in 0..count {
266        let id = read_def_id(buf, &mut off)?;
267        let container_id = read_def_id(buf, &mut off)?;
268        let byte_offset = read_u32(buf, &mut off)?;
269        addresses.push(AddressDef {
270            id,
271            container_id,
272            byte_offset,
273        });
274    }
275    Ok(addresses)
276}
277
278/// Read the address-paths section using a pre-parsed index.
279pub fn read_section_address_paths(
280    buf: &[u8],
281    index: &InkbIndex,
282) -> Result<Vec<AddressPath>, DecodeError> {
283    let Some(range) = index.section_range(SectionKind::AddressPaths) else {
284        // AddressPaths section is optional for backwards compatibility
285        // (legacy `.inkb` and converter output omit it).
286        return Ok(Vec::new());
287    };
288    let mut off = range.start;
289    let count = read_u32(buf, &mut off)? as usize;
290    // Each entry: path NameId(2) + target def_id(8) = 10 bytes
291    let mut paths = Vec::with_capacity(safe_capacity(count, buf.len(), off, 10));
292    for _ in 0..count {
293        let path = NameId(read_u16(buf, &mut off)?);
294        let target = read_def_id(buf, &mut off)?;
295        paths.push(AddressPath { path, target });
296    }
297    Ok(paths)
298}
299
300// ── Decode helpers (private) ────────────────────────────────────────────────
301
302fn decode_global_var(buf: &[u8], off: &mut usize) -> Result<GlobalVarDef, DecodeError> {
303    let id = read_def_id(buf, off)?;
304    let name = NameId(read_u16(buf, off)?);
305    let value_type = decode_value_type(buf, off)?;
306    let default_value = decode_value(buf, off)?;
307    let mutable = read_u8(buf, off)? != 0;
308    Ok(GlobalVarDef {
309        id,
310        name,
311        value_type,
312        default_value,
313        mutable,
314    })
315}
316
317fn decode_value_type(buf: &[u8], off: &mut usize) -> Result<ValueType, DecodeError> {
318    let tag = read_u8(buf, off)?;
319    match tag {
320        VAL_INT => Ok(ValueType::Int),
321        VAL_FLOAT => Ok(ValueType::Float),
322        VAL_BOOL => Ok(ValueType::Bool),
323        VAL_STRING => Ok(ValueType::String),
324        VAL_LIST => Ok(ValueType::List),
325        VAL_DIVERT_TARGET => Ok(ValueType::DivertTarget),
326        VAL_VAR_POINTER => Ok(ValueType::VariablePointer),
327        VAL_FRAGMENT_REF => Ok(ValueType::FragmentRef),
328        VAL_NULL => Ok(ValueType::Null),
329        _ => Err(DecodeError::InvalidValueType(tag)),
330    }
331}
332
333fn decode_value(buf: &[u8], off: &mut usize) -> Result<Value, DecodeError> {
334    let tag = read_u8(buf, off)?;
335    match tag {
336        VAL_INT => Ok(Value::Int(read_i32(buf, off)?)),
337        VAL_FLOAT => {
338            if *off + 4 > buf.len() {
339                return Err(DecodeError::UnexpectedEof);
340            }
341            let v = f32::from_le_bytes([buf[*off], buf[*off + 1], buf[*off + 2], buf[*off + 3]]);
342            *off += 4;
343            Ok(Value::Float(v))
344        }
345        VAL_BOOL => Ok(Value::Bool(read_u8(buf, off)? != 0)),
346        VAL_STRING => Ok(Value::String(read_str(buf, off)?.into())),
347        VAL_LIST => {
348            let item_count = read_u32(buf, off)? as usize;
349            let mut items = Vec::with_capacity(safe_capacity(item_count, buf.len(), *off, 8));
350            for _ in 0..item_count {
351                items.push(read_def_id(buf, off)?);
352            }
353            let origin_count = read_u32(buf, off)? as usize;
354            let mut origins = Vec::with_capacity(safe_capacity(origin_count, buf.len(), *off, 8));
355            for _ in 0..origin_count {
356                origins.push(read_def_id(buf, off)?);
357            }
358            Ok(Value::List(ListValue { items, origins }.into()))
359        }
360        VAL_DIVERT_TARGET => Ok(Value::DivertTarget(read_def_id(buf, off)?)),
361        VAL_VAR_POINTER => Ok(Value::VariablePointer(read_def_id(buf, off)?)),
362        VAL_FRAGMENT_REF => Ok(Value::FragmentRef(read_u32(buf, off)?)),
363        VAL_NULL => Ok(Value::Null),
364        _ => Err(DecodeError::InvalidValueType(tag)),
365    }
366}
367
368fn decode_list_def(buf: &[u8], off: &mut usize) -> Result<ListDef, DecodeError> {
369    let id = read_def_id(buf, off)?;
370    let name = NameId(read_u16(buf, off)?);
371    let item_count = read_u32(buf, off)? as usize;
372    let mut items = Vec::with_capacity(safe_capacity(item_count, buf.len(), *off, 6));
373    for _ in 0..item_count {
374        let name_id = NameId(read_u16(buf, off)?);
375        let ordinal = read_i32(buf, off)?;
376        items.push((name_id, ordinal));
377    }
378    Ok(ListDef { id, name, items })
379}
380
381fn decode_list_item(buf: &[u8], off: &mut usize) -> Result<ListItemDef, DecodeError> {
382    let id = read_def_id(buf, off)?;
383    let origin = read_def_id(buf, off)?;
384    let ordinal = read_i32(buf, off)?;
385    let name = NameId(read_u16(buf, off)?);
386    Ok(ListItemDef {
387        id,
388        origin,
389        ordinal,
390        name,
391    })
392}
393
394/// Read the list literals from a complete `.inkb` file using its index.
395pub fn read_section_list_literals(
396    buf: &[u8],
397    index: &InkbIndex,
398) -> Result<Vec<ListValue>, DecodeError> {
399    let Some(range) = index.section_range(SectionKind::ListLiterals) else {
400        return Ok(Vec::new());
401    };
402    let mut off = range.start;
403    let count = read_u32(buf, &mut off)? as usize;
404    let mut literals = Vec::with_capacity(safe_capacity(count, buf.len(), off, 8));
405    for _ in 0..count {
406        let item_count = read_u32(buf, &mut off)? as usize;
407        let mut items = Vec::with_capacity(safe_capacity(item_count, buf.len(), off, 8));
408        for _ in 0..item_count {
409            items.push(read_def_id(buf, &mut off)?);
410        }
411        let origin_count = read_u32(buf, &mut off)? as usize;
412        let mut origins = Vec::with_capacity(safe_capacity(origin_count, buf.len(), off, 8));
413        for _ in 0..origin_count {
414            origins.push(read_def_id(buf, &mut off)?);
415        }
416        literals.push(ListValue { items, origins });
417    }
418    Ok(literals)
419}
420
421fn decode_external(buf: &[u8], off: &mut usize) -> Result<ExternalFnDef, DecodeError> {
422    let id = read_def_id(buf, off)?;
423    let name = NameId(read_u16(buf, off)?);
424    let arg_count = read_u8(buf, off)?;
425    let has_fallback = read_u8(buf, off)? != 0;
426    let fallback = if has_fallback {
427        Some(read_def_id(buf, off)?)
428    } else {
429        None
430    };
431    Ok(ExternalFnDef {
432        id,
433        name,
434        arg_count,
435        fallback,
436    })
437}
438
439fn decode_container(buf: &[u8], off: &mut usize) -> Result<ContainerDef, DecodeError> {
440    let id = read_def_id(buf, off)?;
441    let scope_id = read_def_id(buf, off)?;
442    let has_name = read_u8(buf, off)? != 0;
443    let name = if has_name {
444        Some(NameId(read_u16(buf, off)?))
445    } else {
446        None
447    };
448    let counting_bits = read_u8(buf, off)?;
449    let counting_flags = CountingFlags::from_bits(counting_bits).unwrap_or(CountingFlags::empty());
450    let path_hash = read_i32(buf, off)?;
451
452    let bytecode_len = read_u32(buf, off)? as usize;
453    if *off + bytecode_len > buf.len() {
454        return Err(DecodeError::UnexpectedEof);
455    }
456    let bytecode = buf[*off..*off + bytecode_len].to_vec();
457    *off += bytecode_len;
458
459    Ok(ContainerDef {
460        id,
461        scope_id,
462        name,
463        bytecode,
464        counting_flags,
465        path_hash,
466    })
467}
468
469/// Read the line tables from a complete `.inkb` file using its index.
470pub fn read_section_line_tables(
471    buf: &[u8],
472    index: &InkbIndex,
473) -> Result<Vec<ScopeLineTable>, DecodeError> {
474    let range =
475        index
476            .section_range(SectionKind::LineTables)
477            .ok_or(DecodeError::MissingSectionKind(
478                SectionKind::LineTables as u8,
479            ))?;
480    let mut off = range.start;
481    let count = read_u32(buf, &mut off)? as usize;
482    let mut tables = Vec::with_capacity(safe_capacity(count, buf.len(), off, 12));
483    for _ in 0..count {
484        tables.push(decode_scope_line_table(buf, &mut off)?);
485    }
486    Ok(tables)
487}
488
489fn decode_scope_line_table(buf: &[u8], off: &mut usize) -> Result<ScopeLineTable, DecodeError> {
490    let scope_id = read_def_id(buf, off)?;
491    let line_count = read_u32(buf, off)? as usize;
492    let mut lines = Vec::with_capacity(safe_capacity(line_count, buf.len(), *off, 9));
493    for _ in 0..line_count {
494        lines.push(decode_line_entry(buf, off)?);
495    }
496    Ok(ScopeLineTable { scope_id, lines })
497}
498
499fn decode_line_entry(buf: &[u8], off: &mut usize) -> Result<LineEntry, DecodeError> {
500    let content = decode_line_content(buf, off)?;
501    let source_hash = read_u64(buf, off)?;
502    let has_audio = read_u8(buf, off)? != 0;
503    let audio_ref = if has_audio {
504        Some(read_str(buf, off)?)
505    } else {
506        None
507    };
508    // Slot info
509    let slot_count = read_u8(buf, off)? as usize;
510    let mut slot_info = Vec::with_capacity(slot_count);
511    for _ in 0..slot_count {
512        let index = read_u8(buf, off)?;
513        let name = read_str(buf, off)?;
514        slot_info.push(SlotInfo { index, name });
515    }
516
517    // Source location
518    let has_source_loc = read_u8(buf, off)? != 0;
519    let source_location = if has_source_loc {
520        let file = read_str(buf, off)?;
521        let range_start = read_u32(buf, off)?;
522        let range_end = read_u32(buf, off)?;
523        Some(SourceLocation {
524            file,
525            range_start,
526            range_end,
527        })
528    } else {
529        None
530    };
531
532    let flags = crate::LineFlags::from_content(&content);
533    Ok(LineEntry {
534        content,
535        flags,
536        source_hash,
537        audio_ref,
538        slot_info,
539        source_location,
540    })
541}
542
543pub(crate) fn decode_line_content(buf: &[u8], off: &mut usize) -> Result<LineContent, DecodeError> {
544    let tag = read_u8(buf, off)?;
545    match tag {
546        LINE_PLAIN => Ok(LineContent::Plain(read_str(buf, off)?)),
547        LINE_TEMPLATE => {
548            let part_count = read_u32(buf, off)? as usize;
549            let mut parts = Vec::with_capacity(safe_capacity(part_count, buf.len(), *off, 2));
550            for _ in 0..part_count {
551                parts.push(decode_line_part(buf, off)?);
552            }
553            Ok(LineContent::Template(parts))
554        }
555        _ => Err(DecodeError::InvalidLineContent(tag)),
556    }
557}
558
559fn decode_line_part(buf: &[u8], off: &mut usize) -> Result<LinePart, DecodeError> {
560    let tag = read_u8(buf, off)?;
561    match tag {
562        PART_LITERAL => Ok(LinePart::Literal(read_str(buf, off)?)),
563        PART_SLOT => Ok(LinePart::Slot(read_u8(buf, off)?)),
564        PART_SELECT => {
565            let slot = read_u8(buf, off)?;
566            let variant_count = read_u32(buf, off)? as usize;
567            let mut variants = Vec::with_capacity(safe_capacity(variant_count, buf.len(), *off, 6));
568            for _ in 0..variant_count {
569                let key = decode_select_key(buf, off)?;
570                let text = read_str(buf, off)?;
571                variants.push((key, text));
572            }
573            let default = read_str(buf, off)?;
574            Ok(LinePart::Select {
575                slot,
576                variants,
577                default,
578            })
579        }
580        _ => Err(DecodeError::InvalidLinePart(tag)),
581    }
582}
583
584fn decode_select_key(buf: &[u8], off: &mut usize) -> Result<SelectKey, DecodeError> {
585    let tag = read_u8(buf, off)?;
586    match tag {
587        KEY_CARDINAL => Ok(SelectKey::Cardinal(decode_plural_category(buf, off)?)),
588        KEY_ORDINAL => Ok(SelectKey::Ordinal(decode_plural_category(buf, off)?)),
589        KEY_EXACT => Ok(SelectKey::Exact(read_i32(buf, off)?)),
590        KEY_KEYWORD => Ok(SelectKey::Keyword(read_str(buf, off)?)),
591        _ => Err(DecodeError::InvalidSelectKey(tag)),
592    }
593}
594
595fn decode_plural_category(buf: &[u8], off: &mut usize) -> Result<PluralCategory, DecodeError> {
596    let tag = read_u8(buf, off)?;
597    match tag {
598        CAT_ZERO => Ok(PluralCategory::Zero),
599        CAT_ONE => Ok(PluralCategory::One),
600        CAT_TWO => Ok(PluralCategory::Two),
601        CAT_FEW => Ok(PluralCategory::Few),
602        CAT_MANY => Ok(PluralCategory::Many),
603        CAT_OTHER => Ok(PluralCategory::Other),
604        _ => Err(DecodeError::InvalidPluralCategory(tag)),
605    }
606}