#![allow(clippy::trivially_copy_pass_by_ref, clippy::needless_range_loop)]
use byteorder::{LittleEndian, ReadBytesExt};
use super::super::utils::half_to_f32;
use super::types::{
Bone, BoneBinding, ClothFlags, Gr2ContentInfo, MeshData, MeshExtendedData, MeshPropertySet,
Model, ModelFlags, Skeleton, TopologyGroup, Transform, Vertex,
};
use super::vertex_types::{MemberDef, MemberType, SectionHeader, VertexType};
use crate::error::{Error, Result};
use crate::formats::gr2::bitknit_decompress as decompress_bitknit;
use crate::formats::gr2::magic;
pub struct Gr2Reader {
pub data: Vec<u8>,
pub is_64bit: bool,
section_offsets: Vec<usize>,
}
impl Gr2Reader {
pub fn new(file_data: &[u8]) -> Result<Self> {
if file_data.len() < 16 {
return Err(Error::DecompressionError("GR2 file too small".to_string()));
}
let sig: [u8; 16] = file_data[0..16].try_into().unwrap();
let is_64bit = if sig == magic::LE64 || sig == magic::LE64_V2 {
true
} else if sig == magic::LE32 {
false
} else {
return Err(Error::DecompressionError(
"Invalid GR2 magic signature".to_string(),
));
};
let mut cursor = std::io::Cursor::new(&file_data[0x20..]);
let version = cursor.read_u32::<LittleEndian>()?;
if version != 6 && version != 7 {
return Err(Error::DecompressionError(format!(
"Unsupported GR2 version: {version}"
)));
}
cursor.set_position(12);
let sections_offset = cursor.read_u32::<LittleEndian>()?;
let num_sections = cursor.read_u32::<LittleEndian>()?;
let section_header_pos = 0x20 + sections_offset as usize;
let mut sections = Vec::with_capacity(num_sections as usize);
for i in 0..num_sections as usize {
let offset = section_header_pos + i * 44;
let mut c = std::io::Cursor::new(&file_data[offset..]);
let compression = c.read_u32::<LittleEndian>()?;
let offset_in_file = c.read_u32::<LittleEndian>()?;
let compressed_size = c.read_u32::<LittleEndian>()?;
let uncompressed_size = c.read_u32::<LittleEndian>()?;
c.set_position(28);
let relocations_offset = c.read_u32::<LittleEndian>()?;
let num_relocations = c.read_u32::<LittleEndian>()?;
sections.push(SectionHeader {
compression,
offset_in_file,
compressed_size,
uncompressed_size,
relocations_offset,
num_relocations,
});
}
let total_size: usize = sections.iter().map(|s| s.uncompressed_size as usize).sum();
let mut data = vec![0u8; total_size];
let mut section_offsets = Vec::with_capacity(sections.len());
let mut current_offset = 0usize;
for section in §ions {
section_offsets.push(current_offset);
if section.compressed_size == 0 {
current_offset += section.uncompressed_size as usize;
continue;
}
let start = section.offset_in_file as usize;
let end = start + section.compressed_size as usize;
let compressed = &file_data[start..end];
let decompressed = match section.compression {
0 => compressed.to_vec(),
4 => decompress_bitknit(compressed, section.uncompressed_size as usize)?,
c => {
return Err(Error::DecompressionError(format!(
"Unsupported compression: {c}"
)));
}
};
let dest_end = current_offset + decompressed.len();
data[current_offset..dest_end].copy_from_slice(&decompressed);
current_offset += section.uncompressed_size as usize;
}
for (section_idx, section) in sections.iter().enumerate() {
if section.num_relocations == 0 {
continue;
}
let rel_data = if section.compression == 4 {
let rel_offset = section.relocations_offset as usize;
let rel_compressed_size = u32::from_le_bytes([
file_data[rel_offset],
file_data[rel_offset + 1],
file_data[rel_offset + 2],
file_data[rel_offset + 3],
]) as usize;
let rel_compressed =
&file_data[rel_offset + 4..rel_offset + 4 + rel_compressed_size];
decompress_bitknit(rel_compressed, section.num_relocations as usize * 12)?
} else {
let rel_offset = section.relocations_offset as usize;
let rel_size = section.num_relocations as usize * 12;
file_data[rel_offset..rel_offset + rel_size].to_vec()
};
for i in 0..section.num_relocations as usize {
let offset_in_section = u32::from_le_bytes([
rel_data[i * 12],
rel_data[i * 12 + 1],
rel_data[i * 12 + 2],
rel_data[i * 12 + 3],
]) as usize;
let target_section = u32::from_le_bytes([
rel_data[i * 12 + 4],
rel_data[i * 12 + 5],
rel_data[i * 12 + 6],
rel_data[i * 12 + 7],
]) as usize;
let target_offset = u32::from_le_bytes([
rel_data[i * 12 + 8],
rel_data[i * 12 + 9],
rel_data[i * 12 + 10],
rel_data[i * 12 + 11],
]) as usize;
let src_addr = section_offsets[section_idx] + offset_in_section;
let target_addr = section_offsets[target_section] + target_offset;
if is_64bit {
let bytes = (target_addr as u64).to_le_bytes();
data[src_addr..src_addr + 8].copy_from_slice(&bytes);
} else {
let bytes = (target_addr as u32).to_le_bytes();
data[src_addr..src_addr + 4].copy_from_slice(&bytes);
}
}
}
Ok(Self {
data,
is_64bit,
section_offsets,
})
}
fn ptr_size(&self) -> usize {
if self.is_64bit { 8 } else { 4 }
}
fn read_ptr(&self, offset: usize) -> usize {
if self.is_64bit {
u64::from_le_bytes(self.data[offset..offset + 8].try_into().unwrap()) as usize
} else {
u32::from_le_bytes(self.data[offset..offset + 4].try_into().unwrap()) as usize
}
}
fn read_u32(&self, offset: usize) -> u32 {
u32::from_le_bytes(self.data[offset..offset + 4].try_into().unwrap())
}
fn read_u16(&self, offset: usize) -> u16 {
u16::from_le_bytes(self.data[offset..offset + 2].try_into().unwrap())
}
fn read_i16(&self, offset: usize) -> i16 {
i16::from_le_bytes(self.data[offset..offset + 2].try_into().unwrap())
}
fn read_i32(&self, offset: usize) -> i32 {
i32::from_le_bytes(self.data[offset..offset + 4].try_into().unwrap())
}
fn read_f32(&self, offset: usize) -> f32 {
f32::from_le_bytes(self.data[offset..offset + 4].try_into().unwrap())
}
fn read_f16(&self, offset: usize) -> f32 {
half_to_f32(self.read_u16(offset))
}
fn read_string(&self, offset: usize) -> String {
if offset == 0 || offset >= self.data.len() {
return String::new();
}
let mut end = offset;
while end < self.data.len() && self.data[end] != 0 {
end += 1;
}
String::from_utf8_lossy(&self.data[offset..end]).to_string()
}
fn read_string_ptr(&self, offset: usize) -> String {
let ptr = self.read_ptr(offset);
self.read_string(ptr)
}
fn parse_vertex_type(&self, offset: usize) -> VertexType {
let member_size = if self.is_64bit { 44 } else { 32 };
let mut members = Vec::new();
let mut pos = offset;
for _ in 0..30 {
if pos + member_size > self.data.len() {
break;
}
let type_val = self.read_u32(pos);
if type_val == 0 {
break;
}
let name = self.read_string_ptr(pos + 4);
let array_size_offset = if self.is_64bit { 20 } else { 12 };
let array_size = self.read_u32(pos + array_size_offset);
members.push(MemberDef {
name,
member_type: MemberType::from_u32(type_val),
array_size: array_size.max(1),
});
pos += member_size;
}
VertexType { members }
}
fn read_vertex(&self, offset: usize, vertex_type: &VertexType) -> Vertex {
let mut vertex = Vertex::default();
let mut pos = offset;
for member in &vertex_type.members {
match member.name.as_str() {
"Position" => {
for i in 0..3.min(member.array_size as usize) {
vertex.position[i] = self.read_f32(pos + i * 4);
}
}
"BoneWeights" => {
for i in 0..4.min(member.array_size as usize) {
vertex.bone_weights[i] = self.data[pos + i];
}
}
"BoneIndices" => {
for i in 0..4.min(member.array_size as usize) {
vertex.bone_indices[i] = self.data[pos + i];
}
}
"QTangent" => {
for i in 0..4.min(member.array_size as usize) {
vertex.qtangent[i] = self.read_i16(pos + i * 2);
}
}
"DiffuseColor0" => {
for i in 0..4.min(member.array_size as usize) {
vertex.color[i] = self.data[pos + i];
}
}
"TextureCoordinates0" => match member.member_type {
MemberType::Real16 => {
vertex.uv[0] = self.read_f16(pos);
vertex.uv[1] = self.read_f16(pos + 2);
}
MemberType::Real32 => {
vertex.uv[0] = self.read_f32(pos);
vertex.uv[1] = self.read_f32(pos + 4);
}
_ => {}
},
_ => {}
}
pos += member.total_size();
}
vertex
}
fn parse_mesh_property_set(&self, ptr: usize) -> Option<MeshPropertySet> {
if ptr == 0 || ptr + 44 > self.data.len() {
return None;
}
Some(MeshPropertySet {
model_flags: ModelFlags::from_u32(self.read_u32(ptr)),
cloth_flags: ClothFlags::from_u32(self.read_u32(ptr + 8)),
lod_distance: self.read_f32(ptr + 36),
is_impostor: self.read_i32(ptr + 40) != 0,
})
}
fn parse_mesh_extended_data(&self, ptr: usize) -> Option<MeshExtendedData> {
if ptr == 0 || ptr + 44 > self.data.len() {
return None;
}
let ptr_size = self.ptr_size();
let base = ptr;
Some(MeshExtendedData {
mesh_proxy: self.read_i32(base),
rigid: self.read_i32(base + 4),
cloth: self.read_i32(base + 8),
spring: self.read_i32(base + 12),
occluder: self.read_i32(base + 16),
lod: self.read_i32(base + 20),
user_defined_properties: {
let p = self.read_ptr(base + 24);
if p != 0 && p < self.data.len() {
Some(self.read_string(p))
} else {
None
}
},
mesh_properties: self.parse_mesh_property_set(self.read_ptr(base + 24 + ptr_size)),
})
}
pub fn parse_meshes(&self, file_data: &[u8]) -> Result<Vec<MeshData>> {
let mut cursor = std::io::Cursor::new(&file_data[0x20..]);
cursor.set_position(28);
let root_section = cursor.read_u32::<LittleEndian>()? as usize;
let root_offset = cursor.read_u32::<LittleEndian>()? as usize;
let root_addr = self.section_offsets[root_section] + root_offset;
let ptr_size = self.ptr_size();
let array_size = 4 + ptr_size;
let mut pos = root_addr;
pos += ptr_size * 3; pos += array_size * 4; pos += array_size;
let mesh_count = self.read_u32(pos) as usize;
let meshes_ptr = self.read_ptr(pos + 4);
let mut meshes = Vec::with_capacity(mesh_count);
for i in 0..mesh_count {
let mesh_ptr = self.read_ptr(meshes_ptr + i * ptr_size);
if mesh_ptr == 0 || mesh_ptr >= self.data.len() {
continue;
}
let name = self.read_string_ptr(mesh_ptr);
let vertex_data_ptr = self.read_ptr(mesh_ptr + ptr_size);
let topology_ptr = self.read_ptr(mesh_ptr + ptr_size * 2 + array_size);
let ext_offset = mesh_ptr + ptr_size * 3 + array_size * 3;
let ext_type_ptr = self.read_ptr(ext_offset);
let ext_data_ptr = self.read_ptr(ext_offset + ptr_size);
let extended_data = if ext_type_ptr != 0 && ext_data_ptr != 0 {
self.parse_mesh_extended_data(ext_data_ptr)
} else {
None
};
let vertices = if vertex_data_ptr > 0 && vertex_data_ptr < self.data.len() {
let type_ptr = self.read_ptr(vertex_data_ptr);
let count = self.read_u32(vertex_data_ptr + 8) as usize;
let data_ptr = self.read_ptr(vertex_data_ptr + 12);
let vt = if type_ptr > 0 {
self.parse_vertex_type(type_ptr)
} else {
VertexType { members: vec![] }
};
let stride = vt.stride();
let mut verts = Vec::with_capacity(count);
if data_ptr > 0 && stride > 0 {
for j in 0..count {
let offset = data_ptr + j * stride;
if offset + stride <= self.data.len() {
verts.push(self.read_vertex(offset, &vt));
}
}
}
verts
} else {
Vec::new()
};
let mat_bind_offset = mesh_ptr + ptr_size * 2 + array_size + ptr_size;
let material_binding_names = self.parse_material_bindings(mat_bind_offset);
let bone_bind_offset = mat_bind_offset + array_size;
let bone_bindings = self.parse_bone_bindings(bone_bind_offset);
let (indices, is_32bit, topology_groups) =
if topology_ptr > 0 && topology_ptr < self.data.len() {
let groups = self.parse_topology_groups(topology_ptr);
let idx32_count = self.read_u32(topology_ptr + 12) as usize;
let idx32_ptr = self.read_ptr(topology_ptr + 16);
let idx16_count = self.read_u32(topology_ptr + 24) as usize;
let idx16_ptr = self.read_ptr(topology_ptr + 28);
if idx32_count > 0 && idx32_ptr > 0 {
let mut inds = Vec::with_capacity(idx32_count);
for j in 0..idx32_count {
let idx_offset = idx32_ptr + j * 4;
if idx_offset + 4 <= self.data.len() {
inds.push(self.read_u32(idx_offset));
}
}
(inds, true, groups)
} else if idx16_count > 0 && idx16_ptr > 0 {
let mut inds = Vec::with_capacity(idx16_count);
for j in 0..idx16_count {
let idx_offset = idx16_ptr + j * 2;
if idx_offset + 2 <= self.data.len() {
inds.push(u32::from(self.read_u16(idx_offset)));
}
}
(inds, false, groups)
} else {
(Vec::new(), false, groups)
}
} else {
(Vec::new(), false, Vec::new())
};
if !vertices.is_empty() {
meshes.push(MeshData {
name,
vertices,
indices,
is_32bit_indices: is_32bit,
extended_data,
bone_bindings,
material_binding_names,
topology_groups,
});
}
}
Ok(meshes)
}
fn read_transform(&self, offset: usize) -> Transform {
let mut transform = Transform::default();
for i in 0..3 {
transform.translation[i] = self.read_f32(offset + 4 + i * 4);
}
for i in 0..4 {
transform.rotation[i] = self.read_f32(offset + 16 + i * 4);
}
for i in 0..9 {
transform.scale_shear[i] = self.read_f32(offset + 32 + i * 4);
}
transform
}
pub fn parse_skeleton(&self, file_data: &[u8]) -> Result<Option<Skeleton>> {
let mut cursor = std::io::Cursor::new(&file_data[0x20..]);
cursor.set_position(28);
let root_section = cursor.read_u32::<LittleEndian>()? as usize;
let root_offset = cursor.read_u32::<LittleEndian>()? as usize;
let root_addr = self.section_offsets[root_section] + root_offset;
let ptr_size = self.ptr_size();
let array_size = 4 + ptr_size;
let mut pos = root_addr;
pos += ptr_size * 3; pos += array_size * 2;
let skeleton_count = self.read_u32(pos) as usize;
let skeletons_ptr = self.read_ptr(pos + 4);
if skeleton_count == 0 || skeletons_ptr == 0 {
return Ok(None);
}
let skeleton_ptr = self.read_ptr(skeletons_ptr);
if skeleton_ptr == 0 || skeleton_ptr >= self.data.len() {
return Ok(None);
}
let name = self.read_string_ptr(skeleton_ptr);
let bone_count = self.read_u32(skeleton_ptr + ptr_size) as usize;
let bones_array_ptr = self.read_ptr(skeleton_ptr + ptr_size + 4);
let lod_type_offset = skeleton_ptr + ptr_size + array_size;
let lod_type = self.read_i32(lod_type_offset);
let mut bones = Vec::with_capacity(bone_count);
let bone_size = ptr_size + 4 + 68 + 64 + 4 + 2 * ptr_size;
for i in 0..bone_count {
let bone_offset = bones_array_ptr + i * bone_size;
if bone_offset + bone_size > self.data.len() {
break;
}
let bone_name = self.read_string_ptr(bone_offset);
let parent_index = self.read_u32(bone_offset + ptr_size) as i32;
let transform = self.read_transform(bone_offset + ptr_size + 4);
let iwt_offset = bone_offset + ptr_size + 4 + 68;
let mut inverse_world_transform = [0.0f32; 16];
for j in 0..16 {
inverse_world_transform[j] = self.read_f32(iwt_offset + j * 4);
}
let lod_error_offset = iwt_offset + 64;
let lod_error = self.read_f32(lod_error_offset);
bones.push(Bone {
name: bone_name,
parent_index,
transform,
inverse_world_transform,
lod_error,
});
}
Ok(Some(Skeleton {
name,
bones,
lod_type,
}))
}
fn parse_material_bindings(&self, offset: usize) -> Vec<String> {
if offset + 12 > self.data.len() {
return Vec::new();
}
let count = self.read_u32(offset) as usize;
if count == 0 {
return Vec::new();
}
let array_ptr = self.read_ptr(offset + 4);
if array_ptr == 0 || array_ptr >= self.data.len() {
return Vec::new();
}
let ptr_size = self.ptr_size();
let mut names = Vec::with_capacity(count);
for i in 0..count {
let binding_ptr = self.read_ptr(array_ptr + i * ptr_size);
if binding_ptr > 0 && binding_ptr < self.data.len() {
let mat_name = self.read_string_ptr(binding_ptr);
names.push(mat_name);
}
}
names
}
fn parse_bone_bindings(&self, offset: usize) -> Vec<BoneBinding> {
if offset + 12 > self.data.len() {
return Vec::new();
}
let count = self.read_u32(offset) as usize;
if count == 0 {
return Vec::new();
}
let array_ptr = self.read_ptr(offset + 4);
if array_ptr == 0 || array_ptr >= self.data.len() {
return Vec::new();
}
let ptr_size = self.ptr_size();
let binding_size = ptr_size + 24 + 4 + ptr_size; let mut bindings = Vec::with_capacity(count);
for i in 0..count {
let b_offset = array_ptr + i * binding_size;
if b_offset + binding_size > self.data.len() {
break;
}
let bone_name = self.read_string_ptr(b_offset);
let mut pos = b_offset + ptr_size;
let mut obb_min = [0.0f32; 3];
for j in 0..3 {
obb_min[j] = self.read_f32(pos + j * 4);
}
pos += 12;
let mut obb_max = [0.0f32; 3];
for j in 0..3 {
obb_max[j] = self.read_f32(pos + j * 4);
}
pos += 12;
let tri_idx_count = self.read_u32(pos) as usize;
let tri_idx_ptr = self.read_ptr(pos + 4);
let mut tri_indices = Vec::with_capacity(tri_idx_count);
if tri_idx_ptr > 0 && tri_idx_ptr < self.data.len() {
for j in 0..tri_idx_count {
let idx_off = tri_idx_ptr + j * 4;
if idx_off + 4 <= self.data.len() {
tri_indices.push(self.read_i32(idx_off));
}
}
}
bindings.push(BoneBinding {
bone_name,
obb_min,
obb_max,
tri_count: tri_indices.len() as i32,
tri_indices,
});
}
bindings
}
fn parse_topology_groups(&self, topology_ptr: usize) -> Vec<TopologyGroup> {
if topology_ptr + 12 > self.data.len() {
return Vec::new();
}
let count = self.read_u32(topology_ptr) as usize;
if count == 0 {
return Vec::new();
}
let groups_ptr = self.read_ptr(topology_ptr + 4);
if groups_ptr == 0 || groups_ptr >= self.data.len() {
return Vec::new();
}
let mut groups = Vec::with_capacity(count);
for i in 0..count {
let g_offset = groups_ptr + i * 12; if g_offset + 12 > self.data.len() {
break;
}
groups.push(TopologyGroup {
material_index: self.read_i32(g_offset),
tri_first: self.read_i32(g_offset + 4),
tri_count: self.read_i32(g_offset + 8),
});
}
groups
}
pub fn parse_models(&self, file_data: &[u8]) -> Result<Vec<Model>> {
let mut cursor = std::io::Cursor::new(&file_data[0x20..]);
cursor.set_position(28);
let root_section = cursor.read_u32::<LittleEndian>()? as usize;
let root_offset = cursor.read_u32::<LittleEndian>()? as usize;
let root_addr = self.section_offsets[root_section] + root_offset;
let ptr_size = self.ptr_size();
let array_size = 4 + ptr_size;
let mut pos = root_addr;
pos += ptr_size * 3; pos += array_size * 4; pos += array_size; pos += array_size; let model_count = self.read_u32(pos) as usize;
let models_ptr = self.read_ptr(pos + 4);
if model_count == 0 || models_ptr == 0 {
return Ok(Vec::new());
}
let mut models = Vec::with_capacity(model_count);
for i in 0..model_count {
let model_ptr = self.read_ptr(models_ptr + i * ptr_size);
if model_ptr == 0 || model_ptr >= self.data.len() {
continue;
}
let name = self.read_string_ptr(model_ptr);
let placement_offset = model_ptr + ptr_size * 2 + array_size;
let initial_placement = self.read_transform(placement_offset);
let mesh_bind_offset = model_ptr + ptr_size * 2;
let mesh_bind_count = self.read_u32(mesh_bind_offset) as usize;
let mesh_bind_ptr = self.read_ptr(mesh_bind_offset + 4);
let mut mesh_binding_names = Vec::with_capacity(mesh_bind_count);
if mesh_bind_ptr > 0 && mesh_bind_ptr < self.data.len() {
for j in 0..mesh_bind_count {
let binding_ptr = self.read_ptr(mesh_bind_ptr + j * ptr_size);
if binding_ptr > 0 && binding_ptr < self.data.len() {
let mesh_name = self.read_string_ptr(binding_ptr);
mesh_binding_names.push(mesh_name);
}
}
}
models.push(Model {
name,
mesh_binding_names,
initial_placement,
});
}
Ok(models)
}
pub fn get_content_info(&self, file_data: &[u8]) -> Result<Gr2ContentInfo> {
let mut cursor = std::io::Cursor::new(&file_data[0x20..]);
cursor.set_position(28);
let root_section = cursor.read_u32::<LittleEndian>()? as usize;
let root_offset = cursor.read_u32::<LittleEndian>()? as usize;
let root_addr = self.section_offsets[root_section] + root_offset;
let ptr_size = self.ptr_size();
let array_size = 4 + ptr_size;
let mut pos = root_addr + ptr_size * 3;
pos += array_size; let material_count = self.read_u32(pos) as usize;
pos += array_size;
let skeleton_count = self.read_u32(pos) as usize;
pos += array_size * 3; let mesh_count = self.read_u32(pos) as usize;
pos += array_size;
let model_count = self.read_u32(pos) as usize;
Ok(Gr2ContentInfo {
material_count,
skeleton_count,
mesh_count,
model_count,
})
}
}