use binrw::{BinRead, BinWrite};
#[derive(Debug, Clone, Copy, PartialEq, Eq, BinRead, BinWrite)]
#[brw(little)]
pub struct MverChunk {
pub version: u32,
}
impl Default for MverChunk {
fn default() -> Self {
Self { version: 18 }
}
}
#[derive(Debug, Clone, Copy, Default, BinRead, BinWrite)]
#[brw(little)]
pub struct MhdrChunk {
pub flags: u32,
pub mcin_offset: u32,
pub mtex_offset: u32,
pub mmdx_offset: u32,
pub mmid_offset: u32,
pub mwmo_offset: u32,
pub mwid_offset: u32,
pub mddf_offset: u32,
pub modf_offset: u32,
pub mfbo_offset: u32,
pub mh2o_offset: u32,
pub mtxf_offset: u32,
pub unused1: u32,
pub unused2: u32,
pub unused3: u32,
pub unused4: u32,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, BinRead, BinWrite)]
#[brw(little)]
pub struct McinEntry {
pub offset: u32,
pub size: u32,
pub flags: u32,
pub async_id: u32,
}
#[derive(Debug, Clone, BinRead, BinWrite)]
#[brw(little)]
pub struct McinChunk {
#[br(count = 256)]
pub entries: Vec<McinEntry>,
}
impl Default for McinChunk {
fn default() -> Self {
Self {
entries: vec![McinEntry::default(); 256],
}
}
}
impl McinChunk {
pub fn get_entry(&self, x: usize, y: usize) -> Option<&McinEntry> {
if x >= 16 || y >= 16 {
return None;
}
let index = y * 16 + x;
self.entries.get(index)
}
pub fn get_entry_mut(&mut self, x: usize, y: usize) -> Option<&mut McinEntry> {
if x >= 16 || y >= 16 {
return None;
}
let index = y * 16 + x;
self.entries.get_mut(index)
}
}
#[derive(Debug, Clone, Copy, Default, BinRead, BinWrite)]
#[brw(little)]
pub struct MfboChunk {
pub max_plane: [i16; 9],
pub min_plane: [i16; 9],
}
#[derive(Debug, Clone, Copy, BinRead, BinWrite)]
#[brw(little)]
pub struct MampChunk {
pub amplifier: u32,
}
#[derive(Debug, Clone, BinRead, BinWrite)]
#[brw(little)]
pub struct MtxpChunk {
#[br(parse_with = parse_texture_height_params)]
#[bw(write_with = write_texture_height_params)]
pub entries: Vec<TextureHeightParams>,
}
#[derive(Debug, Clone, Copy, BinRead, BinWrite)]
#[brw(little)]
pub struct TextureHeightParams {
pub flags: u32,
pub height_scale: f32,
pub height_offset: f32,
pub padding: u32,
}
impl TextureHeightParams {
pub fn uses_height_texture(&self) -> bool {
self.height_scale != 0.0 || self.height_offset != 1.0
}
}
#[binrw::parser(reader, endian)]
#[allow(clippy::while_let_loop)]
fn parse_texture_height_params() -> binrw::BinResult<Vec<TextureHeightParams>> {
let mut params = Vec::new();
loop {
match TextureHeightParams::read_options(reader, endian, ()) {
Ok(entry) => params.push(entry),
Err(_) => break,
}
}
Ok(params)
}
#[binrw::writer(writer, endian)]
fn write_texture_height_params(params: &Vec<TextureHeightParams>) -> binrw::BinResult<()> {
for param in params {
param.write_options(writer, endian, ())?;
}
Ok(())
}
#[derive(Debug, Clone, BinRead, BinWrite)]
#[brw(little)]
pub struct MtxfChunk {
#[br(parse_with = parse_texture_flags)]
#[bw(write_with = write_texture_flags)]
pub flags: Vec<u32>,
}
#[binrw::parser(reader, endian)]
#[allow(clippy::while_let_loop)]
fn parse_texture_flags() -> binrw::BinResult<Vec<u32>> {
let mut flags = Vec::new();
loop {
match u32::read_options(reader, endian, ()) {
Ok(value) => flags.push(value),
Err(_) => break,
}
}
Ok(flags)
}
#[binrw::writer(writer, endian)]
fn write_texture_flags(flags: &Vec<u32>) -> binrw::BinResult<()> {
for flag in flags {
flag.write_options(writer, endian, ())?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_mver_default() {
let mver = MverChunk::default();
assert_eq!(mver.version, 18);
}
#[test]
fn test_mver_round_trip() {
let original = MverChunk { version: 18 };
let mut buffer = Cursor::new(Vec::new());
original.write_le(&mut buffer).unwrap();
let data = buffer.into_inner();
let mut cursor = Cursor::new(data);
let parsed = MverChunk::read_le(&mut cursor).unwrap();
assert_eq!(original, parsed);
}
#[test]
fn test_mhdr_size() {
let mhdr = MhdrChunk::default();
let mut buffer = Cursor::new(Vec::new());
mhdr.write_le(&mut buffer).unwrap();
assert_eq!(buffer.position(), 64);
}
#[test]
fn test_mcin_entry_count() {
let mcin = McinChunk::default();
assert_eq!(mcin.entries.len(), 256);
}
#[test]
fn test_mcin_get_entry() {
let mut mcin = McinChunk::default();
mcin.entries[0].offset = 1000;
mcin.entries[255].offset = 2000;
assert_eq!(mcin.get_entry(0, 0).unwrap().offset, 1000);
assert_eq!(mcin.get_entry(15, 15).unwrap().offset, 2000);
assert!(mcin.get_entry(16, 0).is_none());
}
#[test]
fn test_mfbo_chunk_size() {
assert_eq!(std::mem::size_of::<MfboChunk>(), 36);
}
#[test]
fn test_mfbo_chunk_parse() {
let mut data = Vec::new();
for i in 0..9 {
data.extend_from_slice(&(100 + i as i16).to_le_bytes());
}
for i in 0..9 {
data.extend_from_slice(&(i as i16).to_le_bytes());
}
let mut cursor = Cursor::new(data);
let mfbo = MfboChunk::read_le(&mut cursor).unwrap();
for i in 0..9 {
assert_eq!(mfbo.max_plane[i], 100 + i as i16);
}
for i in 0..9 {
assert_eq!(mfbo.min_plane[i], i as i16);
}
}
#[test]
fn test_mfbo_chunk_round_trip() {
let original = MfboChunk {
max_plane: [500, 510, 520, 530, 540, 550, 560, 570, 580],
min_plane: [10, 20, 30, 40, 50, 60, 70, 80, 90],
};
let mut buffer = Cursor::new(Vec::new());
original.write_le(&mut buffer).unwrap();
assert_eq!(buffer.position(), 36);
let data = buffer.into_inner();
let mut cursor = Cursor::new(data);
let parsed = MfboChunk::read_le(&mut cursor).unwrap();
assert_eq!(original.max_plane, parsed.max_plane);
assert_eq!(original.min_plane, parsed.min_plane);
}
#[test]
fn test_mfbo_chunk_default() {
let mfbo = MfboChunk::default();
assert_eq!(mfbo.max_plane, [0; 9]);
assert_eq!(mfbo.min_plane, [0; 9]);
}
#[test]
fn test_mamp_chunk_size() {
assert_eq!(std::mem::size_of::<MampChunk>(), 4);
}
#[test]
fn test_mamp_chunk_parse() {
let data = vec![0x02, 0x00, 0x00, 0x00]; let mut cursor = Cursor::new(data);
let mamp = MampChunk::read_le(&mut cursor).unwrap();
assert_eq!(mamp.amplifier, 2);
}
#[test]
fn test_mtxp_chunk_parse() {
let data = vec![
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, ];
let mut cursor = Cursor::new(data);
let mtxp = MtxpChunk::read_le(&mut cursor).unwrap();
assert_eq!(mtxp.entries.len(), 2);
assert_eq!(mtxp.entries[0].flags, 1);
assert!((mtxp.entries[0].height_scale - 0.5).abs() < 0.001);
assert!((mtxp.entries[0].height_offset - 1.0).abs() < 0.001);
assert_eq!(mtxp.entries[1].flags, 2);
assert!((mtxp.entries[1].height_scale - 1.0).abs() < 0.001);
assert!((mtxp.entries[1].height_offset - 0.5).abs() < 0.001);
}
#[test]
fn test_mtxp_chunk_empty() {
let data = vec![];
let mut cursor = Cursor::new(data);
let mtxp = MtxpChunk::read_le(&mut cursor).unwrap();
assert_eq!(mtxp.entries.len(), 0);
}
#[test]
fn test_mtxp_chunk_round_trip() {
let original = MtxpChunk {
entries: vec![
TextureHeightParams {
flags: 1,
height_scale: 0.5,
height_offset: 1.0,
padding: 0,
},
TextureHeightParams {
flags: 2,
height_scale: 2.0,
height_offset: 0.25,
padding: 0,
},
],
};
let mut buffer = Cursor::new(Vec::new());
original.write_le(&mut buffer).unwrap();
let data = buffer.into_inner();
let mut cursor = Cursor::new(data);
let parsed = MtxpChunk::read_le(&mut cursor).unwrap();
assert_eq!(original.entries.len(), parsed.entries.len());
for (orig, parsed) in original.entries.iter().zip(parsed.entries.iter()) {
assert_eq!(orig.flags, parsed.flags);
assert!((orig.height_scale - parsed.height_scale).abs() < 0.001);
assert!((orig.height_offset - parsed.height_offset).abs() < 0.001);
}
}
#[test]
fn test_texture_height_params_uses_height_texture() {
let default_params = TextureHeightParams {
flags: 0,
height_scale: 0.0,
height_offset: 1.0,
padding: 0,
};
assert!(!default_params.uses_height_texture());
let with_scale = TextureHeightParams {
flags: 0,
height_scale: 0.5,
height_offset: 1.0,
padding: 0,
};
assert!(with_scale.uses_height_texture());
let with_offset = TextureHeightParams {
flags: 0,
height_scale: 0.0,
height_offset: 0.5,
padding: 0,
};
assert!(with_offset.uses_height_texture());
}
#[test]
fn test_mtxf_chunk_parse() {
let data = vec![
0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let mut cursor = Cursor::new(data);
let mtxf = MtxfChunk::read_le(&mut cursor).unwrap();
assert_eq!(mtxf.flags.len(), 3);
assert_eq!(mtxf.flags[0], 0x01);
assert_eq!(mtxf.flags[1], 0x02);
assert_eq!(mtxf.flags[2], 0x00);
}
#[test]
fn test_mtxf_chunk_empty() {
let data = vec![];
let mut cursor = Cursor::new(data);
let mtxf = MtxfChunk::read_le(&mut cursor).unwrap();
assert_eq!(mtxf.flags.len(), 0);
}
#[test]
fn test_mtxf_chunk_round_trip() {
let original = MtxfChunk {
flags: vec![0x01, 0x02, 0x04, 0x08],
};
let mut buffer = Cursor::new(Vec::new());
original.write_le(&mut buffer).unwrap();
let data = buffer.into_inner();
let mut cursor = Cursor::new(data);
let parsed = MtxfChunk::read_le(&mut cursor).unwrap();
assert_eq!(original.flags.len(), parsed.flags.len());
assert_eq!(original.flags, parsed.flags);
}
#[test]
fn test_mtxf_chunk_single_flag() {
let data = vec![0x00, 0x01, 0x00, 0x00];
let mut cursor = Cursor::new(data);
let mtxf = MtxfChunk::read_le(&mut cursor).unwrap();
assert_eq!(mtxf.flags.len(), 1);
assert_eq!(mtxf.flags[0], 0x100);
}
#[test]
fn test_mtxf_chunk_all_zeros() {
let data = vec![
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let mut cursor = Cursor::new(data);
let mtxf = MtxfChunk::read_le(&mut cursor).unwrap();
assert_eq!(mtxf.flags.len(), 3);
assert!(mtxf.flags.iter().all(|&f| f == 0));
}
}