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 MESH: &[u8; 4] = b"MESH";
const SKIN: &[u8; 4] = b"SKIN";
const AREA: &[u8; 4] = b"AREA";
const PRMG: &[u8; 4] = b"PRMG";
const INFO: &[u8; 4] = b"INFO";
const STRM: &[u8; 4] = b"STRM";
const DECL: &[u8; 4] = b"decl";
const INFOL: &[u8; 4] = b"info";
const DATA: &[u8; 4] = b"data";
#[derive(Clone, Copy)]
struct DeclElem {
stream: u16,
offset: u16,
typ: u8,
usage: u8,
}
fn decl_type_size(typ: u8) -> usize {
match typ {
16 => 8, 15 => 4, 5 => 4, 8 => 4, 4 => 4, _ => 4,
}
}
fn parse_decl(b: &[u8]) -> Vec<DeclElem> {
let mut v = Vec::new();
for e in 0..b.len() / 8 {
let stream = u16::from_le_bytes([b[e * 8], b[e * 8 + 1]]);
let offset = u16::from_le_bytes([b[e * 8 + 2], b[e * 8 + 3]]);
let typ = b[e * 8 + 4];
let usage = b[e * 8 + 6];
v.push(DeclElem { stream, offset, typ, usage });
}
v
}
fn build_decl(elems: &[DeclElem]) -> (Vec<u8>, usize) {
let mut out = Vec::new();
let mut off = 0usize;
for e in elems {
out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&(off as u16).to_le_bytes());
out.push(e.typ);
out.push(0);
out.push(e.usage);
out.push(0);
off += decl_type_size(e.typ);
}
out.extend_from_slice(&[0xff, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00]);
(out, off)
}
fn find_child(descs: &[Desc], start: usize, end: usize, tag: &[u8; 4]) -> Option<usize> {
(start..=end.min(descs.len() - 1)).find(|&i| &descs[i].tag == tag)
}
fn decode_f16(b: &[u8]) -> f32 {
half_to_f32(u16::from_le_bytes([b[0], b[1]]))
}
fn encode_f16(x: f32) -> [u8; 2] {
f32_to_half(x).to_le_bytes()
}
fn half_to_f32(h: u16) -> f32 {
let sign = ((h >> 15) & 1) as u32;
let exp = ((h >> 10) & 0x1f) as u32;
let mant = (h & 0x3ff) as u32;
let bits = if exp == 0 {
if mant == 0 {
sign << 31
} else {
let mut e = -1i32;
let mut m = mant;
while (m & 0x400) == 0 {
m <<= 1;
e -= 1;
}
m &= 0x3ff;
(sign << 31) | (((127 - 15 + e + 1) as u32) << 23) | (m << 13)
}
} else if exp == 0x1f {
(sign << 31) | (0xff << 23) | (mant << 13)
} else {
(sign << 31) | (((exp as i32 - 15 + 127) as u32) << 23) | (mant << 13)
};
f32::from_bits(bits)
}
fn f32_to_half(x: f32) -> u16 {
let bits = x.to_bits();
let sign = ((bits >> 16) & 0x8000) as u16;
let exp = ((bits >> 23) & 0xff) as i32 - 127 + 15;
let mant = bits & 0x7fffff;
if exp <= 0 {
if exp < -10 {
return sign;
}
let mant = (mant | 0x800000) >> (1 - exp);
let m = ((mant + 0x1000) >> 13) as u16;
sign | m
} else if exp >= 0x1f {
sign | 0x7c00 } else {
let m = ((mant + 0x1000) >> 13) as u16;
sign | (((exp as u16) << 10) + m)
}
}
fn reskin_prmg(
descs: &mut [Desc],
pi: usize,
info_bone: u8,
translation: [f32; 3],
skin_info_template: &[u8; 56],
) -> Result<(), String> {
let span = descs[pi].u4 as usize;
let end = pi + span;
let strm_pre = find_child(descs, pi + 1, end, STRM).ok_or("PRMG: no STRM")?;
let strm_pre_end = strm_pre + descs[strm_pre].u4 as usize;
let decl_pre = find_child(descs, strm_pre + 1, strm_pre_end, DECL).ok_or("STRM: no decl")?;
let src_decl_has_tangent = parse_decl(&descs[decl_pre].body)
.iter()
.any(|e| e.stream != 255 && e.usage == 6);
let info_i = find_child(descs, pi + 1, end, INFO).ok_or("PRMG: no INFO")?;
let has_tangent = src_decl_has_tangent;
let mut ni = skin_info_template.to_vec();
if has_tangent {
ni[12..16].copy_from_slice(&0xb2677dd7u32.to_le_bytes()); ni[16..20].copy_from_slice(&0xdd3622c2u32.to_le_bytes()); }
ni[24..28].copy_from_slice(&((1u32 << 16) | info_bone as u32).to_le_bytes());
descs[info_i].body = ni;
descs[info_i].size = 56;
let strm_i = find_child(descs, pi + 1, end, STRM).ok_or("PRMG: no STRM")?;
let strm_end = strm_i + descs[strm_i].u4 as usize;
let sinfo = find_child(descs, strm_i + 1, strm_end, INFOL).ok_or("STRM: no info")?;
let sdecl = find_child(descs, strm_i + 1, strm_end, DECL).ok_or("STRM: no decl")?;
let sdata = find_child(descs, strm_i + 1, strm_end, DATA).ok_or("STRM: no data")?;
if descs[sinfo].body.len() != 12 {
return Err("STRM info not 12 bytes".into());
}
let field0 = rd_u32(&descs[sinfo].body, 0);
let old_stride = rd_u32(&descs[sinfo].body, 4) as usize;
let vcount = rd_u32(&descs[sinfo].body, 8) as usize;
let src_elems = parse_decl(&descs[sdecl].body);
if src_elems.iter().any(|e| e.stream != 255 && (e.usage == 1 || e.usage == 2)) {
return Err("STRM already has blend channels".into());
}
let normal_pos = src_elems
.iter()
.position(|e| e.stream != 255 && e.usage == 3)
.ok_or("STRM decl has no NORMAL element")?;
let mut new_elems: Vec<DeclElem> = Vec::new();
let mut copy_plan: Vec<(usize, usize)> = Vec::new(); for (i, e) in src_elems.iter().enumerate() {
if e.stream == 255 {
continue;
}
if i == normal_pos {
new_elems.push(DeclElem { stream: 0, offset: 0, typ: 5, usage: 2 }); new_elems.push(DeclElem { stream: 0, offset: 0, typ: 8, usage: 1 }); }
new_elems.push(*e);
copy_plan.push((e.offset as usize, decl_type_size(e.typ)));
}
let (new_decl, new_stride) = build_decl(&new_elems);
let _ = field0;
let new_field0 = (new_elems.len() + 1) as u32; let mut ninfo = Vec::with_capacity(12);
ninfo.extend_from_slice(&new_field0.to_le_bytes());
ninfo.extend_from_slice(&(new_stride as u32).to_le_bytes());
ninfo.extend_from_slice(&(vcount as u32).to_le_bytes());
descs[sinfo].body = ninfo;
descs[sdecl].body = new_decl;
descs[sdecl].size = descs[sdecl].body.len() as u32;
let src = descs[sdata].body.clone();
if src.len() != old_stride * vcount {
return Err(format!("eye data size {} != {old_stride}*{vcount}", src.len()));
}
let mut dst = Vec::with_capacity(new_stride * vcount);
for v in 0..vcount {
let base = v * old_stride;
let mut ci = 0usize; for (i, e) in src_elems.iter().enumerate() {
if e.stream == 255 {
continue;
}
if i == normal_pos {
dst.extend_from_slice(&[0, 0, 0, 0]); dst.extend_from_slice(&[0xff, 0, 0, 0]); }
let (soff, sz) = copy_plan[ci];
if i == 0 && e.usage == 0 && sz >= 6 {
let px = decode_f16(&src[base + soff..]) + translation[0];
let py = decode_f16(&src[base + soff + 2..]) + translation[1];
let pz = decode_f16(&src[base + soff + 4..]) + translation[2];
dst.extend_from_slice(&encode_f16(px));
dst.extend_from_slice(&encode_f16(py));
dst.extend_from_slice(&encode_f16(pz));
dst.extend_from_slice(&src[base + soff + 6..base + soff + sz]); } else {
dst.extend_from_slice(&src[base + soff..base + soff + sz]);
}
ci += 1;
}
}
if dst.len() != new_stride * vcount {
return Err(format!("repack produced {} bytes, expected {new_stride}*{vcount}", dst.len()));
}
descs[sdata].body = dst;
descs[sdata].size = (new_stride * vcount) as u32;
Ok(())
}
fn fix_prmg_child_u3(descs: &mut [Desc], pi: usize, remove: &[bool]) {
let n = descs.len();
let pend = (pi + descs[pi].u4 as usize).min(n - 1);
let mut children: Vec<usize> = Vec::new();
let mut c = pi + 1;
while c <= pend {
children.push(c);
c += descs[c].u4 as usize + 1;
}
let removed_ridx: Vec<u32> = children.iter().filter(|&&c| remove[c]).map(|&c| descs[c].u3).collect();
for &c in &children {
if remove[c] {
continue;
}
let dec = removed_ridx.iter().filter(|&&r| r < descs[c].u3).count() as u32;
descs[c].u3 -= dec;
}
}
fn read_mat4(b: &[u8], dw: usize) -> [[f32; 4]; 4] {
let mut m = [[0f32; 4]; 4];
for r in 0..4 {
for c in 0..4 {
let o = (dw + r * 4 + c) * 4;
m[r][c] = f32::from_le_bytes([b[o], b[o + 1], b[o + 2], b[o + 3]]);
}
}
m
}
fn mul_row(v: [f32; 4], m: &[[f32; 4]; 4]) -> [f32; 4] {
let mut o = [0f32; 4];
for c in 0..4 {
o[c] = v[0] * m[0][c] + v[1] * m[1][c] + v[2] * m[2][c] + v[3] * m[3][c];
}
o
}
fn mul_mat(a: &[[f32; 4]; 4], b: &[[f32; 4]; 4]) -> [[f32; 4]; 4] {
let mut o = [[0f32; 4]; 4];
for r in 0..4 {
for c in 0..4 {
o[r][c] = a[r][0] * b[0][c] + a[r][1] * b[1][c] + a[r][2] * b[2][c] + a[r][3] * b[3][c];
}
}
o
}
fn bone_world_translation(hier: &[u8], bone: usize) -> Result<[f32; 3], String> {
const BONE_SZ: usize = 176;
let nb = hier.len() / BONE_SZ;
if bone >= nb {
return Err(format!("bone {bone} >= {nb}"));
}
let parent_of = |bi: usize| -> i32 {
let d2 = u32::from_le_bytes([
hier[bi * BONE_SZ + 8],
hier[bi * BONE_SZ + 9],
hier[bi * BONE_SZ + 10],
hier[bi * BONE_SZ + 11],
]);
let p = (d2 & 0xffff) as usize;
if p < nb && p != bi { p as i32 } else { -1 }
};
let local = |bi: usize| read_mat4(&hier[bi * BONE_SZ..(bi + 1) * BONE_SZ], 4);
let mut world = local(bone);
let mut visited = vec![bone];
let mut cur = parent_of(bone);
while cur >= 0 && !visited.contains(&(cur as usize)) {
let p = cur as usize;
world = mul_mat(&world, &local(p));
visited.push(p);
cur = parent_of(p);
}
let t = mul_row([0.0, 0.0, 0.0, 1.0], &world);
Ok([t[0], t[1], t[2]])
}
pub fn reskin_container_eyes(
container: &mut Vec<u8>,
bones: &[u8],
obama_eye_info: &[u8; 56],
) -> Result<usize, 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<Desc> = 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 {
container[data_base + u0 as usize..data_base + u0 as usize + size as usize].to_vec()
} else {
Vec::new()
};
descs.push(Desc { tag, u0, size, u3, u4, body });
}
let mesh_idx: Vec<usize> = (0..n_desc).filter(|&i| &descs[i].tag == MESH).collect();
if mesh_idx.is_empty() {
return Ok(0);
}
if bones.len() < mesh_idx.len() {
return Err(format!("need {} bone(s), got {}", mesh_idx.len(), bones.len()));
}
let mut remove = vec![false; n_desc];
for i in 0..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;
}
}
}
}
const HEAD_BONE: u8 = 25;
let hier = descs
.iter()
.find(|d| &d.tag == b"HIER")
.map(|d| d.body.clone())
.ok_or("no HIER chunk")?;
for (mi, &m) in mesh_idx.iter().enumerate() {
let eye_bone = bones[mi];
let translation = bone_world_translation(&hier, eye_bone as usize)?;
let mend = m + descs[m].u4 as usize;
let mut c = m + 1;
while c <= mend {
if &descs[c].tag == PRMG {
reskin_prmg(&mut descs, c, HEAD_BONE, translation, obama_eye_info)?;
c += descs[c].u4 as usize + 1;
} else {
c += 1;
}
}
descs[m].tag = *SKIN;
}
for &m in &mesh_idx {
let mend = m + descs[m].u4 as usize;
let mut c = m + 1;
while c <= mend {
if &descs[c].tag == PRMG {
fix_prmg_child_u3(&mut descs, c, &remove);
c += descs[c].u4 as usize + 1;
} else {
c += 1;
}
}
}
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 {
descs[j].u4 -= removed_prefix[hi + 1] - removed_prefix[lo];
}
}
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[8..12]);
out.extend_from_slice(&container[12..16]);
out.extend_from_slice(&(new_n as u32).to_le_bytes());
let mut body_region: Vec<u8> = Vec::new();
let mut rows: 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.extend_from_slice(&d.tag);
rows.extend_from_slice(&u0_out.to_le_bytes());
rows.extend_from_slice(&d.size.to_le_bytes());
rows.extend_from_slice(&d.u3.to_le_bytes());
rows.extend_from_slice(&d.u4.to_le_bytes());
}
out.extend_from_slice(&rows);
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());
*container = out;
Ok(mesh_idx.len())
}