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
bhf Extracts BHF/BDT contents
bnd Extracts BND contents
dat Extracts King's Field IV DAT contents
dcx Extracts and decompress DCX data
hash Calculates hash for a string
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")
.help("PARAMDEF file path")
.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();
process::exit(match matches.subcommand() {
@ -97,6 +105,7 @@ fn main() {
("bhf", Some(s)) => { cmd_bhf(s) }
("paramdef", Some(s)) => { cmd_paramdef(s) }
("param", Some(s)) => { cmd_param(s) }
("dat", Some(s)) => { cmd_dat(s) }
_ => { 0 }
})
}
@ -239,3 +248,12 @@ fn cmd_param(args: &ArgMatches) -> i32 {
};
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 common;
pub mod dcx;
pub mod dat;
pub mod param;
pub mod paramdef;
}
@ -16,6 +17,7 @@ pub mod unpackers {
pub mod bnd;
pub mod dcx;
pub mod errors;
pub mod dat;
pub mod param;
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 {
// Extract all entries, print but ignore path errors.
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))),
}
}