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 .as_chunks::<2>()
39 .0
40 .iter()
41 .map(|v| u16::from_le_bytes(*v))
42 .collect::<Vec<_>>(),
43 )?;
44 if let FileDataReference::Internal(guid) = text.parse()? {
45 let filename = format!(
46 "{}.onebin",
47 text.strip_prefix("<ifndf>{")
48 .unwrap()
49 .strip_suffix('}')
50 .unwrap()
51 );
52 let payload = store.file_data(guid)?;
53 let path = destination.join("synthetic_onefiles").join(&filename);
54 if path.exists() {
55 assert_eq!(fs::read(&path)?, payload);
56 } else {
57 fs::write(path, payload)?;
58 }
59 let reference = format!("<file>{filename}");
60 let data: Vec<_> = reference
61 .encode_utf16()
62 .flat_map(u16::to_le_bytes)
63 .collect();
64 encoded.splice(offset + 4..offset + 4 + count * 2, data.iter().copied());
65 encoded[offset..offset + 4]
66 .copy_from_slice(&(data.len() as u32 / 2).to_le_bytes());
67 assert!(encoded.len() <= 0x1fff);
68 let header = (raw & !(0x1fff << 10)) | ((encoded.len() as u32) << 10);
69 encoded[..4].copy_from_slice(&header.to_le_bytes());
70 changed += 1;
71 touched = true;
72 }
73 }
74 body.extend(encoded);
75 }
76 if touched {
77 assert!(
78 body.len() + 4 <= end - start - 16,
79 "Native fragment has insufficient reserved space"
80 );
81 body.extend_from_slice(&(0xff_u32 | (4 << 10)).to_le_bytes());
82 output[start + 16..end].fill(0);
83 output[start + 16..start + 16 + body.len()].copy_from_slice(&body);
84 }
85 }
86 }
87 assert!(changed > 0);
88 let converted = Store::parse(&output)?;
89 assert!(converted.checksum_mismatches.is_empty());
90 RevisionIndex::parse(&converted)?.validate_current()?;
91 fs::write(destination.join("synthetic.one"), &output)?;
92 fs::write(
93 destination.join("Open Notebook.onetoc2"),
94 onestore::create_table_of_contents(
95 "Open Notebook.onetoc2",
96 &[("synthetic.one", converted.header.file_id)],
97 )?,
98 )?;
99 println!(
100 "Converted {changed} file-data declarations without changing payload identities or bytes"
101 );
102 Ok(())
103}