param: good enough parsing of bitfields

This commit is contained in:
dece 2020-05-22 22:52:56 +02:00
parent da80ff1d9a
commit 15dd0b2cb8
6 changed files with 174 additions and 14 deletions

28
Cargo.lock generated
View file

@ -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"

View file

@ -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"]

View file

@ -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(&paramdef)) {
Ok(param) => { unpackers::param::print_param(&param); 0 }
Ok(param) => { unpackers::param::print_param_with_def(&param, &paramdef); 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(&param); 0 }
Ok(param) => { unpackers::param::print_param(&param); 0 }
Err(e) => { eprintln!("Failed to load PARAM: {:?}", e); 1 }
}
}

View file

@ -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<String>,
pub data: Vec<u8>,
pub data: Vec<ParamRowValue>,
}
#[derive(strum_macros::IntoStaticStr)]
pub enum ParamRowValue {
S8(i8), U8(u8), S16(i16), U16(u16), S32(i32), U32(u32), F32(f32), UNK(Vec<u8>)
}
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: &paramdef::Paramdef
) -> IResult<&'a[u8], Vec<ParamRowValue>> {
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 &paramdef.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<&paramdef::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;
}
}

View file

@ -35,6 +35,7 @@ pub fn load_param(
}
}
/// Print a PARAM's name and number of rows.
fn print_param_intro(param: &param::Param) {
println!(
"{} -- {}",
@ -43,16 +44,23 @@ fn print_param_intro(param: &param::Param) {
);
}
pub fn print_param_no_data(param: &param::Param) {
print_param_intro(param);
for row in &param.rows {
println!(" - [{}] {}", row.id, row.name.as_ref().unwrap_or(&String::from("<noname>")));
}
}
/// Print simple information about a PARAM.
pub fn print_param(param: &param::Param) {
print_param_intro(param);
for row in &param.rows {
println!(" - [{}] {}", row.id, row.name.as_ref().unwrap_or(&String::from("<noname>")));
if row.data.len() > 0 {
println!(" {:?}", row.data);
}
}
}
/// Print a PARAM's data using PARAMDEF fields.
pub fn print_param_with_def(param: &param::Param, paramdef: &paramdef::Paramdef) {
print_param_intro(param);
for row in &param.rows {
println!("{:?}", row);
}
}

View file

@ -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);
}
}