dat: extract archive contents (King's Field IV)
This commit is contained in:
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;
|
||||||
}
|
}
|
||||||
|
|
47
src/parsers/dat.rs
Normal file
47
src/parsers/dat.rs
Normal 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 }))
|
||||||
|
}
|
|
@ -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
68
src/unpackers/dat.rs
Normal 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))),
|
||||||
|
}
|
||||||
|
}
|
Reference in a new issue