1use crate::codec::{
4 crc32, write_def_id, write_i32, write_str, write_u8, write_u16, write_u32, write_u64,
5};
6use crate::definition::{
7 AddressDef, AddressPath, ContainerDef, ExternalFnDef, GlobalVarDef, LineEntry, ListDef,
8 ListItemDef, ScopeLineTable,
9};
10use crate::line::{LineContent, LinePart, PluralCategory, SelectKey};
11use crate::story::StoryData;
12use crate::value::{ListValue, Value, ValueType};
13
14use super::{
15 CAT_FEW, CAT_MANY, CAT_ONE, CAT_OTHER, CAT_TWO, CAT_ZERO, HEADER_PREAMBLE, KEY_CARDINAL,
16 KEY_EXACT, KEY_KEYWORD, KEY_ORDINAL, LINE_PLAIN, LINE_TEMPLATE, MAGIC, PART_LITERAL,
17 PART_SELECT, PART_SLOT, SECTION_COUNT, SECTION_ENTRY_SIZE, SectionKind, VAL_BOOL,
18 VAL_DIVERT_TARGET, VAL_FLOAT, VAL_FRAGMENT_REF, VAL_INT, VAL_LIST, VAL_NULL, VAL_STRING,
19 VAL_VAR_POINTER, VERSION,
20};
21
22#[expect(clippy::cast_possible_truncation)]
26pub fn write_inkb(story: &StoryData, buf: &mut Vec<u8>) {
27 let base = buf.len();
28 let header_size = HEADER_PREAMBLE + SECTION_COUNT as usize * SECTION_ENTRY_SIZE;
29
30 buf.resize(base + header_size, 0);
32
33 let section_kinds = [
35 SectionKind::NameTable,
36 SectionKind::Variables,
37 SectionKind::ListDefs,
38 SectionKind::ListItems,
39 SectionKind::Externals,
40 SectionKind::Containers,
41 SectionKind::LineTables,
42 SectionKind::Labels,
43 SectionKind::ListLiterals,
44 SectionKind::AddressPaths,
45 ];
46 let mut section_offsets = [0u32; 10];
47
48 section_offsets[0] = (buf.len() - base) as u32;
50 write_section_name_table(&story.name_table, buf);
51
52 section_offsets[1] = (buf.len() - base) as u32;
54 write_section_variables(&story.variables, buf);
55
56 section_offsets[2] = (buf.len() - base) as u32;
58 write_section_list_defs(&story.list_defs, buf);
59
60 section_offsets[3] = (buf.len() - base) as u32;
62 write_section_list_items(&story.list_items, buf);
63
64 section_offsets[4] = (buf.len() - base) as u32;
66 write_section_externals(&story.externals, buf);
67
68 section_offsets[5] = (buf.len() - base) as u32;
70 write_section_containers(&story.containers, buf);
71
72 section_offsets[6] = (buf.len() - base) as u32;
74 write_section_line_tables(&story.line_tables, buf);
75
76 section_offsets[7] = (buf.len() - base) as u32;
78 write_section_addresses(&story.addresses, buf);
79
80 section_offsets[8] = (buf.len() - base) as u32;
82 write_section_list_literals(&story.list_literals, buf);
83
84 section_offsets[9] = (buf.len() - base) as u32;
86 write_section_address_paths(&story.address_paths, buf);
87
88 let file_size = (buf.len() - base) as u32;
89 let checksum = crc32(&buf[base + header_size..]);
90
91 let h = &mut buf[base..];
93 h[0..4].copy_from_slice(MAGIC);
94 h[4..6].copy_from_slice(&VERSION.to_le_bytes());
95 h[6] = SECTION_COUNT;
96 h[7] = 0; h[8..12].copy_from_slice(&file_size.to_le_bytes());
98 h[12..16].copy_from_slice(&checksum.to_le_bytes());
99
100 for (i, kind) in section_kinds.iter().enumerate() {
101 let entry_base = HEADER_PREAMBLE + i * SECTION_ENTRY_SIZE;
102 h[entry_base] = *kind as u8;
103 h[entry_base + 1] = 0; h[entry_base + 2] = 0;
105 h[entry_base + 3] = 0;
106 h[entry_base + 4..entry_base + 8].copy_from_slice(§ion_offsets[i].to_le_bytes());
107 }
108}
109
110#[expect(clippy::cast_possible_truncation)]
117pub fn assemble_inkb(sections: &[(SectionKind, &[u8])], out: &mut Vec<u8>) {
118 let base = out.len();
119 let section_count = sections.len() as u8;
120 let header_size = HEADER_PREAMBLE + sections.len() * SECTION_ENTRY_SIZE;
121
122 out.resize(base + header_size, 0);
124
125 let mut entries: Vec<(SectionKind, u32)> = Vec::with_capacity(sections.len());
127 for (kind, data) in sections {
128 let offset = (out.len() - base) as u32;
129 entries.push((*kind, offset));
130 out.extend_from_slice(data);
131 }
132
133 let file_size = (out.len() - base) as u32;
134 let checksum = crc32(&out[base + header_size..]);
135
136 let h = &mut out[base..];
138 h[0..4].copy_from_slice(MAGIC);
139 h[4..6].copy_from_slice(&VERSION.to_le_bytes());
140 h[6] = section_count;
141 h[7] = 0;
142 h[8..12].copy_from_slice(&file_size.to_le_bytes());
143 h[12..16].copy_from_slice(&checksum.to_le_bytes());
144
145 for (i, (kind, offset)) in entries.iter().enumerate() {
146 let entry_base = HEADER_PREAMBLE + i * SECTION_ENTRY_SIZE;
147 h[entry_base] = *kind as u8;
148 h[entry_base + 1] = 0;
149 h[entry_base + 2] = 0;
150 h[entry_base + 3] = 0;
151 h[entry_base + 4..entry_base + 8].copy_from_slice(&offset.to_le_bytes());
152 }
153}
154
155#[expect(clippy::cast_possible_truncation)]
159pub fn write_section_name_table(names: &[String], buf: &mut Vec<u8>) {
160 write_u32(buf, names.len() as u32);
161 for name in names {
162 write_str(buf, name);
163 }
164}
165
166#[expect(clippy::cast_possible_truncation)]
168pub fn write_section_variables(variables: &[GlobalVarDef], buf: &mut Vec<u8>) {
169 write_u32(buf, variables.len() as u32);
170 for var in variables {
171 encode_global_var(var, buf);
172 }
173}
174
175#[expect(clippy::cast_possible_truncation)]
177pub fn write_section_list_defs(list_defs: &[ListDef], buf: &mut Vec<u8>) {
178 write_u32(buf, list_defs.len() as u32);
179 for ld in list_defs {
180 encode_list_def(ld, buf);
181 }
182}
183
184#[expect(clippy::cast_possible_truncation)]
186pub fn write_section_list_items(list_items: &[ListItemDef], buf: &mut Vec<u8>) {
187 write_u32(buf, list_items.len() as u32);
188 for li in list_items {
189 encode_list_item(li, buf);
190 }
191}
192
193#[expect(clippy::cast_possible_truncation)]
195pub fn write_section_externals(externals: &[ExternalFnDef], buf: &mut Vec<u8>) {
196 write_u32(buf, externals.len() as u32);
197 for ext in externals {
198 encode_external(ext, buf);
199 }
200}
201
202#[expect(clippy::cast_possible_truncation)]
204pub fn write_section_containers(containers: &[ContainerDef], buf: &mut Vec<u8>) {
205 write_u32(buf, containers.len() as u32);
206 for c in containers {
207 encode_container(c, buf);
208 }
209}
210
211#[expect(clippy::cast_possible_truncation)]
213pub fn write_section_addresses(addresses: &[AddressDef], buf: &mut Vec<u8>) {
214 write_u32(buf, addresses.len() as u32);
215 for addr in addresses {
216 write_def_id(buf, addr.id);
217 write_def_id(buf, addr.container_id);
218 write_u32(buf, addr.byte_offset);
219 }
220}
221
222#[expect(clippy::cast_possible_truncation)]
224pub fn write_section_address_paths(address_paths: &[AddressPath], buf: &mut Vec<u8>) {
225 write_u32(buf, address_paths.len() as u32);
226 for ap in address_paths {
227 write_u16(buf, ap.path.0);
228 write_def_id(buf, ap.target);
229 }
230}
231
232fn encode_global_var(v: &GlobalVarDef, buf: &mut Vec<u8>) {
235 write_def_id(buf, v.id);
236 write_u16(buf, v.name.0);
237 encode_value_type(v.value_type, buf);
238 encode_value(&v.default_value, buf);
239 write_u8(buf, u8::from(v.mutable));
240 write_u8(buf, u8::from(v.local));
241}
242
243fn encode_value_type(vt: ValueType, buf: &mut Vec<u8>) {
244 let tag = match vt {
245 ValueType::Int => VAL_INT,
246 ValueType::Float => VAL_FLOAT,
247 ValueType::Bool => VAL_BOOL,
248 ValueType::String => VAL_STRING,
249 ValueType::List => VAL_LIST,
250 ValueType::DivertTarget => VAL_DIVERT_TARGET,
251 ValueType::VariablePointer => VAL_VAR_POINTER,
252 ValueType::FragmentRef => VAL_FRAGMENT_REF,
254 ValueType::TempPointer | ValueType::Null => VAL_NULL,
255 };
256 write_u8(buf, tag);
257}
258
259#[expect(clippy::cast_possible_truncation)]
260fn encode_value(v: &Value, buf: &mut Vec<u8>) {
261 match v {
262 Value::Int(n) => {
263 write_u8(buf, VAL_INT);
264 write_i32(buf, *n);
265 }
266 Value::Float(n) => {
267 write_u8(buf, VAL_FLOAT);
268 buf.extend_from_slice(&n.to_le_bytes());
269 }
270 Value::Bool(b) => {
271 write_u8(buf, VAL_BOOL);
272 write_u8(buf, u8::from(*b));
273 }
274 Value::String(s) => {
275 write_u8(buf, VAL_STRING);
276 write_str(buf, s);
277 }
278 Value::List(lv) => {
279 write_u8(buf, VAL_LIST);
280 write_u32(buf, lv.items.len() as u32);
281 for item in &lv.items {
282 write_def_id(buf, *item);
283 }
284 write_u32(buf, lv.origins.len() as u32);
285 for origin in &lv.origins {
286 write_def_id(buf, *origin);
287 }
288 }
289 Value::DivertTarget(id) => {
290 write_u8(buf, VAL_DIVERT_TARGET);
291 write_def_id(buf, *id);
292 }
293 Value::VariablePointer(id) => {
294 write_u8(buf, VAL_VAR_POINTER);
295 write_def_id(buf, *id);
296 }
297 Value::FragmentRef(idx) => {
298 write_u8(buf, VAL_FRAGMENT_REF);
299 write_u32(buf, *idx);
300 }
301 Value::TempPointer { .. } | Value::Null => {
303 write_u8(buf, VAL_NULL);
304 }
305 }
306}
307
308#[expect(clippy::cast_possible_truncation)]
309fn encode_list_def(ld: &ListDef, buf: &mut Vec<u8>) {
310 write_def_id(buf, ld.id);
311 write_u16(buf, ld.name.0);
312 write_u32(buf, ld.items.len() as u32);
313 for (name_id, ordinal) in &ld.items {
314 write_u16(buf, name_id.0);
315 write_i32(buf, *ordinal);
316 }
317}
318
319fn encode_list_item(li: &ListItemDef, buf: &mut Vec<u8>) {
320 write_def_id(buf, li.id);
321 write_def_id(buf, li.origin);
322 write_i32(buf, li.ordinal);
323 write_u16(buf, li.name.0);
324}
325
326#[expect(clippy::cast_possible_truncation)]
328pub fn write_section_list_literals(list_literals: &[ListValue], buf: &mut Vec<u8>) {
329 write_u32(buf, list_literals.len() as u32);
330 for lv in list_literals {
331 write_u32(buf, lv.items.len() as u32);
332 for item in &lv.items {
333 write_def_id(buf, *item);
334 }
335 write_u32(buf, lv.origins.len() as u32);
336 for origin in &lv.origins {
337 write_def_id(buf, *origin);
338 }
339 }
340}
341
342fn encode_external(ext: &ExternalFnDef, buf: &mut Vec<u8>) {
343 write_def_id(buf, ext.id);
344 write_u16(buf, ext.name.0);
345 write_u8(buf, ext.arg_count);
346 match ext.fallback {
347 Some(fb) => {
348 write_u8(buf, 1);
349 write_def_id(buf, fb);
350 }
351 None => {
352 write_u8(buf, 0);
353 }
354 }
355}
356
357#[expect(clippy::cast_possible_truncation)]
358fn encode_container(c: &ContainerDef, buf: &mut Vec<u8>) {
359 write_def_id(buf, c.id);
360 write_def_id(buf, c.scope_id);
361 match c.name {
362 Some(name_id) => {
363 write_u8(buf, 1);
364 write_u16(buf, name_id.0);
365 }
366 None => {
367 write_u8(buf, 0);
368 }
369 }
370 write_u8(buf, c.counting_flags.bits());
371 write_i32(buf, c.path_hash);
372 write_u8(buf, c.param_count);
373 write_u8(buf, u8::from(c.local));
374 write_u32(buf, c.bytecode.len() as u32);
375 buf.extend_from_slice(&c.bytecode);
376}
377
378#[expect(clippy::cast_possible_truncation)]
380pub fn write_section_line_tables(line_tables: &[ScopeLineTable], buf: &mut Vec<u8>) {
381 write_u32(buf, line_tables.len() as u32);
382 for lt in line_tables {
383 encode_scope_line_table(lt, buf);
384 }
385}
386
387#[expect(clippy::cast_possible_truncation)]
388fn encode_scope_line_table(lt: &ScopeLineTable, buf: &mut Vec<u8>) {
389 write_def_id(buf, lt.scope_id);
390 write_u32(buf, lt.lines.len() as u32);
391 for entry in <.lines {
392 encode_line_entry(entry, buf);
393 }
394}
395
396fn encode_line_entry(entry: &LineEntry, buf: &mut Vec<u8>) {
397 encode_line_content(&entry.content, buf);
398 write_u64(buf, entry.source_hash);
399 match &entry.audio_ref {
400 Some(audio) => {
401 write_u8(buf, 1);
402 write_str(buf, audio);
403 }
404 None => {
405 write_u8(buf, 0);
406 }
407 }
408
409 #[expect(clippy::cast_possible_truncation)]
411 write_u8(buf, entry.slot_info.len() as u8);
412 for slot in &entry.slot_info {
413 write_u8(buf, slot.index);
414 write_str(buf, &slot.name);
415 }
416
417 match &entry.source_location {
419 Some(loc) => {
420 write_u8(buf, 1);
421 write_str(buf, &loc.file);
422 write_u32(buf, loc.range_start);
423 write_u32(buf, loc.range_end);
424 }
425 None => {
426 write_u8(buf, 0);
427 }
428 }
429}
430
431#[expect(clippy::cast_possible_truncation)]
432pub(crate) fn encode_line_content(content: &LineContent, buf: &mut Vec<u8>) {
433 match content {
434 LineContent::Plain(s) => {
435 write_u8(buf, LINE_PLAIN);
436 write_str(buf, s);
437 }
438 LineContent::Template(parts) => {
439 write_u8(buf, LINE_TEMPLATE);
440 write_u32(buf, parts.len() as u32);
441 for part in parts {
442 encode_line_part(part, buf);
443 }
444 }
445 }
446}
447
448#[expect(clippy::cast_possible_truncation)]
449fn encode_line_part(part: &LinePart, buf: &mut Vec<u8>) {
450 match part {
451 LinePart::Literal(s) => {
452 write_u8(buf, PART_LITERAL);
453 write_str(buf, s);
454 }
455 LinePart::Slot(idx) => {
456 write_u8(buf, PART_SLOT);
457 write_u8(buf, *idx);
458 }
459 LinePart::Select {
460 slot,
461 variants,
462 default,
463 } => {
464 write_u8(buf, PART_SELECT);
465 write_u8(buf, *slot);
466 write_u32(buf, variants.len() as u32);
467 for (key, text) in variants {
468 encode_select_key(key, buf);
469 write_str(buf, text);
470 }
471 write_str(buf, default);
472 }
473 }
474}
475
476fn encode_select_key(key: &SelectKey, buf: &mut Vec<u8>) {
477 match key {
478 SelectKey::Cardinal(cat) => {
479 write_u8(buf, KEY_CARDINAL);
480 encode_plural_category(*cat, buf);
481 }
482 SelectKey::Ordinal(cat) => {
483 write_u8(buf, KEY_ORDINAL);
484 encode_plural_category(*cat, buf);
485 }
486 SelectKey::Exact(n) => {
487 write_u8(buf, KEY_EXACT);
488 write_i32(buf, *n);
489 }
490 SelectKey::Keyword(k) => {
491 write_u8(buf, KEY_KEYWORD);
492 write_str(buf, k);
493 }
494 }
495}
496
497fn encode_plural_category(cat: PluralCategory, buf: &mut Vec<u8>) {
498 let tag = match cat {
499 PluralCategory::Zero => CAT_ZERO,
500 PluralCategory::One => CAT_ONE,
501 PluralCategory::Two => CAT_TWO,
502 PluralCategory::Few => CAT_FEW,
503 PluralCategory::Many => CAT_MANY,
504 PluralCategory::Other => CAT_OTHER,
505 };
506 write_u8(buf, tag);
507}