1//! Builds a new external-payload test fixture from a native capture with reserved fragment space.
2use onestore::{FileDataReference, RevisionIndex, Store};
3use std::{fs, path::PathBuf};
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let args: Vec<_> = std::env::args_os().skip(1).collect();
6 if args.len() != 2 {
7 return Err("Provide a native source section and a new fixture directory".into());
8 }
9 let source = fs::read(&args[0])?;
10 let destination = PathBuf::from(&args[1]);
11 fs::create_dir(&destination)?;
12 fs::create_dir(destination.join("synthetic_onefiles"))?;
13 let store = Store::parse(&source)?;
14 assert!(store.checksum_mismatches.is_empty());
15 RevisionIndex::parse(&store)?.validate_current()?;
16 let mut output = source.clone();
17 let mut changed = 0;
18 for list in store.lists.values() {
19 for chunk in &list.fragments {
20 let start = chunk.offset as usize;
21 let end = start + chunk.length as usize - 20;
22 let mut body = Vec::new();
23 let mut touched = false;
24 for node in list
25 .nodes
26 .iter()
27 .filter(|node| node.offset >= start + 16 && node.offset < end)
28 {
29 let raw = u32::from_le_bytes(source[node.offset..node.offset + 4].try_into()?);
30 let size = ((raw >> 10) & 0x1fff) as usize;
31 let mut encoded = source[node.offset..node.offset + size].to_vec();
32 if matches!(node.id, 0x72 | 0x73) {
33 let offset = 4 + 4 + 4 + if node.id == 0x72 { 1 } else { 4 };
34 let count =
35 u32::from_le_bytes(encoded[offset..offset + 4].try_into()?) as usize;
36 let text = String::from_utf16(
37 &encoded[offset + 4..offset + 4 + count * 2]
38 .chunks_exact(2)
39 .map(|v| u16::from_le_bytes(v.try_into().unwrap()))
40 .collect::<Vec<_>>(),
41 )?;
42 if let FileDataReference::Internal(guid) = text.parse()? {
43 let filename = format!(
44 "{}.onebin",
45 text.strip_prefix("<ifndf>{")
46 .unwrap()
47 .strip_suffix('}')
48 .unwrap()
49 );
50 let payload = store.file_data(guid)?;
51 let path = destination.join("synthetic_onefiles").join(&filename);
52 if path.exists() {
53 assert_eq!(fs::read(&path)?, payload);
54 } else {
55 fs::write(path, payload)?;
56 }
57 let reference = format!("<file>{filename}");
58 let data: Vec<_> = reference
59 .encode_utf16()
60 .flat_map(u16::to_le_bytes)
61 .collect();
62 encoded.splice(offset + 4..offset + 4 + count * 2, data.iter().copied());
63 encoded[offset..offset + 4]
64 .copy_from_slice(&(data.len() as u32 / 2).to_le_bytes());
65 assert!(encoded.len() <= 0x1fff);
66 let header = (raw & !(0x1fff << 10)) | ((encoded.len() as u32) << 10);
67 encoded[..4].copy_from_slice(&header.to_le_bytes());
68 changed += 1;
69 touched = true;
70 }
71 }
72 body.extend(encoded);
73 }
74 if touched {
75 assert!(
76 body.len() + 4 <= end - start - 16,
77 "Native fragment has insufficient reserved space"
78 );
79 body.extend_from_slice(&(0xff_u32 | (4 << 10)).to_le_bytes());
80 output[start + 16..end].fill(0);
81 output[start + 16..start + 16 + body.len()].copy_from_slice(&body);
82 }
83 }
84 }
85 assert!(changed > 0);
86 let converted = Store::parse(&output)?;
87 assert!(converted.checksum_mismatches.is_empty());
88 RevisionIndex::parse(&converted)?.validate_current()?;
89 fs::write(destination.join("synthetic.one"), &output)?;
90 fs::write(
91 destination.join("Open Notebook.onetoc2"),
92 onestore::create_table_of_contents(
93 "Open Notebook.onetoc2",
94 &[("synthetic.one", converted.header.file_id)],
95 )?,
96 )?;
97 println!(
98 "Converted {changed} file-data declarations without changing payload identities or bytes"
99 );
100 Ok(())
101}