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(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"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, 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, wad_c_temp: String, } // EOF