| 1 | //! Builds a new external-payload test fixture from a native capture with reserved fragment space. |
| 2 | use onestore::{FileDataReference, RevisionIndex, Store}; |
| 3 | use std::{fs, path::PathBuf}; |
| 4 | fn 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 | } |