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    let local = read_u8(buf, off)? != 0;
309    Ok(GlobalVarDef {
310        id,
311        name,
312        value_type,
313        default_value,
314        mutable,
315        local,
316    })
317}
318
319fn decode_value_type(buf: &[u8], off: &mut usize) -> Result<ValueType, DecodeError> {
320    let tag = read_u8(buf, off)?;
321    match tag {
322        VAL_INT => Ok(ValueType::Int),
323        VAL_FLOAT => Ok(ValueType::Float),
324        VAL_BOOL => Ok(ValueType::Bool),
325        VAL_STRING => Ok(ValueType::String),
326        VAL_LIST => Ok(ValueType::List),
327        VAL_DIVERT_TARGET => Ok(ValueType::DivertTarget),
328        VAL_VAR_POINTER => Ok(ValueType::VariablePointer),
329        VAL_FRAGMENT_REF => Ok(ValueType::FragmentRef),
330        VAL_NULL => Ok(ValueType::Null),
331        _ => Err(DecodeError::InvalidValueType(tag)),
332    }
333}
334
335fn decode_value(buf: &[u8], off: &mut usize) -> Result<Value, DecodeError> {
336    let tag = read_u8(buf, off)?;
337    match tag {
338        VAL_INT => Ok(Value::Int(read_i32(buf, off)?)),
339        VAL_FLOAT => {
340            if *off + 4 > buf.len() {
341                return Err(DecodeError::UnexpectedEof);
342            }
343            let v = f32::from_le_bytes([buf[*off], buf[*off + 1], buf[*off + 2], buf[*off + 3]]);
344            *off += 4;
345            Ok(Value::Float(v))
346        }
347        VAL_BOOL => Ok(Value::Bool(read_u8(buf, off)? != 0)),
348        VAL_STRING => Ok(Value::String(read_str(buf, off)?.into())),
349        VAL_LIST => {
350            let item_count = read_u32(buf, off)? as usize;
351            let mut items = Vec::with_capacity(safe_capacity(item_count, buf.len(), *off, 8));
352            for _ in 0..item_count {
353                items.push(read_def_id(buf, off)?);
354            }
355            let origin_count = read_u32(buf, off)? as usize;
356            let mut origins = Vec::with_capacity(safe_capacity(origin_count, buf.len(), *off, 8));
357            for _ in 0..origin_count {
358                origins.push(read_def_id(buf, off)?);
359            }
360            Ok(Value::List(ListValue { items, origins }.into()))
361        }
362        VAL_DIVERT_TARGET => Ok(Value::DivertTarget(read_def_id(buf, off)?)),
363        VAL_VAR_POINTER => Ok(Value::VariablePointer(read_def_id(buf, off)?)),
364        VAL_FRAGMENT_REF => Ok(Value::FragmentRef(read_u32(buf, off)?)),
365        VAL_NULL => Ok(Value::Null),
366        _ => Err(DecodeError::InvalidValueType(tag)),
367    }
368}
369
370fn decode_list_def(buf: &[u8], off: &mut usize) -> Result<ListDef, DecodeError> {
371    let id = read_def_id(buf, off)?;
372    let name = NameId(read_u16(buf, off)?);
373    let item_count = read_u32(buf, off)? as usize;
374    let mut items = Vec::with_capacity(safe_capacity(item_count, buf.len(), *off, 6));
375    for _ in 0..item_count {
376        let name_id = NameId(read_u16(buf, off)?);
377        let ordinal = read_i32(buf, off)?;
378        items.push((name_id, ordinal));
379    }
380    Ok(ListDef { id, name, items })
381}
382
383fn decode_list_item(buf: &[u8], off: &mut usize) -> Result<ListItemDef, DecodeError> {
384    let id = read_def_id(buf, off)?;
385    let origin = read_def_id(buf, off)?;
386    let ordinal = read_i32(buf, off)?;
387    let name = NameId(read_u16(buf, off)?);
388    Ok(ListItemDef {
389        id,
390        origin,
391        ordinal,
392        name,
393    })
394}
395
396/// Read the list literals from a complete `.inkb` file using its index.
397pub fn read_section_list_literals(
398    buf: &[u8],
399    index: &InkbIndex,
400) -> Result<Vec<ListValue>, DecodeError> {
401    let Some(range) = index.section_range(SectionKind::ListLiterals) else {
402        return Ok(Vec::new());
403    };
404    let mut off = range.start;
405    let count = read_u32(buf, &mut off)? as usize;
406    let mut literals = Vec::with_capacity(safe_capacity(count, buf.len(), off, 8));
407    for _ in 0..count {
408        let item_count = read_u32(buf, &mut off)? as usize;
409        let mut items = Vec::with_capacity(safe_capacity(item_count, buf.len(), off, 8));
410        for _ in 0..item_count {
411            items.push(read_def_id(buf, &mut off)?);
412        }
413        let origin_count = read_u32(buf, &mut off)? as usize;
414        let mut origins = Vec::with_capacity(safe_capacity(origin_count, buf.len(), off, 8));
415        for _ in 0..origin_count {
416            origins.push(read_def_id(buf, &mut off)?);
417        }
418        literals.push(ListValue { items, origins });
419    }
420    Ok(literals)
421}
422
423fn decode_external(buf: &[u8], off: &mut usize) -> Result<ExternalFnDef, DecodeError> {
424    let id = read_def_id(buf, off)?;
425    let name = NameId(read_u16(buf, off)?);
426    let arg_count = read_u8(buf, off)?;
427    let has_fallback = read_u8(buf, off)? != 0;
428    let fallback = if has_fallback {
429        Some(read_def_id(buf, off)?)
430    } else {
431        None
432    };
433    Ok(ExternalFnDef {
434        id,
435        name,
436        arg_count,
437        fallback,
438    })
439}
440
441fn decode_container(buf: &[u8], off: &mut usize) -> Result<ContainerDef, DecodeError> {
442    let id = read_def_id(buf, off)?;
443    let scope_id = read_def_id(buf, off)?;
444    let has_name = read_u8(buf, off)? != 0;
445    let name = if has_name {
446        Some(NameId(read_u16(buf, off)?))
447    } else {
448        None
449    };
450    let counting_bits = read_u8(buf, off)?;
451    let counting_flags = CountingFlags::from_bits(counting_bits).unwrap_or(CountingFlags::empty());
452    let path_hash = read_i32(buf, off)?;
453    let param_count = read_u8(buf, off)?;
454    let local = read_u8(buf, off)? != 0;
455
456    let bytecode_len = read_u32(buf, off)? as usize;
457    if *off + bytecode_len > buf.len() {
458        return Err(DecodeError::UnexpectedEof);
459    }
460    let bytecode = buf[*off..*off + bytecode_len].to_vec();
461    *off += bytecode_len;
462
463    Ok(ContainerDef {
464        id,
465        scope_id,
466        name,
467        bytecode,
468        counting_flags,
469        path_hash,
470        param_count,
471        local,
472    })
473}
474
475/// Read the line tables from a complete `.inkb` file using its index.
476pub fn read_section_line_tables(
477    buf: &[u8],
478    index: &InkbIndex,
479) -> Result<Vec<ScopeLineTable>, DecodeError> {
480    let range =
481        index
482            .section_range(SectionKind::LineTables)
483            .ok_or(DecodeError::MissingSectionKind(
484                SectionKind::LineTables as u8,
485            ))?;
486    let mut off = range.start;
487    let count = read_u32(buf, &mut off)? as usize;
488    let mut tables = Vec::with_capacity(safe_capacity(count, buf.len(), off, 12));
489    for _ in 0..count {
490        tables.push(decode_scope_line_table(buf, &mut off)?);
491    }
492    Ok(tables)
493}
494
495fn decode_scope_line_table(buf: &[u8], off: &mut usize) -> Result<ScopeLineTable, DecodeError> {
496    let scope_id = read_def_id(buf, off)?;
497    let line_count = read_u32(buf, off)? as usize;
498    let mut lines = Vec::with_capacity(safe_capacity(line_count, buf.len(), *off, 9));
499    for _ in 0..line_count {
500        lines.push(decode_line_entry(buf, off)?);
501    }
502    Ok(ScopeLineTable { scope_id, lines })
503}
504
505fn decode_line_entry(buf: &[u8], off: &mut usize) -> Result<LineEntry, DecodeError> {
506    let content = decode_line_content(buf, off)?;
507    let source_hash = read_u64(buf, off)?;
508    let has_audio = read_u8(buf, off)? != 0;
509    let audio_ref = if has_audio {
510        Some(read_str(buf, off)?)
511    } else {
512        None
513    };
514    // Slot info
515    let slot_count = read_u8(buf, off)? as usize;
516    let mut slot_info = Vec::with_capacity(slot_count);
517    for _ in 0..slot_count {
518        let index = read_u8(buf, off)?;
519        let name = read_str(buf, off)?;
520        slot_info.push(SlotInfo { index, name });
521    }
522
523    // Source location
524    let has_source_loc = read_u8(buf, off)? != 0;
525    let source_location = if has_source_loc {
526        let file = read_str(buf, off)?;
527        let range_start = read_u32(buf, off)?;
528        let range_end = read_u32(buf, off)?;
529        Some(SourceLocation {
530            file,
531            range_start,
532            range_end,
533        })
534    } else {
535        None
536    };
537
538    let flags = crate::LineFlags::from_content(&content);
539    Ok(LineEntry {
540        content,
541        flags,
542        source_hash,
543        audio_ref,
544        slot_info,
545        source_location,
546    })
547}
548
549pub(crate) fn decode_line_content(buf: &[u8], off: &mut usize) -> Result<LineContent, DecodeError> {
550    let tag = read_u8(buf, off)?;
551    match tag {
552        LINE_PLAIN => Ok(LineContent::Plain(read_str(buf, off)?)),
553        LINE_TEMPLATE => {
554            let part_count = read_u32(buf, off)? as usize;
555            let mut parts = Vec::with_capacity(safe_capacity(part_count, buf.len(), *off, 2));
556            for _ in 0..part_count {
557                parts.push(decode_line_part(buf, off)?);
558            }
559            Ok(LineContent::Template(parts))
560        }
561        _ => Err(DecodeError::InvalidLineContent(tag)),
562    }
563}
564
565fn decode_line_part(buf: &[u8], off: &mut usize) -> Result<LinePart, DecodeError> {
566    let tag = read_u8(buf, off)?;
567    match tag {
568        PART_LITERAL => Ok(LinePart::Literal(read_str(buf, off)?)),
569        PART_SLOT => Ok(LinePart::Slot(read_u8(buf, off)?)),
570        PART_SELECT => {
571            let slot = read_u8(buf, off)?;
572            let variant_count = read_u32(buf, off)? as usize;
573            let mut variants = Vec::with_capacity(safe_capacity(variant_count, buf.len(), *off, 6));
574            for _ in 0..variant_count {
575                let key = decode_select_key(buf, off)?;
576                let text = read_str(buf, off)?;
577                variants.push((key, text));
578            }
579            let default = read_str(buf, off)?;
580            Ok(LinePart::Select {
581                slot,
582                variants,
583                default,
584            })
585        }
586        _ => Err(DecodeError::InvalidLinePart(tag)),
587    }
588}
589
590fn decode_select_key(buf: &[u8], off: &mut usize) -> Result<SelectKey, DecodeError> {
591    let tag = read_u8(buf, off)?;
592    match tag {
593        KEY_CARDINAL => Ok(SelectKey::Cardinal(decode_plural_category(buf, off)?)),
594        KEY_ORDINAL => Ok(SelectKey::Ordinal(decode_plural_category(buf, off)?)),
595        KEY_EXACT => Ok(SelectKey::Exact(read_i32(buf, off)?)),
596        KEY_KEYWORD => Ok(SelectKey::Keyword(read_str(buf, off)?)),
597        _ => Err(DecodeError::InvalidSelectKey(tag)),
598    }
599}
600
601fn decode_plural_category(buf: &[u8], off: &mut usize) -> Result<PluralCategory, DecodeError> {
602    let tag = read_u8(buf, off)?;
603    match tag {
604        CAT_ZERO => Ok(PluralCategory::Zero),
605        CAT_ONE => Ok(PluralCategory::One),
606        CAT_TWO => Ok(PluralCategory::Two),
607        CAT_FEW => Ok(PluralCategory::Few),
608        CAT_MANY => Ok(PluralCategory::Many),
609        CAT_OTHER => Ok(PluralCategory::Other),
610        _ => Err(DecodeError::InvalidPluralCategory(tag)),
611    }
612}