From 2cdc815aff9d3143aeac0ccf496278a904f1fe1c Mon Sep 17 00:00:00 2001 From: dece Date: Sat, 16 May 2020 15:23:55 +0200 Subject: [PATCH] paramdef: complete parsing and pretty print it --- src/bin/rir.rs | 2 +- src/parsers/bnd.rs | 7 +- src/parsers/common.rs | 48 +++++++++++++ src/parsers/paramdef.rs | 139 ++++++++++++++++++++++++++++++++++---- src/unpackers/paramdef.rs | 16 ++++- 5 files changed, 193 insertions(+), 19 deletions(-) diff --git a/src/bin/rir.rs b/src/bin/rir.rs index 1f2e01b..0f4eff5 100644 --- a/src/bin/rir.rs +++ b/src/bin/rir.rs @@ -74,7 +74,7 @@ fn main() { .help("Overwrite existing files") .short("f").long("force").takes_value(false).required(false))) .subcommand(SubCommand::with_name("paramdef") - .about("TODO") + .about("Print PARAMDEF contents") .arg(Arg::with_name("file") .help("PARAMDEF file path") .takes_value(true).required(true))) diff --git a/src/parsers/bnd.rs b/src/parsers/bnd.rs index 59bbb74..837afa7 100644 --- a/src/parsers/bnd.rs +++ b/src/parsers/bnd.rs @@ -155,10 +155,9 @@ pub fn parse(i: &[u8]) -> IResult<&[u8], Bnd> { for info in &mut file_infos { let ofs_path = info.ofs_path as usize; let (_, sjis_path) = take_cstring(&full_file[ofs_path..])?; - info.path = sjis_to_string(sjis_path); - if info.path.is_none() { - eprintln!("Failed to parse path: {:?}", sjis_path); - } + info.path = sjis_to_string(sjis_path).or_else(|| { + eprintln!("Failed to parse path: {:?}", sjis_path); None + }); } } Ok((full_file, Bnd { header, file_infos })) diff --git a/src/parsers/common.rs b/src/parsers/common.rs index 3ccaf9c..4734b9f 100644 --- a/src/parsers/common.rs +++ b/src/parsers/common.rs @@ -2,10 +2,21 @@ use encoding_rs::SHIFT_JIS; use nom::IResult; use nom::bytes::complete::take_while; +/// Parse a zero-terminated string from the slice. pub fn take_cstring(i: &[u8]) -> IResult<&[u8], &[u8]> { take_while(|c| c != b'\0')(i) } +/// Parse a zero-terminated string from the slice, discarding the rest. +/// +/// The cstring will be parsed from the first max_length bytes of the +/// slice, and on success the parser will discard exactly max_length +/// bytes from the input, regardless of the parsed string length. +pub fn take_cstring_from(i: &[u8], max_length: usize) -> IResult<&[u8], &[u8]> { + take_cstring(i).map(|(_, s)| Ok((&i[max_length..], s)) )? +} + +/// Decode a Shift JIS encoded byte slice. pub fn sjis_to_string(i: &[u8]) -> Option { let (cow, _, has_errors) = SHIFT_JIS.decode(i); if has_errors { @@ -13,3 +24,40 @@ pub fn sjis_to_string(i: &[u8]) -> Option { } Some(cow.to_string()) } + +/// Decode a Shift JIS encoded byte slice or hex representation. +pub fn sjis_to_string_lossy(i: &[u8]) -> String { + sjis_to_string(i).unwrap_or(format!("{:x?}", i)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_take_cstring() { + assert_eq!(take_cstring(b"ABC\0\xFF"), Ok((b"\0\xFF".as_ref(), b"ABC".as_ref()))); + assert_eq!(take_cstring(b"\0"), Ok((b"\0".as_ref(), b"".as_ref()))); + } + + #[test] + fn test_take_cstring_from() { + // Take cstring from the whole slice; nothing remains. + assert_eq!( + take_cstring_from( + b"ABC\0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20", // 0x10 bytes + 0x10, + ), + Ok((b"".as_ref(), b"ABC".as_ref())) + ); + + // Take cstring from the first half of the slice; the second half remains. + assert_eq!( + take_cstring_from( + b"ABC\0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20", // 0x10 bytes + 0x8, + ), + Ok((b"\x20\x20\x20\x20\x20\x20\x20\x20".as_ref(), b"ABC".as_ref())) + ); + } +} diff --git a/src/parsers/paramdef.rs b/src/parsers/paramdef.rs index 404576f..40c8d74 100644 --- a/src/parsers/paramdef.rs +++ b/src/parsers/paramdef.rs @@ -1,12 +1,9 @@ -use std::str; - use nom::IResult; -use nom::bytes::complete::{tag, take}; use nom::multi::count; use nom::number::complete::*; use nom::sequence::tuple; -use crate::parsers::common::{sjis_to_string, take_cstring}; +use crate::parsers::common::{sjis_to_string_lossy, take_cstring, take_cstring_from}; #[derive(Debug)] pub struct ParamdefHeader { @@ -15,20 +12,40 @@ pub struct ParamdefHeader { pub data_version: u16, pub num_entries: u16, pub entry_size: u16, - pub param_name: Vec, + pub param_name: String, pub endianness: u8, pub unicode: u8, pub format_version: u16, + pub ofs_entries: u64, +} + +impl ParamdefHeader { + pub fn use_be(&self) -> bool { use_be(self.endianness) } + pub fn has_ofs_entries(&self) -> bool { has_ofs_entries(self.format_version) } + pub fn has_64b_ofs_desc(&self) -> bool { self.format_version >= 201 } } +fn use_be(endianness: u8) -> bool { endianness == 0xFF } + +fn has_ofs_entries(format_version: u16) -> bool { format_version >= 201 } + fn parse_header(i: &[u8]) -> IResult<&[u8], ParamdefHeader> { - let p_u32 = if i[0x2C] == 0xFF { be_u32 } else { le_u32 }; - let p_u16 = if i[0x2C] == 0xFF { be_u16 } else { le_u16 }; + let p_u32 = if use_be(i[0x2C]) { be_u32 } else { le_u32 }; + let p_u16 = if use_be(i[0x2C]) { be_u16 } else { le_u16 }; let (i, (file_size, header_size, data_version, num_entries, entry_size)) = tuple((p_u32, p_u16, p_u16, p_u16, p_u16))(i)?; - let (_, param_name) = take_cstring(&i[..0x20])?; + let (i, param_name) = take_cstring_from(i, 0x20)?; let (i, (endianness, unicode, format_version)) = - tuple((le_u8, le_u8, p_u16))(&i[0x20..])?; + tuple((le_u8, le_u8, p_u16))(i)?; + + let (i, ofs_entries) = if has_ofs_entries(format_version) { + let p_u64 = if use_be(i[0x2C]) { be_u64 } else { le_u64 }; + p_u64(i)? + } else { + (i, 0) + }; + + Ok(( i, ParamdefHeader { @@ -37,20 +54,118 @@ fn parse_header(i: &[u8]) -> IResult<&[u8], ParamdefHeader> { data_version, num_entries, entry_size, - param_name: param_name.to_vec(), + param_name: String::from_utf8_lossy(param_name).to_string(), endianness, unicode, format_version, + ofs_entries, + } + )) +} + +pub struct ParamdefEntry { + pub display_name: String, + pub display_type: String, + pub display_format: String, + pub default_value: f32, + pub min_value: f32, + pub max_value: f32, + pub increment: f32, + pub edit_flags: u32, + pub byte_count: u32, + pub ofs_desc: ParamdefEntryDescOffset, + pub internal_type: String, + pub internal_name: Option, + pub sort_id: u32, + + pub description: Option, +} + +pub union ParamdefEntryDescOffset { + ofs32: u32, + ofs64: u64, +} + +fn parse_entry<'a>(i: &'a[u8], header: &ParamdefHeader) -> IResult<&'a[u8], ParamdefEntry> { + let (i, display_name) = take_cstring_from(i, 0x40)?; + let (i, display_type) = take_cstring_from(i, 0x8)?; + let (i, display_format) = take_cstring_from(i, 0x8)?; + + let p_f32 = if header.endianness == 0xFF { be_f32 } else { le_f32 }; + let p_u32 = if header.endianness == 0xFF { be_u32 } else { le_u32 }; + let p_u64 = if header.endianness == 0xFF { be_u64 } else { le_u64 }; + + let (i, (default_value, min_value, max_value, increment, edit_flags, byte_count)) = + tuple((p_f32, p_f32, p_f32, p_f32, p_u32, p_u32))(i)?; + + let (i, ofs_desc) = if header.format_version < 201 { + let (i, o) = p_u32(i)?; + (i, ParamdefEntryDescOffset { ofs32: o }) + } else { + let (i, o) = p_u64(i)?; + (i, ParamdefEntryDescOffset { ofs64: o }) + }; + + let (i, internal_type) = take_cstring_from(i, 0x20)?; + + let (i, internal_name): (&[u8], Option) = if header.format_version >= 102 { + take_cstring_from(i, 0x20).map(|(i, s)| (i, Some(sjis_to_string_lossy(s))))? + } else { + (i, None) + }; + + let (i, sort_id) = if header.format_version >= 104 { p_u32(i)? } else { (i, 0) }; + + Ok(( + i, + ParamdefEntry { + display_name: sjis_to_string_lossy(display_name), + display_type: sjis_to_string_lossy(display_type), + display_format: sjis_to_string_lossy(display_format), + default_value, + min_value, + max_value, + increment, + edit_flags, + byte_count, + ofs_desc, + internal_type: sjis_to_string_lossy(internal_type), + internal_name, + sort_id, + + description: None, } )) } -#[derive(Debug)] pub struct Paramdef { pub header: ParamdefHeader, + pub entries: Vec, } pub fn parse(i: &[u8]) -> IResult<&[u8], Paramdef> { + let full_file = i; let (i, header) = parse_header(i)?; - Ok((i, Paramdef { header })) + let i = if header.has_ofs_entries() { + let ofs_entries = header.ofs_entries as usize; + &full_file[ofs_entries..] + } else { + i // Unsure if header.header_size has to be used here, pray there never is padding. + }; + let (i, mut entries) = count(|i| parse_entry(i, &header), header.num_entries as usize)(i)?; + + for entry in &mut entries { + let ofs: usize = if header.has_64b_ofs_desc() { + unsafe { entry.ofs_desc.ofs64 as usize } + } else { + unsafe { entry.ofs_desc.ofs32 as usize } + }; + if ofs == 0 { + continue + } + let (_, sjis_desc) = take_cstring(&full_file[ofs..])?; + entry.description = Some(sjis_to_string_lossy(sjis_desc)); + } + + Ok((i, Paramdef { header, entries })) } diff --git a/src/unpackers/paramdef.rs b/src/unpackers/paramdef.rs index 33bf184..f86e013 100644 --- a/src/unpackers/paramdef.rs +++ b/src/unpackers/paramdef.rs @@ -15,10 +15,22 @@ pub fn load_paramdef(paramdef_data: &[u8]) -> Result Ok(result), Err(NomError(e)) | Err(NomFailure(e)) => Err(UnpackError::parsing_err("PARAMDEF", e.1)), - e => Err(UnpackError::Unknown(format!("Unknown error: {:?}", e))), + Err(e) => Err(UnpackError::Unknown(format!("Unknown error: {:?}", e))), } } pub fn print_paramdef(paramdef: ¶mdef::Paramdef) { - println!("{:?}", paramdef); + println!("{} -- {} entries", paramdef.header.param_name, paramdef.header.num_entries); + println!("Data version: {}", paramdef.header.data_version); + println!("Format version: {}", paramdef.header.format_version); + + for entry in ¶mdef.entries { + println!(" - {} ({})", entry.display_name, entry.display_type); + if let Some(name) = &entry.internal_name { + println!(" Internal name and type: {}, {}", name, entry.internal_type); + } + if let Some(desc) = &entry.description { + println!(" Description: {}", desc); + } + } }