use std::io::{self, Cursor, Read, Seek, SeekFrom, Write};
use binrw::{binread, BinRead, BinWriterExt, VecArgs};
use crate::error::Result;
use super::shared::{read_block, Header};
const MAX_LODS: usize = 3;
#[binread]
#[derive(Debug)]
#[br(little)]
struct ModelHeader {
_size: SectionInfo<u32>,
_compressed_size: SectionInfo<u32>,
offset: SectionInfo<u32>,
block_index: SectionInfo<u16>,
block_count: SectionInfo<u16>,
vertex_declaration_count: u16,
material_count: u16,
lod_count: u8,
index_buffer_streaming_enabled: u8, edge_geometry_enabled: u8, _padding: u8,
}
#[binread]
#[derive(Debug)]
#[br(little)]
struct SectionInfo<T: BinRead<Args = ()> + 'static> {
stack: T,
runtime: T,
vertex_buffer: [T; MAX_LODS],
edge_geometry_vertex_buffer: [T; MAX_LODS],
index_buffer: [T; MAX_LODS],
}
pub fn read(mut reader: impl Read + Seek, offset: u32, header: Header) -> Result<Vec<u8>> {
let model_header = ModelHeader::read(&mut reader)?;
let block_counts = &model_header.block_count;
let total_blocks = block_counts.stack
+ block_counts.runtime
+ block_counts.vertex_buffer.iter().sum::<u16>()
+ block_counts.edge_geometry_vertex_buffer.iter().sum::<u16>()
+ block_counts.index_buffer.iter().sum::<u16>();
let block_sizes = <Vec<u16>>::read_args(
&mut reader,
VecArgs {
count: total_blocks.try_into().unwrap(),
inner: (),
},
)?;
let mut writer = Cursor::new(Vec::<u8>::with_capacity(
header.raw_file_size.try_into().unwrap(),
));
writer.seek(SeekFrom::Start(0x44))?;
let stack_size = read_blocks(
model_header.block_count.stack,
model_header.block_index.stack,
offset + model_header.offset.stack,
&block_sizes,
&mut reader,
&mut writer,
)?;
let runtime_size = read_blocks(
model_header.block_count.runtime,
model_header.block_index.runtime,
offset + model_header.offset.runtime,
&block_sizes,
&mut reader,
&mut writer,
)?;
let mut vertex_data_offsets = [0u32; MAX_LODS];
let mut vertex_buffer_sizes = [0u32; MAX_LODS];
let mut index_data_offsets = [0u32; MAX_LODS];
let mut index_buffer_sizes = [0u32; MAX_LODS];
for lod_index in 0..MAX_LODS {
let block_count = model_header.block_count.vertex_buffer[lod_index];
if block_count != 0 {
if lod_index == 0 || block_count > 0 {
vertex_data_offsets[lod_index] = writer.position().try_into().unwrap();
}
vertex_buffer_sizes[lod_index] = read_blocks(
block_count,
model_header.block_index.vertex_buffer[lod_index],
offset + model_header.offset.vertex_buffer[lod_index],
&block_sizes,
&mut reader,
&mut writer,
)?;
}
let block_count = model_header.block_count.edge_geometry_vertex_buffer[lod_index];
if block_count != 0 {
read_blocks(
block_count,
model_header.block_index.edge_geometry_vertex_buffer[lod_index],
offset + model_header.offset.edge_geometry_vertex_buffer[lod_index],
&block_sizes,
&mut reader,
&mut writer,
)?;
}
let block_count = model_header.block_count.index_buffer[lod_index];
if block_count != 0 {
if lod_index == 0 || block_count > 0 {
index_data_offsets[lod_index] = writer.position().try_into().unwrap();
}
index_buffer_sizes[lod_index] = read_blocks(
block_count,
model_header.block_index.index_buffer[lod_index],
offset + model_header.offset.index_buffer[lod_index],
&block_sizes,
&mut reader,
&mut writer,
)?;
}
}
writer.seek(SeekFrom::Start(0))?;
writer.write_le(&header.block_count)?; writer.write_le(&stack_size)?;
writer.write_le(&runtime_size)?;
writer.write_le(&model_header.vertex_declaration_count)?;
writer.write_le(&model_header.material_count)?;
writer.write_le(&vertex_data_offsets)?;
writer.write_le(&index_data_offsets)?;
writer.write_le(&vertex_buffer_sizes)?;
writer.write_le(&index_buffer_sizes)?;
writer.write_le(&model_header.lod_count)?;
writer.write_le(&model_header.index_buffer_streaming_enabled)?;
writer.write_le(&model_header.edge_geometry_enabled)?;
writer.write_le(&0u8)?;
Ok(writer.into_inner())
}
fn read_blocks(
block_count: u16,
block_index: u16,
section_offset: u32,
block_sizes: &[u16],
reader: &mut (impl Read + Seek),
writer: &mut impl Write,
) -> Result<u32> {
let size = (0..block_count)
.scan(section_offset, |offset, index| {
let current_offset = *offset;
*offset += u32::from(block_sizes[usize::from(block_index + index)]);
Some(current_offset)
})
.try_fold(0u32, |size, offset| -> Result<u32> {
let bytes_read = io::copy(&mut read_block(reader, offset)?, writer)?;
Ok(size + u32::try_from(bytes_read).unwrap())
})?;
Ok(size)
}