use crate::codec::{crc32, read_def_id, read_i32, read_str, read_u8, read_u16, read_u32, read_u64};
use crate::counting::CountingFlags;
use crate::definition::{
AddressDef, AddressPath, ContainerDef, ExternalFnDef, GlobalVarDef, LineEntry, ListDef,
ListItemDef, ScopeLineTable, SlotInfo, SourceLocation,
};
use crate::id::NameId;
use crate::line::{LineContent, LinePart, PluralCategory, SelectKey};
use crate::opcode::DecodeError;
use crate::story::StoryData;
use crate::value::{ListValue, Value, ValueType};
use super::{
CAT_FEW, CAT_MANY, CAT_ONE, CAT_OTHER, CAT_TWO, CAT_ZERO, HEADER_PREAMBLE, InkbIndex,
KEY_CARDINAL, KEY_EXACT, KEY_KEYWORD, KEY_ORDINAL, LINE_PLAIN, LINE_TEMPLATE, MAGIC,
PART_LITERAL, PART_SELECT, PART_SLOT, SECTION_ENTRY_SIZE, SectionEntry, SectionKind, VAL_BOOL,
VAL_DIVERT_TARGET, VAL_FLOAT, VAL_FRAGMENT_REF, VAL_INT, VAL_LIST, VAL_NULL, VAL_STRING,
VAL_VAR_POINTER, VERSION, safe_capacity,
};
pub fn read_inkb(buf: &[u8]) -> Result<StoryData, DecodeError> {
let index = read_inkb_index(buf)?;
let header_size = index.header_size();
let computed = crc32(&buf[header_size..]);
if computed != index.checksum {
return Err(DecodeError::ChecksumMismatch {
expected: index.checksum,
actual: computed,
});
}
let name_table = read_section_name_table(buf, &index)?;
let variables = read_section_variables(buf, &index)?;
let list_defs = read_section_list_defs(buf, &index)?;
let list_items = read_section_list_items(buf, &index)?;
let externals = read_section_externals(buf, &index)?;
let containers = read_section_containers(buf, &index)?;
let line_tables = read_section_line_tables(buf, &index)?;
let addresses = read_section_addresses(buf, &index)?;
let list_literals = read_section_list_literals(buf, &index)?;
let address_paths = read_section_address_paths(buf, &index)?;
Ok(StoryData {
containers,
line_tables,
variables,
list_defs,
list_items,
externals,
addresses,
address_paths,
name_table,
list_literals,
source_checksum: index.checksum,
})
}
pub fn read_inkb_index(buf: &[u8]) -> Result<InkbIndex, DecodeError> {
if buf.len() < HEADER_PREAMBLE {
return Err(DecodeError::UnexpectedEof);
}
let magic: [u8; 4] = [buf[0], buf[1], buf[2], buf[3]];
if &magic != MAGIC {
return Err(DecodeError::BadMagic(magic));
}
let mut off = 4;
let version = read_u16(buf, &mut off)?;
if version != VERSION {
return Err(DecodeError::UnsupportedVersion(version));
}
let section_count = read_u8(buf, &mut off)?;
let _reserved = read_u8(buf, &mut off)?;
let file_size = read_u32(buf, &mut off)?;
let checksum = read_u32(buf, &mut off)?;
if file_size as usize != buf.len() {
return Err(DecodeError::FileSizeMismatch {
expected: file_size,
actual: buf.len(),
});
}
let total_header = HEADER_PREAMBLE + section_count as usize * SECTION_ENTRY_SIZE;
if buf.len() < total_header {
return Err(DecodeError::UnexpectedEof);
}
let mut sections = Vec::with_capacity(section_count as usize);
for _ in 0..section_count {
let kind_tag = read_u8(buf, &mut off)?;
let kind = SectionKind::from_u8(kind_tag)?;
let _reserved0 = read_u8(buf, &mut off)?;
let _reserved1 = read_u8(buf, &mut off)?;
let _reserved2 = read_u8(buf, &mut off)?;
let offset = read_u32(buf, &mut off)?;
sections.push(SectionEntry { kind, offset });
}
#[expect(clippy::cast_possible_truncation)]
let header_size = total_header as u32;
let mut prev_offset = header_size;
for entry in §ions {
if entry.offset < header_size || entry.offset > file_size || entry.offset < prev_offset {
return Err(DecodeError::InvalidSectionOffset {
kind: entry.kind as u8,
offset: entry.offset,
});
}
prev_offset = entry.offset;
}
Ok(InkbIndex {
version,
file_size,
checksum,
sections,
})
}
pub fn read_section_name_table(buf: &[u8], index: &InkbIndex) -> Result<Vec<String>, DecodeError> {
let range =
index
.section_range(SectionKind::NameTable)
.ok_or(DecodeError::MissingSectionKind(
SectionKind::NameTable as u8,
))?;
let mut off = range.start;
let count = read_u32(buf, &mut off)? as usize;
let mut names = Vec::with_capacity(safe_capacity(count, buf.len(), off, 4));
for _ in 0..count {
names.push(read_str(buf, &mut off)?);
}
Ok(names)
}
pub fn read_section_variables(
buf: &[u8],
index: &InkbIndex,
) -> Result<Vec<GlobalVarDef>, DecodeError> {
let range =
index
.section_range(SectionKind::Variables)
.ok_or(DecodeError::MissingSectionKind(
SectionKind::Variables as u8,
))?;
let mut off = range.start;
let count = read_u32(buf, &mut off)? as usize;
let mut vars = Vec::with_capacity(safe_capacity(count, buf.len(), off, 12));
for _ in 0..count {
vars.push(decode_global_var(buf, &mut off)?);
}
Ok(vars)
}
pub fn read_section_list_defs(buf: &[u8], index: &InkbIndex) -> Result<Vec<ListDef>, DecodeError> {
let range = index
.section_range(SectionKind::ListDefs)
.ok_or(DecodeError::MissingSectionKind(SectionKind::ListDefs as u8))?;
let mut off = range.start;
let count = read_u32(buf, &mut off)? as usize;
let mut defs = Vec::with_capacity(safe_capacity(count, buf.len(), off, 14));
for _ in 0..count {
defs.push(decode_list_def(buf, &mut off)?);
}
Ok(defs)
}
pub fn read_section_list_items(
buf: &[u8],
index: &InkbIndex,
) -> Result<Vec<ListItemDef>, DecodeError> {
let range =
index
.section_range(SectionKind::ListItems)
.ok_or(DecodeError::MissingSectionKind(
SectionKind::ListItems as u8,
))?;
let mut off = range.start;
let count = read_u32(buf, &mut off)? as usize;
let mut items = Vec::with_capacity(safe_capacity(count, buf.len(), off, 20));
for _ in 0..count {
items.push(decode_list_item(buf, &mut off)?);
}
Ok(items)
}
pub fn read_section_externals(
buf: &[u8],
index: &InkbIndex,
) -> Result<Vec<ExternalFnDef>, DecodeError> {
let range =
index
.section_range(SectionKind::Externals)
.ok_or(DecodeError::MissingSectionKind(
SectionKind::Externals as u8,
))?;
let mut off = range.start;
let count = read_u32(buf, &mut off)? as usize;
let mut exts = Vec::with_capacity(safe_capacity(count, buf.len(), off, 12));
for _ in 0..count {
exts.push(decode_external(buf, &mut off)?);
}
Ok(exts)
}
pub fn read_section_containers(
buf: &[u8],
index: &InkbIndex,
) -> Result<Vec<ContainerDef>, DecodeError> {
let range =
index
.section_range(SectionKind::Containers)
.ok_or(DecodeError::MissingSectionKind(
SectionKind::Containers as u8,
))?;
let mut off = range.start;
let count = read_u32(buf, &mut off)? as usize;
let mut containers = Vec::with_capacity(safe_capacity(count, buf.len(), off, 21));
for _ in 0..count {
containers.push(decode_container(buf, &mut off)?);
}
Ok(containers)
}
pub fn read_section_addresses(
buf: &[u8],
index: &InkbIndex,
) -> Result<Vec<AddressDef>, DecodeError> {
let Some(range) = index.section_range(SectionKind::Labels) else {
return Ok(Vec::new());
};
let mut off = range.start;
let count = read_u32(buf, &mut off)? as usize;
let mut addresses = Vec::with_capacity(safe_capacity(count, buf.len(), off, 20));
for _ in 0..count {
let id = read_def_id(buf, &mut off)?;
let container_id = read_def_id(buf, &mut off)?;
let byte_offset = read_u32(buf, &mut off)?;
addresses.push(AddressDef {
id,
container_id,
byte_offset,
});
}
Ok(addresses)
}
pub fn read_section_address_paths(
buf: &[u8],
index: &InkbIndex,
) -> Result<Vec<AddressPath>, DecodeError> {
let Some(range) = index.section_range(SectionKind::AddressPaths) else {
return Ok(Vec::new());
};
let mut off = range.start;
let count = read_u32(buf, &mut off)? as usize;
let mut paths = Vec::with_capacity(safe_capacity(count, buf.len(), off, 10));
for _ in 0..count {
let path = NameId(read_u16(buf, &mut off)?);
let target = read_def_id(buf, &mut off)?;
paths.push(AddressPath { path, target });
}
Ok(paths)
}
fn decode_global_var(buf: &[u8], off: &mut usize) -> Result<GlobalVarDef, DecodeError> {
let id = read_def_id(buf, off)?;
let name = NameId(read_u16(buf, off)?);
let value_type = decode_value_type(buf, off)?;
let default_value = decode_value(buf, off)?;
let mutable = read_u8(buf, off)? != 0;
Ok(GlobalVarDef {
id,
name,
value_type,
default_value,
mutable,
})
}
fn decode_value_type(buf: &[u8], off: &mut usize) -> Result<ValueType, DecodeError> {
let tag = read_u8(buf, off)?;
match tag {
VAL_INT => Ok(ValueType::Int),
VAL_FLOAT => Ok(ValueType::Float),
VAL_BOOL => Ok(ValueType::Bool),
VAL_STRING => Ok(ValueType::String),
VAL_LIST => Ok(ValueType::List),
VAL_DIVERT_TARGET => Ok(ValueType::DivertTarget),
VAL_VAR_POINTER => Ok(ValueType::VariablePointer),
VAL_FRAGMENT_REF => Ok(ValueType::FragmentRef),
VAL_NULL => Ok(ValueType::Null),
_ => Err(DecodeError::InvalidValueType(tag)),
}
}
fn decode_value(buf: &[u8], off: &mut usize) -> Result<Value, DecodeError> {
let tag = read_u8(buf, off)?;
match tag {
VAL_INT => Ok(Value::Int(read_i32(buf, off)?)),
VAL_FLOAT => {
if *off + 4 > buf.len() {
return Err(DecodeError::UnexpectedEof);
}
let v = f32::from_le_bytes([buf[*off], buf[*off + 1], buf[*off + 2], buf[*off + 3]]);
*off += 4;
Ok(Value::Float(v))
}
VAL_BOOL => Ok(Value::Bool(read_u8(buf, off)? != 0)),
VAL_STRING => Ok(Value::String(read_str(buf, off)?.into())),
VAL_LIST => {
let item_count = read_u32(buf, off)? as usize;
let mut items = Vec::with_capacity(safe_capacity(item_count, buf.len(), *off, 8));
for _ in 0..item_count {
items.push(read_def_id(buf, off)?);
}
let origin_count = read_u32(buf, off)? as usize;
let mut origins = Vec::with_capacity(safe_capacity(origin_count, buf.len(), *off, 8));
for _ in 0..origin_count {
origins.push(read_def_id(buf, off)?);
}
Ok(Value::List(ListValue { items, origins }.into()))
}
VAL_DIVERT_TARGET => Ok(Value::DivertTarget(read_def_id(buf, off)?)),
VAL_VAR_POINTER => Ok(Value::VariablePointer(read_def_id(buf, off)?)),
VAL_FRAGMENT_REF => Ok(Value::FragmentRef(read_u32(buf, off)?)),
VAL_NULL => Ok(Value::Null),
_ => Err(DecodeError::InvalidValueType(tag)),
}
}
fn decode_list_def(buf: &[u8], off: &mut usize) -> Result<ListDef, DecodeError> {
let id = read_def_id(buf, off)?;
let name = NameId(read_u16(buf, off)?);
let item_count = read_u32(buf, off)? as usize;
let mut items = Vec::with_capacity(safe_capacity(item_count, buf.len(), *off, 6));
for _ in 0..item_count {
let name_id = NameId(read_u16(buf, off)?);
let ordinal = read_i32(buf, off)?;
items.push((name_id, ordinal));
}
Ok(ListDef { id, name, items })
}
fn decode_list_item(buf: &[u8], off: &mut usize) -> Result<ListItemDef, DecodeError> {
let id = read_def_id(buf, off)?;
let origin = read_def_id(buf, off)?;
let ordinal = read_i32(buf, off)?;
let name = NameId(read_u16(buf, off)?);
Ok(ListItemDef {
id,
origin,
ordinal,
name,
})
}
pub fn read_section_list_literals(
buf: &[u8],
index: &InkbIndex,
) -> Result<Vec<ListValue>, DecodeError> {
let Some(range) = index.section_range(SectionKind::ListLiterals) else {
return Ok(Vec::new());
};
let mut off = range.start;
let count = read_u32(buf, &mut off)? as usize;
let mut literals = Vec::with_capacity(safe_capacity(count, buf.len(), off, 8));
for _ in 0..count {
let item_count = read_u32(buf, &mut off)? as usize;
let mut items = Vec::with_capacity(safe_capacity(item_count, buf.len(), off, 8));
for _ in 0..item_count {
items.push(read_def_id(buf, &mut off)?);
}
let origin_count = read_u32(buf, &mut off)? as usize;
let mut origins = Vec::with_capacity(safe_capacity(origin_count, buf.len(), off, 8));
for _ in 0..origin_count {
origins.push(read_def_id(buf, &mut off)?);
}
literals.push(ListValue { items, origins });
}
Ok(literals)
}
fn decode_external(buf: &[u8], off: &mut usize) -> Result<ExternalFnDef, DecodeError> {
let id = read_def_id(buf, off)?;
let name = NameId(read_u16(buf, off)?);
let arg_count = read_u8(buf, off)?;
let has_fallback = read_u8(buf, off)? != 0;
let fallback = if has_fallback {
Some(read_def_id(buf, off)?)
} else {
None
};
Ok(ExternalFnDef {
id,
name,
arg_count,
fallback,
})
}
fn decode_container(buf: &[u8], off: &mut usize) -> Result<ContainerDef, DecodeError> {
let id = read_def_id(buf, off)?;
let scope_id = read_def_id(buf, off)?;
let has_name = read_u8(buf, off)? != 0;
let name = if has_name {
Some(NameId(read_u16(buf, off)?))
} else {
None
};
let counting_bits = read_u8(buf, off)?;
let counting_flags = CountingFlags::from_bits(counting_bits).unwrap_or(CountingFlags::empty());
let path_hash = read_i32(buf, off)?;
let param_count = read_u8(buf, off)?;
let bytecode_len = read_u32(buf, off)? as usize;
if *off + bytecode_len > buf.len() {
return Err(DecodeError::UnexpectedEof);
}
let bytecode = buf[*off..*off + bytecode_len].to_vec();
*off += bytecode_len;
Ok(ContainerDef {
id,
scope_id,
name,
bytecode,
counting_flags,
path_hash,
param_count,
})
}
pub fn read_section_line_tables(
buf: &[u8],
index: &InkbIndex,
) -> Result<Vec<ScopeLineTable>, DecodeError> {
let range =
index
.section_range(SectionKind::LineTables)
.ok_or(DecodeError::MissingSectionKind(
SectionKind::LineTables as u8,
))?;
let mut off = range.start;
let count = read_u32(buf, &mut off)? as usize;
let mut tables = Vec::with_capacity(safe_capacity(count, buf.len(), off, 12));
for _ in 0..count {
tables.push(decode_scope_line_table(buf, &mut off)?);
}
Ok(tables)
}
fn decode_scope_line_table(buf: &[u8], off: &mut usize) -> Result<ScopeLineTable, DecodeError> {
let scope_id = read_def_id(buf, off)?;
let line_count = read_u32(buf, off)? as usize;
let mut lines = Vec::with_capacity(safe_capacity(line_count, buf.len(), *off, 9));
for _ in 0..line_count {
lines.push(decode_line_entry(buf, off)?);
}
Ok(ScopeLineTable { scope_id, lines })
}
fn decode_line_entry(buf: &[u8], off: &mut usize) -> Result<LineEntry, DecodeError> {
let content = decode_line_content(buf, off)?;
let source_hash = read_u64(buf, off)?;
let has_audio = read_u8(buf, off)? != 0;
let audio_ref = if has_audio {
Some(read_str(buf, off)?)
} else {
None
};
let slot_count = read_u8(buf, off)? as usize;
let mut slot_info = Vec::with_capacity(slot_count);
for _ in 0..slot_count {
let index = read_u8(buf, off)?;
let name = read_str(buf, off)?;
slot_info.push(SlotInfo { index, name });
}
let has_source_loc = read_u8(buf, off)? != 0;
let source_location = if has_source_loc {
let file = read_str(buf, off)?;
let range_start = read_u32(buf, off)?;
let range_end = read_u32(buf, off)?;
Some(SourceLocation {
file,
range_start,
range_end,
})
} else {
None
};
let flags = crate::LineFlags::from_content(&content);
Ok(LineEntry {
content,
flags,
source_hash,
audio_ref,
slot_info,
source_location,
})
}
pub(crate) fn decode_line_content(buf: &[u8], off: &mut usize) -> Result<LineContent, DecodeError> {
let tag = read_u8(buf, off)?;
match tag {
LINE_PLAIN => Ok(LineContent::Plain(read_str(buf, off)?)),
LINE_TEMPLATE => {
let part_count = read_u32(buf, off)? as usize;
let mut parts = Vec::with_capacity(safe_capacity(part_count, buf.len(), *off, 2));
for _ in 0..part_count {
parts.push(decode_line_part(buf, off)?);
}
Ok(LineContent::Template(parts))
}
_ => Err(DecodeError::InvalidLineContent(tag)),
}
}
fn decode_line_part(buf: &[u8], off: &mut usize) -> Result<LinePart, DecodeError> {
let tag = read_u8(buf, off)?;
match tag {
PART_LITERAL => Ok(LinePart::Literal(read_str(buf, off)?)),
PART_SLOT => Ok(LinePart::Slot(read_u8(buf, off)?)),
PART_SELECT => {
let slot = read_u8(buf, off)?;
let variant_count = read_u32(buf, off)? as usize;
let mut variants = Vec::with_capacity(safe_capacity(variant_count, buf.len(), *off, 6));
for _ in 0..variant_count {
let key = decode_select_key(buf, off)?;
let text = read_str(buf, off)?;
variants.push((key, text));
}
let default = read_str(buf, off)?;
Ok(LinePart::Select {
slot,
variants,
default,
})
}
_ => Err(DecodeError::InvalidLinePart(tag)),
}
}
fn decode_select_key(buf: &[u8], off: &mut usize) -> Result<SelectKey, DecodeError> {
let tag = read_u8(buf, off)?;
match tag {
KEY_CARDINAL => Ok(SelectKey::Cardinal(decode_plural_category(buf, off)?)),
KEY_ORDINAL => Ok(SelectKey::Ordinal(decode_plural_category(buf, off)?)),
KEY_EXACT => Ok(SelectKey::Exact(read_i32(buf, off)?)),
KEY_KEYWORD => Ok(SelectKey::Keyword(read_str(buf, off)?)),
_ => Err(DecodeError::InvalidSelectKey(tag)),
}
}
fn decode_plural_category(buf: &[u8], off: &mut usize) -> Result<PluralCategory, DecodeError> {
let tag = read_u8(buf, off)?;
match tag {
CAT_ZERO => Ok(PluralCategory::Zero),
CAT_ONE => Ok(PluralCategory::One),
CAT_TWO => Ok(PluralCategory::Two),
CAT_FEW => Ok(PluralCategory::Few),
CAT_MANY => Ok(PluralCategory::Many),
CAT_OTHER => Ok(PluralCategory::Other),
_ => Err(DecodeError::InvalidPluralCategory(tag)),
}
}