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

master
dece 4 years ago
parent 3c3a9d0028
commit 1c509413bf

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

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

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

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

@ -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),
_ => {} _ => {}
} }
} }

@ -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))),
}
}
Loading…
Cancel
Save