use mercs2_formats::crc32::crc32_mercs2;
fn rd_u32(b: &[u8], o: usize) -> u32 {
u32::from_le_bytes([b[o], b[o + 1], b[o + 2], b[o + 3]])
}
#[derive(Clone)]
struct Desc {
tag: [u8; 4],
u0: u32, size: u32, u3: u32, u4: u32, body: Vec<u8>, }
const AREA: &[u8; 4] = b"AREA";
const MESH: &[u8; 4] = b"MESH";
const GEOM: &[u8; 4] = b"GEOM";
const INDX: &[u8; 4] = b"INDX";
const INFO: &[u8; 4] = b"INFO";
const SEGM: &[u8; 4] = b"SEGM";
fn parse_descs(container: &[u8]) -> Result<(usize, usize, Vec<Desc>), String> {
if container.len() < 20 || &container[0..4] != b"UCFX" {
return Err("not a UCFX container".into());
}
let data_base = rd_u32(container, 4) as usize;
let n_desc = rd_u32(container, 16) as usize;
if 20 + n_desc * 20 > container.len() {
return Err("descriptor table out of range".into());
}
let mut descs = Vec::with_capacity(n_desc);
for d in 0..n_desc {
let o = 20 + d * 20;
let mut tag = [0u8; 4];
tag.copy_from_slice(&container[o..o + 4]);
let u0 = rd_u32(container, o + 4);
let size = rd_u32(container, o + 8);
let u3 = rd_u32(container, o + 12);
let u4 = rd_u32(container, o + 16);
let body = if u0 != 0xFFFF_FFFF && size > 0 {
let start = data_base + u0 as usize;
let end = start + size as usize;
if end > container.len() {
return Err(format!("body of desc {d} ({:?}) out of range", tag));
}
container[start..end].to_vec()
} else {
Vec::new()
};
descs.push(Desc { tag, u0, size, u3, u4, body });
}
Ok((data_base, n_desc, descs))
}
fn rebuild_without(
container_head_u1: &[u8],
container_head_u2: &[u8],
descs: &mut [Desc],
remove: &[bool],
) -> Vec<u8> {
let n_desc = descs.len();
let mut removed_prefix = vec![0u32; n_desc + 1];
for j in 0..n_desc {
removed_prefix[j + 1] = removed_prefix[j] + if remove[j] { 1 } else { 0 };
}
for j in 0..n_desc {
if remove[j] || descs[j].u4 == 0 {
continue;
}
let lo = j + 1;
let hi = (j + descs[j].u4 as usize).min(n_desc - 1);
if lo <= hi {
let removed_in = removed_prefix[hi + 1] - removed_prefix[lo];
descs[j].u4 -= removed_in;
}
}
let survivors: Vec<&Desc> =
descs.iter().enumerate().filter(|(j, _)| !remove[*j]).map(|(_, d)| d).collect();
let new_n = survivors.len();
let new_data_base = 20 + new_n * 20;
let mut out = Vec::new();
out.extend_from_slice(b"UCFX");
out.extend_from_slice(&(new_data_base as u32).to_le_bytes());
out.extend_from_slice(container_head_u1);
out.extend_from_slice(container_head_u2);
out.extend_from_slice(&(new_n as u32).to_le_bytes());
let mut body_region: Vec<u8> = Vec::new();
let mut rows_bytes: Vec<u8> = Vec::with_capacity(new_n * 20);
for d in &survivors {
let u0_out = if d.u0 != 0xFFFF_FFFF && d.size > 0 {
let off = body_region.len() as u32;
body_region.extend_from_slice(&d.body);
off
} else {
d.u0
};
rows_bytes.extend_from_slice(&d.tag);
rows_bytes.extend_from_slice(&u0_out.to_le_bytes());
rows_bytes.extend_from_slice(&d.size.to_le_bytes());
rows_bytes.extend_from_slice(&d.u3.to_le_bytes());
rows_bytes.extend_from_slice(&d.u4.to_le_bytes());
}
out.extend_from_slice(&rows_bytes);
out.extend_from_slice(&body_region);
let csum = crc32_mercs2(&out);
out.extend_from_slice(b"CSUM");
out.extend_from_slice(&csum.to_le_bytes());
out
}
pub fn unwrap_container_mesh(container: &mut Vec<u8>) -> Result<usize, String> {
let (_db, n_desc, mut descs) = parse_descs(container)?;
let mut remove = vec![false; n_desc];
let mut area_groups = 0usize;
let mut i = 0usize;
while i < n_desc {
if &descs[i].tag == AREA {
let span = descs[i].u4 as usize;
for k in 0..=span {
if i + k < n_desc {
remove[i + k] = true;
}
}
area_groups += 1;
i += span + 1;
} else {
i += 1;
}
}
if area_groups == 0 {
return Ok(0);
}
let u1 = container[8..12].to_vec();
let u2 = container[12..16].to_vec();
*container = rebuild_without(&u1, &u2, &mut descs, &remove);
Ok(area_groups)
}
pub fn drop_container_mesh_slots(container: &mut Vec<u8>) -> Result<usize, String> {
let (_db, n_desc, mut descs) = parse_descs(container)?;
let mut remove = vec![false; n_desc];
let mut dropped = 0usize;
let mut i = 0usize;
while i < n_desc {
if &descs[i].tag == MESH {
let span = descs[i].u4 as usize;
for k in 0..=span {
if i + k < n_desc {
remove[i + k] = true;
}
}
dropped += 1;
i += span + 1;
} else {
i += 1;
}
}
if dropped == 0 {
return Ok(0);
}
let geom_idx = descs.iter().position(|d| &d.tag == GEOM);
if let Some(gi) = geom_idx {
if let Some(info_i) = (gi + 1..n_desc)
.find(|&j| &descs[j].tag == INFO && descs[j].size == 4 && !remove[j])
{
if descs[info_i].body.len() == 4 {
let old = rd_u32(&descs[info_i].body, 0);
let new = old.saturating_sub(dropped as u32);
descs[info_i].body = new.to_le_bytes().to_vec();
}
}
if let Some(indx_i) = (gi + 1..n_desc).find(|&j| &descs[j].tag == INDX && !remove[j]) {
let b = &descs[indx_i].body;
let entries = b.len() / 2;
let keep = entries.saturating_sub(dropped);
descs[indx_i].body = b[..keep * 2].to_vec();
descs[indx_i].size = (keep * 2) as u32;
}
let geom_span = descs[gi].u4 as usize;
let geom_end = (gi + geom_span).min(n_desc - 1);
let mut c = gi + 1;
while c <= geom_end {
if !remove[c] {
descs[c].u3 = descs[c].u3.saturating_sub(dropped as u32);
}
c += descs[c].u4 as usize + 1;
}
}
if let Some(seg_i) = descs.iter().position(|d| &d.tag == SEGM && d.size > 0) {
let rec = 4usize;
let recs = descs[seg_i].body.len() / rec;
let keep = recs.saturating_sub(dropped);
descs[seg_i].body.truncate(keep * rec);
descs[seg_i].size = (keep * rec) as u32;
}
let u1 = container[8..12].to_vec();
let u2 = container[12..16].to_vec();
*container = rebuild_without(&u1, &u2, &mut descs, &remove);
Ok(dropped)
}