304 lines
8.0 KiB
Rust
304 lines
8.0 KiB
Rust
use maraiah::{durandal::{bin::*, err::*, file::*, image::*, sound::*, text::*},
|
|
marathon::{machdr, map, pict, shp, snd, trm, wad}};
|
|
use std::{collections::HashSet,
|
|
fs,
|
|
io::{self, Write}};
|
|
|
|
fn make_tga(fname: &str, im: &impl Image) -> ResultS<()>
|
|
{
|
|
let mut out = io::BufWriter::new(fs::File::create(fname)?);
|
|
write_tga(&mut out, im)
|
|
}
|
|
|
|
fn make_chunk(opt: &Options, cid: Ident, cnk: &[u8], eid: u16) -> ResultS<()>
|
|
{
|
|
let cid = mac_roman_conv(&cid);
|
|
let fname = format!("{}/{:04}{}.bin", opt.out_dir, eid, cid);
|
|
let mut out = io::BufWriter::new(fs::File::create(&fname)?);
|
|
out.write_all(cnk)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn make_yaml<T>(opt: &Options, data: &T) -> ResultS<()>
|
|
where T: serde::Serialize + std::fmt::Debug
|
|
{
|
|
if opt.out_debug {
|
|
println!("{:#?}", data);
|
|
} else {
|
|
serde_yaml::to_writer(io::stdout(), &data)?;
|
|
println!();
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn make_wav(fname: &str, snd: &impl Sound) -> ResultS<()>
|
|
{
|
|
let mut out = io::BufWriter::new(fs::File::create(fname)?);
|
|
write_wav(&mut out, snd)
|
|
}
|
|
|
|
fn dump_chunk(opt: &Options, cid: Ident, cnk: &[u8], eid: u16) -> ResultS<()>
|
|
{
|
|
if opt.wad_all {
|
|
make_chunk(opt, cid, cnk, eid)?;
|
|
} else if opt.wad_chunks.contains(&cid) {
|
|
match &cid {
|
|
b"PICT" => {
|
|
let im = pict::load_pict(cnk)?;
|
|
make_tga(&format!("{}/pict_{}.tga", opt.out_dir, eid), &im)?;
|
|
}
|
|
b"Minf" => make_yaml(opt, &map::read_minf(cnk)?)?,
|
|
b"EPNT" => make_yaml(opt, &rd_array(cnk, map::read_epnt)?)?,
|
|
b"PNTS" => make_yaml(opt, &rd_array(cnk, map::read_pnts)?)?,
|
|
b"LINS" => make_yaml(opt, &rd_array(cnk, map::read_lins)?)?,
|
|
b"SIDS" => make_yaml(opt, &rd_array(cnk, map::read_sids)?)?,
|
|
b"POLY" => make_yaml(opt, &rd_array(cnk, map::read_poly)?)?,
|
|
b"LITE" => make_yaml(opt, &rd_array(cnk, map::read_lite)?)?,
|
|
b"OBJS" => make_yaml(opt, &rd_array(cnk, map::read_objs)?)?,
|
|
b"plac" => make_yaml(opt, &rd_array(cnk, map::read_plac)?)?,
|
|
b"ambi" => make_yaml(opt, &rd_array(cnk, map::read_ambi)?)?,
|
|
b"bonk" => make_yaml(opt, &rd_array(cnk, map::read_bonk)?)?,
|
|
b"medi" => make_yaml(opt, &rd_array(cnk, map::read_medi)?)?,
|
|
b"term" => make_yaml(opt, &rd_array(cnk, trm::read_term)?)?,
|
|
_ => (),
|
|
}
|
|
} else if opt.wad_unknown {
|
|
make_chunk(opt, cid, cnk, eid)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn process_wad(opt: &Options, b: &[u8]) -> ResultS<()>
|
|
{
|
|
let wad = wad::Wad::new(b)?;
|
|
|
|
if opt.wad_header {
|
|
make_yaml(opt, &wad.head)?;
|
|
}
|
|
|
|
for (eid, ent) in wad.entries {
|
|
for (cid, cnk) in ent.chunks {
|
|
dump_chunk(opt, cid, cnk, eid)?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn dump_bitmaps(opt: &Options, c: &shp::Collection, i: usize) -> ResultS<()>
|
|
{
|
|
if !opt.shp_bmp {
|
|
return Ok(());
|
|
}
|
|
|
|
for (j, bmp) in c.bmps.iter().enumerate() {
|
|
if opt.shp_bmp_all {
|
|
for (k, tab) in c.tabs.iter().enumerate() {
|
|
let fname = format!("{}/shape{}_{}_{}.tga", opt.out_dir, i, j, k);
|
|
make_tga(&fname, &shp::ImageShp::new(bmp, &tab))?;
|
|
}
|
|
} else {
|
|
let fname = format!("{}/shape{}_{}.tga", opt.out_dir, i, j);
|
|
make_tga(&fname, &shp::ImageShp::new(bmp, &c.tabs[0]))?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn write_shp_objs(opt: &Options, cl: &shp::Collection) -> ResultS<()>
|
|
{
|
|
if opt.shp_tab {
|
|
make_yaml(opt, &cl.tabs)?;
|
|
}
|
|
if opt.shp_frm {
|
|
make_yaml(opt, &cl.frms)?;
|
|
}
|
|
if opt.shp_seq {
|
|
make_yaml(opt, &cl.seqs)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn process_shp(opt: &Options, b: &[u8]) -> ResultS<()>
|
|
{
|
|
for (i, cl) in shp::read_shapes(b)?.iter().enumerate() {
|
|
if let Some(cl) = &cl.0 {
|
|
dump_bitmaps(opt, cl, i)?;
|
|
write_shp_objs(opt, cl)?;
|
|
}
|
|
if let Some(cl) = &cl.1 {
|
|
dump_bitmaps(opt, cl, i + 100)?;
|
|
write_shp_objs(opt, cl)?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn dump_sounds(opt: &Options, st: &snd::SoundTable, c: usize) -> ResultS<()>
|
|
{
|
|
if !opt.snd_dump {
|
|
return Ok(());
|
|
}
|
|
|
|
for (k, sd) in st {
|
|
for (i, snd) in sd.sounds.iter().enumerate() {
|
|
let fname = format!("{}/snd{}_{}_{}.wav", opt.out_dir, c, k, i);
|
|
make_wav(&fname, snd)?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn write_sounds(opt: &Options, st: &snd::SoundTable) -> ResultS<()>
|
|
{
|
|
if opt.snd_write {
|
|
for sd in st.values() {
|
|
make_yaml(opt, &sd.header)?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn process_snd(opt: &Options, b: &[u8]) -> ResultS<()>
|
|
{
|
|
for (c, st) in snd::read_sounds(b)?.iter().enumerate() {
|
|
dump_sounds(opt, st, c)?;
|
|
write_sounds(opt, st)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn main() -> ResultS<()>
|
|
{
|
|
use argparse::*;
|
|
use memmap::Mmap;
|
|
|
|
let mut opt: Options = Default::default();
|
|
{
|
|
let mut ap = ArgumentParser::new();
|
|
|
|
macro_rules! arg {
|
|
($name:expr, $ref:expr, $type:expr, $desc:expr) => {
|
|
ap.refer(&mut $ref).add_option(&[$name], $type, $desc);
|
|
};
|
|
}
|
|
|
|
ap.set_description(env!("CARGO_PKG_DESCRIPTION"));
|
|
|
|
ap.add_option(&["-v", "--version"],
|
|
Print(concat!(env!("CARGO_PKG_NAME"),
|
|
" ",
|
|
env!("CARGO_PKG_VERSION")).to_string()),
|
|
"Show the version");
|
|
|
|
ap.refer(&mut opt.inputs)
|
|
.add_argument("inputs", Collect, "Input files");
|
|
|
|
arg!("--shp-write-tab", opt.shp_tab, StoreTrue,
|
|
"shp: Dump all CLUTs as YAML to standard output");
|
|
|
|
arg!("--shp-dump-bitmaps", opt.shp_bmp, StoreTrue,
|
|
"shp: Dump bitmaps into a folder");
|
|
|
|
arg!("--shp-dump-more-bitmaps", opt.shp_bmp_all, StoreTrue,
|
|
"shp: Dump all color variations of each bitmap");
|
|
|
|
arg!("--shp-write-frm", opt.shp_frm, StoreTrue,
|
|
"shp: Dump all frames as YAML to standard output");
|
|
|
|
arg!("--shp-write-seq", opt.shp_seq, StoreTrue,
|
|
"shp: Dump all sequences as YAML to standard output");
|
|
|
|
arg!("--snd-write", opt.snd_write, StoreTrue,
|
|
"snd: Dump all sound headers as YAML to standard output");
|
|
|
|
arg!("--snd-dump", opt.snd_dump, StoreTrue,
|
|
"snd: Dump all sounds to WAVE files");
|
|
|
|
arg!("--wad-dump-all", opt.wad_all, StoreTrue,
|
|
"wad: Dump all chunks into a folder");
|
|
|
|
arg!("--wad-dump-unknown", opt.wad_unknown, StoreTrue,
|
|
"wad: Dump all unknown chunks into a folder");
|
|
|
|
arg!("--wad-write-header", opt.wad_header, StoreTrue,
|
|
"wad: Dump header info as YAML to standard output");
|
|
|
|
arg!("--wad-write-chunks", opt.wad_c_temp, Store,
|
|
"wad: Dump specified chunks in various formats");
|
|
|
|
arg!("--out-dir", opt.out_dir, Store,
|
|
"Sets output directory for dump options");
|
|
|
|
arg!("--out-debug", opt.out_debug, StoreTrue,
|
|
"Writes debugging output rather than YAML");
|
|
|
|
ap.parse_args_or_exit();
|
|
}
|
|
|
|
if opt.shp_bmp_all {
|
|
opt.shp_bmp = true;
|
|
}
|
|
|
|
if opt.out_dir.is_empty() {
|
|
opt.out_dir = ".".to_string();
|
|
}
|
|
|
|
if !opt.wad_c_temp.is_empty() {
|
|
for ctyp in opt.wad_c_temp.split(',') {
|
|
opt.wad_chunks.insert(ident(ctyp.as_bytes()));
|
|
}
|
|
}
|
|
|
|
validate_folder_path(&opt.out_dir)?;
|
|
|
|
for arg in &opt.inputs {
|
|
let (typ, fna) = if let Some(st) = arg.find(':') {
|
|
arg.split_at(st + 1)
|
|
} else {
|
|
("wad:", arg.as_str())
|
|
};
|
|
|
|
let fp = fs::File::open(fna)?;
|
|
let mm = unsafe {Mmap::map(&fp)?};
|
|
let b = &mm[machdr::try_mac_header(&mm)..];
|
|
|
|
match typ {
|
|
"wad:" => process_wad(&opt, b),
|
|
"shp:" => process_shp(&opt, b),
|
|
"snd:" => process_snd(&opt, b),
|
|
_ => Err(err_msg("invalid file type specified on commandline")),
|
|
}?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct Options
|
|
{
|
|
inputs: Vec<String>,
|
|
out_dir: String,
|
|
out_debug: bool,
|
|
shp_tab: bool,
|
|
shp_bmp: bool,
|
|
shp_bmp_all: bool,
|
|
shp_frm: bool,
|
|
shp_seq: bool,
|
|
snd_dump: bool,
|
|
snd_write: bool,
|
|
wad_all: bool,
|
|
wad_unknown: bool,
|
|
wad_header: bool,
|
|
wad_chunks: HashSet<Ident>,
|
|
wad_c_temp: String,
|
|
}
|
|
|
|
// EOF
|