dat: extract archive contents (King's Field IV)

This commit is contained in:
dece 2020-05-23 19:49:43 +02:00
parent 3c3a9d0028
commit 1c509413bf
6 changed files with 137 additions and 1 deletions

View file

@ -40,6 +40,7 @@ SUBCOMMANDS:
bhds Extracts all BHD/BDT content (alphabetically) in a folder bhds Extracts all BHD/BDT content (alphabetically) in a folder
bhf Extracts BHF/BDT contents bhf Extracts BHF/BDT contents
bnd Extracts BND contents bnd Extracts BND contents
dat Extracts King's Field IV DAT contents
dcx Extracts and decompress DCX data dcx Extracts and decompress DCX data
hash Calculates hash for a string hash Calculates hash for a string
help Prints this message or the help of the given subcommand(s) help Prints this message or the help of the given subcommand(s)

View file

@ -86,6 +86,14 @@ fn main() {
.arg(Arg::with_name("paramdef") .arg(Arg::with_name("paramdef")
.help("PARAMDEF file path") .help("PARAMDEF file path")
.short("d").long("def").takes_value(true).required(false))) .short("d").long("def").takes_value(true).required(false)))
.subcommand(SubCommand::with_name("dat")
.about("Extracts King's Field IV DAT contents")
.arg(Arg::with_name("file")
.help("DAT file path")
.takes_value(true).required(true))
.arg(Arg::with_name("output")
.help("Output directory")
.short("o").long("output").takes_value(true).required(true)))
.get_matches(); .get_matches();
process::exit(match matches.subcommand() { process::exit(match matches.subcommand() {
@ -97,6 +105,7 @@ fn main() {
("bhf", Some(s)) => { cmd_bhf(s) } ("bhf", Some(s)) => { cmd_bhf(s) }
("paramdef", Some(s)) => { cmd_paramdef(s) } ("paramdef", Some(s)) => { cmd_paramdef(s) }
("param", Some(s)) => { cmd_param(s) } ("param", Some(s)) => { cmd_param(s) }
("dat", Some(s)) => { cmd_dat(s) }
_ => { 0 } _ => { 0 }
}) })
} }
@ -239,3 +248,12 @@ fn cmd_param(args: &ArgMatches) -> i32 {
}; };
0 0
} }
fn cmd_dat(args: &ArgMatches) -> i32 {
let file_path: &str = args.value_of("file").unwrap();
let output_path: &str = args.value_of("output").unwrap();
match unpackers::dat::extract_dat_file(file_path, output_path) {
Err(e) => { eprintln!("Failed to extract DAT: {:?}", e); 1 }
_ => 0
}
}

View file

@ -7,6 +7,7 @@ pub mod parsers {
pub mod bnd; pub mod bnd;
pub mod common; pub mod common;
pub mod dcx; pub mod dcx;
pub mod dat;
pub mod param; pub mod param;
pub mod paramdef; pub mod paramdef;
} }
@ -16,6 +17,7 @@ pub mod unpackers {
pub mod bnd; pub mod bnd;
pub mod dcx; pub mod dcx;
pub mod errors; pub mod errors;
pub mod dat;
pub mod param; pub mod param;
pub mod paramdef; pub mod paramdef;
} }

47
src/parsers/dat.rs Normal file
View file

@ -0,0 +1,47 @@
use nom::IResult;
use nom::multi::count;
use nom::number::complete::*;
use nom::sequence::tuple;
use crate::parsers::common::take_cstring_from;
#[derive(Debug)]
pub struct DatHeader {
pub unk00: u32,
pub num_files: u32,
}
fn parse_header(i: &[u8]) -> IResult<&[u8], DatHeader> {
let (i, (unk00, num_files)) = tuple((le_u32, le_u32))(i)?;
Ok((i, DatHeader { unk00, num_files }))
}
#[derive(Debug)]
pub struct DatFileEntry {
pub name: String,
pub size: u32,
pub padded_size: u32,
pub ofs_data: u32,
}
fn parse_file_entry(i: &[u8]) -> IResult<&[u8], DatFileEntry> {
let (i, name) = take_cstring_from(i, 0x34)?;
let name = String::from_utf8_lossy(name).to_string();
let (i, (size, padded_size, ofs_data)) = tuple((le_u32, le_u32, le_u32))(i)?;
Ok((i, DatFileEntry { name, size, padded_size, ofs_data }))
}
#[derive(Debug)]
pub struct Dat {
pub header: DatHeader,
pub files: Vec<DatFileEntry>,
}
/// Parse a DAT archive, returning it with its full file data.
pub fn parse(i: &[u8]) -> IResult<&[u8], Dat> {
let full_file = i;
let (_, header) = parse_header(i)?;
let i = &full_file[0x40..];
let (_, files) = count(parse_file_entry, header.num_files as usize)(i)?;
Ok((full_file, Dat { header, files }))
}

