paramdef: complete parsing and pretty print it

This commit is contained in:
dece 2020-05-16 15:23:55 +02:00
parent 09151b6579
commit 2cdc815aff
5 changed files with 193 additions and 19 deletions

View file

@ -74,7 +74,7 @@ fn main() {
.help("Overwrite existing files") .help("Overwrite existing files")
.short("f").long("force").takes_value(false).required(false))) .short("f").long("force").takes_value(false).required(false)))
.subcommand(SubCommand::with_name("paramdef") .subcommand(SubCommand::with_name("paramdef")
.about("TODO") .about("Print PARAMDEF contents")
.arg(Arg::with_name("file") .arg(Arg::with_name("file")
.help("PARAMDEF file path") .help("PARAMDEF file path")
.takes_value(true).required(true))) .takes_value(true).required(true)))

View file

@ -155,10 +155,9 @@ pub fn parse(i: &[u8]) -> IResult<&[u8], Bnd> {
for info in &mut file_infos { for info in &mut file_infos {
let ofs_path = info.ofs_path as usize; let ofs_path = info.ofs_path as usize;
let (_, sjis_path) = take_cstring(&full_file[ofs_path..])?; let (_, sjis_path) = take_cstring(&full_file[ofs_path..])?;
info.path = sjis_to_string(sjis_path); info.path = sjis_to_string(sjis_path).or_else(|| {
if info.path.is_none() { eprintln!("Failed to parse path: {:?}", sjis_path); None
eprintln!("Failed to parse path: {:?}", sjis_path); });
}
} }
} }
Ok((full_file, Bnd { header, file_infos })) Ok((full_file, Bnd { header, file_infos }))

View file

@ -2,10 +2,21 @@ use encoding_rs::SHIFT_JIS;
use nom::IResult; use nom::IResult;
use nom::bytes::complete::take_while; use nom::bytes::complete::take_while;
/// Parse a zero-terminated string from the slice.
pub fn take_cstring(i: &[u8]) -> IResult<&[u8], &[u8]> { pub fn take_cstring(i: &[u8]) -> IResult<&[u8], &[u8]> {
take_while(|c| c != b'\0')(i) 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<String> { pub fn sjis_to_string(i: &[u8]) -> Option<String> {
let (cow, _, has_errors) = SHIFT_JIS.decode(i); let (cow, _, has_errors) = SHIFT_JIS.decode(i);
if has_errors { if has_errors {
@ -13,3 +24,40 @@ pub fn sjis_to_string(i: &[u8]) -> Option<String> {
} }
Some(cow.to_string()) 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()))
);
}
}

View file

@ -1,12 +1,9 @@
use std::str;
use nom::IResult; use nom::IResult;
use nom::bytes::complete::{tag, take};
use nom::multi::count; use nom::multi::count;
use nom::number::complete::*; use nom::number::complete::*;
use nom::sequence::tuple; 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)] #[derive(Debug)]
pub struct ParamdefHeader { pub struct ParamdefHeader {
@ -15,20 +12,40 @@ pub struct ParamdefHeader {
pub data_version: u16, pub data_version: u16,
pub num_entries: u16, pub num_entries: u16,
pub entry_size: u16, pub entry_size: u16,
pub param_name: Vec<u8>, pub param_name: String,
pub endianness: u8, pub endianness: u8,
pub unicode: u8, pub unicode: u8,
pub format_version: u16, 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> { fn parse_header(i: &[u8]) -> IResult<&[u8], ParamdefHeader> {
let p_u32 = if i[0x2C] == 0xFF { be_u32 } else { le_u32 }; let p_u32 = if use_be(i[0x2C]) { be_u32 } else { le_u32 };
let p_u16 = if i[0x2C] == 0xFF { be_u16 } else { le_u16 }; 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)) = let (i, (file_size, header_size, data_version, num_entries, entry_size)) =
tuple((p_u32, p_u16, p_u16, p_u16, p_u16))(i)?; 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)) = 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(( Ok((
i, i,
ParamdefHeader { ParamdefHeader {
@ -37,20 +54,118 @@ fn parse_header(i: &[u8]) -> IResult<&[u8], ParamdefHeader> {
data_version, data_version,
num_entries, num_entries,
entry_size, entry_size,
param_name: param_name.to_vec(), param_name: String::from_utf8_lossy(param_name).to_string(),
endianness, endianness,
unicode, unicode,
format_version, 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<String>,
pub sort_id: u32,
pub description: Option<String>,
}
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<String>) = 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 struct Paramdef {
pub header: ParamdefHeader, pub header: ParamdefHeader,
pub entries: Vec<ParamdefEntry>,
} }
pub fn parse(i: &[u8]) -> IResult<&[u8], Paramdef> { pub fn parse(i: &[u8]) -> IResult<&[u8], Paramdef> {
let full_file = i;
let (i, header) = parse_header(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 }))
} }

View file

@ -15,10 +15,22 @@ pub fn load_paramdef(paramdef_data: &[u8]) -> Result<paramdef::Paramdef, UnpackE
match paramdef::parse(paramdef_data) { match paramdef::parse(paramdef_data) {
Ok((_, result)) => Ok(result), Ok((_, result)) => Ok(result),
Err(NomError(e)) | Err(NomFailure(e)) => Err(UnpackError::parsing_err("PARAMDEF", e.1)), 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: &paramdef::Paramdef) { pub fn print_paramdef(paramdef: &paramdef::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 &paramdef.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);
}
}
} }