use super::tree::{EditNode, EditTree, Extent, ExtentMapping, Payload};
use std::io::{Read, Seek, SeekFrom};
#[derive(Debug, Default)]
pub struct FixupStats {
pub entries_adjusted: usize,
pub entries_unmapped: usize,
}
pub fn fix_chunk_offsets<R: Read + Seek>(
src: &mut R,
tree: &mut EditTree,
map: &[ExtentMapping],
) -> anyhow::Result<FixupStats> {
let mut sorted: Vec<ExtentMapping> = map.to_vec();
sorted.sort_by_key(|m| m.old_offset);
let mut stats = FixupStats::default();
for node in &mut tree.roots {
fix_node(src, node, &sorted, &mut stats)?;
}
Ok(stats)
}
fn fix_node<R: Read + Seek>(
src: &mut R,
node: &mut EditNode,
map: &[ExtentMapping],
stats: &mut FixupStats,
) -> anyhow::Result<()> {
let is_stco = &node.typ.0 == b"stco";
let is_co64 = &node.typ.0 == b"co64";
if is_stco || is_co64 {
if let Payload::Extent(e) = &node.payload {
let bytes = read_extent(src, e)?;
if let Some(patched) = remap_entries(bytes, is_co64, map, stats) {
node.payload = Payload::Bytes(patched);
}
}
return Ok(());
}
if let Payload::Container { children, .. } = &mut node.payload {
for c in children {
fix_node(src, c, map, stats)?;
}
}
Ok(())
}
fn read_extent<R: Read + Seek>(src: &mut R, e: &Extent) -> anyhow::Result<Vec<u8>> {
let mut buf = vec![0u8; e.len as usize];
src.seek(SeekFrom::Start(e.offset))?;
src.read_exact(&mut buf)?;
Ok(buf)
}
fn remap_entries(
mut payload: Vec<u8>,
is_co64: bool,
map: &[ExtentMapping],
stats: &mut FixupStats,
) -> Option<Vec<u8>> {
if payload.len() < 8 {
return None;
}
let entry_count = u32::from_be_bytes(payload[4..8].try_into().unwrap()) as usize;
let width = if is_co64 { 8 } else { 4 };
let mut changed = false;
for i in 0..entry_count {
let at = 8 + i * width;
if at + width > payload.len() {
break;
}
let old = if is_co64 {
u64::from_be_bytes(payload[at..at + 8].try_into().unwrap())
} else {
u32::from_be_bytes(payload[at..at + 4].try_into().unwrap()) as u64
};
let Some(new) = remap_offset(old, map) else {
stats.entries_unmapped += 1;
continue;
};
if new == old {
continue;
}
if is_co64 {
payload[at..at + 8].copy_from_slice(&new.to_be_bytes());
} else {
let Ok(new32) = u32::try_from(new) else {
stats.entries_unmapped += 1;
continue; };
payload[at..at + 4].copy_from_slice(&new32.to_be_bytes());
}
stats.entries_adjusted += 1;
changed = true;
}
changed.then_some(payload)
}
fn remap_offset(old: u64, sorted: &[ExtentMapping]) -> Option<u64> {
let idx = sorted.partition_point(|m| m.old_offset <= old);
let m = sorted.get(idx.checked_sub(1)?)?;
if old < m.old_offset + m.len {
Some(m.new_offset + (old - m.old_offset))
} else {
None
}
}