View file

@ -43,7 +43,7 @@ pub fn extract_bnd(
for file_info in &bnd.file_infos { for file_info in &bnd.file_infos {
// Extract all entries, print but ignore path errors. // Extract all entries, print but ignore path errors.
match extract_bnd_entry(file_info, bnd_data, output_dir, overwrite) { match extract_bnd_entry(file_info, bnd_data, output_dir, overwrite) {
Err(UnpackError::Naming(e)) => { eprintln!("{}", e) } Err(UnpackError::Naming(e)) => eprintln!("{}", e),
_ => {} _ => {}
} }
} }

68
src/unpackers/dat.rs Normal file
View file

@ -0,0 +1,68 @@
use std::fs;
use std::io::Write;
use std::path;
use nom::Err::{Error as NomError, Failure as NomFailure};
use crate::parsers::dat;
use crate::unpackers::errors::UnpackError;
use crate::utils::fs as utils_fs;
pub fn extract_dat_file(dat_path: &str, output_path: &str) -> Result<(), UnpackError> {
let (dat, dat_data) = load_dat_file(dat_path)?;
extract_dat(&dat, dat_data, output_path)
}
pub fn extract_dat(
dat: &dat::Dat,
dat_data: Vec<u8>,
output_path: &str
) -> Result<(), UnpackError> {
let output_dir = path::Path::new(output_path);
utils_fs::ensure_dir_exists(output_dir)?;
for file_entry in &dat.files {
match extract_file(file_entry, &dat_data, output_dir) {
Err(UnpackError::Io(e)) => eprintln!("Can't extract {}: {}", file_entry.name, e),
_ => {}
}
}
Ok(())
}
fn extract_file(
file_entry: &dat::DatFileEntry,
data: &Vec<u8>,
output_dir: &path::Path
) -> Result<(), UnpackError> {
let ofs_start = file_entry.ofs_data as usize;
let ofs_end = ofs_start + file_entry.size as usize;
let data = &data[ofs_start..ofs_end];
let internal_path = &file_entry.name;
// If the path contains dirs, they have to be created.
if internal_path.contains('/') {
let internal_pb = path::PathBuf::from(internal_path);
let internal_parent_dir = internal_pb.parent()
.ok_or(UnpackError::Naming(format!("Bad path: {:?}", internal_pb)))?;
let mut parent_dir = output_dir.to_path_buf();
parent_dir.push(internal_parent_dir);
utils_fs::ensure_dir_exists(&parent_dir)?;
}
let mut file_path = output_dir.to_path_buf();
file_path.push(internal_path);
let mut output_file = fs::File::create(file_path)?;
output_file.write_all(&data)?;
Ok(())
}
pub fn load_dat_file(dat_path: &str) -> Result<(dat::Dat, Vec<u8>), UnpackError> {
let dat_data = utils_fs::open_file_to_vec(path::Path::new(dat_path))?;
Ok((load_dat(&dat_data)?, dat_data))
}
pub fn load_dat(dat_data: &[u8]) -> Result<dat::Dat, UnpackError> {
match dat::parse(&dat_data) {
Ok((_, dat)) => Ok(dat),
Err(NomError(e)) | Err(NomFailure(e)) => Err(UnpackError::parsing_err("DAT", e.1)),
e => Err(UnpackError::Unknown(format!("Unknown error: {:?}", e))),
}
}