diff --git a/Cargo.lock b/Cargo.lock index 55a8695..ce374cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -114,6 +114,14 @@ dependencies = [ "syn 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "hermit-abi" version = "0.1.10" @@ -395,6 +403,7 @@ dependencies = [ "nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "strum_macros 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -463,6 +472,17 @@ name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "strum_macros" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "syn" version = "1.0.20" @@ -481,6 +501,11 @@ dependencies = [ "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unicode-width" version = "0.1.7" @@ -540,6 +565,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)" = "cd8d03faa7fe0c1431609dfad7bbe827af30f82e1e2ae6f7ee4fca6bd764bc28" "checksum flate2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42" "checksum ghost 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2a36606a68532b5640dc86bb1f33c64b45c4682aad4c50f3937b317ea387f3d6" +"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum hermit-abi 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e" "checksum indoc 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "79255cf29f5711995ddf9ec261b4057b1deb34e66c90656c201e41376872c544" "checksum indoc-impl 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "54554010aa3d17754e484005ea0022f1c93839aabc627c2c55f3d7b47206134c" @@ -580,8 +606,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" "checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +"checksum strum_macros 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c" "checksum syn 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "dd1b5e337360b1fae433c59fcafa0c6b77c605e92540afa5221a7b81a9eca91d" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum unindent 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "63f18aa3b0e35fed5a0048f029558b1518095ffe2a0a31fb87c93dece93a4993" diff --git a/Cargo.toml b/Cargo.toml index a92b681..4eebf77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ flate2 = "1.0" nom = "5" num-bigint = "0.2" num-traits = "0.2" +strum_macros = "0.18" [workspace] members = ["bindings/python"] diff --git a/src/bin/rir.rs b/src/bin/rir.rs index d94581d..df9f5dd 100644 --- a/src/bin/rir.rs +++ b/src/bin/rir.rs @@ -222,7 +222,7 @@ fn cmd_param(args: &ArgMatches) -> i32 { match unpackers::paramdef::load_paramdef_file(paramdef_path) { Ok(paramdef) => { match unpackers::param::load_param_file(file_path, Some(¶mdef)) { - Ok(param) => { unpackers::param::print_param(¶m); 0 } + Ok(param) => { unpackers::param::print_param_with_def(¶m, ¶mdef); 0 } Err(e) => { eprintln!("Failed to load PARAM: {:?}", e); 1 } } } @@ -230,7 +230,7 @@ fn cmd_param(args: &ArgMatches) -> i32 { } } else { match unpackers::param::load_param_file(file_path, None) { - Ok(param) => { unpackers::param::print_param_no_data(¶m); 0 } + Ok(param) => { unpackers::param::print_param(¶m); 0 } Err(e) => { eprintln!("Failed to load PARAM: {:?}", e); 1 } } } diff --git a/src/parsers/param.rs b/src/parsers/param.rs index 65c8737..492c4e7 100644 --- a/src/parsers/param.rs +++ b/src/parsers/param.rs @@ -1,3 +1,5 @@ +use std::fmt::{self, Debug}; + use nom::IResult; use nom::bytes::complete::take; use nom::multi::count; @@ -6,7 +8,7 @@ use nom::sequence::tuple; use crate::parsers::common::{sjis_to_string, take_cstring, take_cstring_from, VarSizeInt}; use crate::parsers::paramdef; -use crate::utils::bin::has_flag; +use crate::utils::bin::{has_flag, mask}; const FLAGS2D_UNK1: u8 = 0b00000001; const FLAGS2D_32B_OFS_DATA: u8 = 0b00000010; @@ -104,7 +106,35 @@ pub struct ParamRow { pub ofs_data: VarSizeInt, pub ofs_name: VarSizeInt, pub name: Option, - pub data: Vec, + pub data: Vec, +} + +#[derive(strum_macros::IntoStaticStr)] +pub enum ParamRowValue { + S8(i8), U8(u8), S16(i16), U16(u16), S32(i32), U32(u32), F32(f32), UNK(Vec) +} + +impl fmt::Debug for ParamRowValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let name: &str = self.into(); + write!(f, "{}: {}", name, self) + } +} + +// Could be probably be done better with a macro... +impl fmt::Display for ParamRowValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ParamRowValue::S8(i) => fmt::Display::fmt(&i, f), + ParamRowValue::U8(i) => fmt::Display::fmt(&i, f), + ParamRowValue::S16(i) => fmt::Display::fmt(&i, f), + ParamRowValue::U16(i) => fmt::Display::fmt(&i, f), + ParamRowValue::S32(i) => fmt::Display::fmt(&i, f), + ParamRowValue::U32(i) => fmt::Display::fmt(&i, f), + ParamRowValue::F32(i) => fmt::Display::fmt(&i, f), + ParamRowValue::UNK(i) => fmt::Debug::fmt(&i, f), + } + } } fn parse_row<'a>(i: &'a[u8], header: &ParamHeader) -> IResult<&'a[u8], ParamRow> { @@ -122,6 +152,83 @@ fn parse_row<'a>(i: &'a[u8], header: &ParamHeader) -> IResult<&'a[u8], ParamRow> Ok((i, ParamRow { id, ofs_data, ofs_name, name: None, data: vec!() })) } +fn parse_row_data<'a>( + i: &'a[u8], + header: &ParamHeader, + paramdef: ¶mdef::Paramdef +) -> IResult<&'a[u8], Vec> { + let use_be = header.use_be(); + let mut data = vec!(); + let mut bitfield = 0u16; // Current bitfield being parsed. u16 is largest handled type. + let mut remaining_bits = 0; // Remaining bits in bitfield. + let mut data_slice = i; + for field in ¶mdef.fields { + let bit_size = field.bit_size(); + let value = if bit_size == 0 { + let (rest, value) = parse_row_value(data_slice, &field.display_type, + field.byte_count as usize, use_be)?; + data_slice = rest; + remaining_bits = 0; + value + } else { + // Bitfield parsing. If it's the first bitfield in a series, get the containing bytes + // in the bitfield var. + if remaining_bits == 0 { + let (rest, bf) = take(field.byte_count as usize)(data_slice)?; + bitfield = match field.display_type.as_str() { + "u8" => { remaining_bits = 8; le_u8(bf).map(|(_, v)| v as u16)? } + "dummy8" => { remaining_bits = 8; le_u8(bf).map(|(_, v)| v as u16)? } + "u16" => { + remaining_bits = 16; + (if use_be { be_u16 } else { le_u16 }) (bf) .map(|(_, v)| v)? + } + e => panic!("Unhandled PARAMDEF type {}", e), + }; + data_slice = rest; + } + // Parse masked bits. + let value = bitfield & mask(bit_size as usize) as u16; + // Shift bitfield so next values can be parsed directly with a bitmask. + bitfield >>= bit_size; + remaining_bits -= bit_size; + match field.display_type.as_str() { + "u8" => ParamRowValue::U8(value as u8), + "dummy8" => ParamRowValue::U8(value as u8), + "u16" => ParamRowValue::U16(value), + e => panic!("Unhandled PARAMDEF type {}", e), + } + }; + data.push(value); + } + Ok((i, data)) +} + +fn parse_row_value<'a>( + i: &'a[u8], + type_str: &str, + num_bytes: usize, + use_be: bool +) -> IResult<&'a[u8], ParamRowValue> { + Ok(match type_str { + "s8" => le_i8(i) + .map(|(i, v)| (i, ParamRowValue::S8(v)))?, + "u8" => le_u8(i) + .map(|(i, v)| (i, ParamRowValue::U8(v)))?, + "s16" => (if use_be { be_i16 } else { le_i16 }) (i) + .map(|(i, v)| (i, ParamRowValue::S16(v)))?, + "u16" => (if use_be { be_u16 } else { le_u16 }) (i) + .map(|(i, v)| (i, ParamRowValue::U16(v)))?, + "s32" => (if use_be { be_i32 } else { le_i32 }) (i) + .map(|(i, v)| (i, ParamRowValue::S32(v)))?, + "u32" => (if use_be { be_u32 } else { le_u32 }) (i) + .map(|(i, v)| (i, ParamRowValue::U32(v)))?, + "f32" => (if use_be { be_f32 } else { le_f32 }) (i) + .map(|(i, v)| (i, ParamRowValue::F32(v)))?, + _ => take(num_bytes)(i) + .map(|(i, v)| (i, ParamRowValue::UNK(v.to_vec())))?, + }) +} + #[derive(Debug)] pub struct Param { pub header: ParamHeader, @@ -151,14 +258,16 @@ pub fn parse<'a>(i: &'a[u8], paramdef: Option<¶mdef::Paramdef>) -> IResult<& } if paramdef.is_some() { - let row_size = paramdef.unwrap().row_size(); + let def = paramdef.unwrap(); + let row_size = def.row_size(); for row in &mut rows { let ofs_data = row.ofs_data.u64_if(header.has_u64_ofs_data()) as usize; if ofs_data == 0 { continue } let ofs_data_end = ofs_data + row_size; - row.data = full_file[ofs_data..ofs_data_end].to_vec(); + let (_, data) = parse_row_data(&full_file[ofs_data..ofs_data_end], &header, &def)?; + row.data = data; } } diff --git a/src/unpackers/param.rs b/src/unpackers/param.rs index 0161b33..2227e99 100644 --- a/src/unpackers/param.rs +++ b/src/unpackers/param.rs @@ -35,6 +35,7 @@ pub fn load_param( } } +/// Print a PARAM's name and number of rows. fn print_param_intro(param: ¶m::Param) { println!( "{} -- {}", @@ -43,16 +44,23 @@ fn print_param_intro(param: ¶m::Param) { ); } -pub fn print_param_no_data(param: ¶m::Param) { +/// Print simple information about a PARAM. +pub fn print_param(param: ¶m::Param) { print_param_intro(param); for row in ¶m.rows { println!(" - [{}] {}", row.id, row.name.as_ref().unwrap_or(&String::from(""))); + if row.data.len() > 0 { + println!(" {:?}", row.data); + } } } -pub fn print_param(param: ¶m::Param) { +/// Print a PARAM's data using PARAMDEF fields. +pub fn print_param_with_def(param: ¶m::Param, paramdef: ¶mdef::Paramdef) { print_param_intro(param); for row in ¶m.rows { + println!("{:?}", row); + } } diff --git a/src/utils/bin.rs b/src/utils/bin.rs index bf515a8..e0ef7d3 100644 --- a/src/utils/bin.rs +++ b/src/utils/bin.rs @@ -6,6 +6,11 @@ pub fn has_flag(i: u8, flag: u8) -> bool { i & flag == flag } +/// Return a mask for this number of bits. +pub fn mask(bit_size: usize) -> usize { + (1 << bit_size) - 1 +} + #[cfg(test)] mod test { use super::*; @@ -16,4 +21,13 @@ mod test { assert!(has_flag(0x80, 0x80)); assert!(!has_flag(0x80, 0x40)); } + + #[test] + fn test_mask() { + assert_eq!(mask(1), 0b00000001); + assert_eq!(mask(2), 0b00000011); + assert_eq!(mask(4), 0b00001111); + assert_eq!(mask(8), 0b11111111); + assert_eq!(mask(15), 0b01111111_11111111); + } }