use maraiah::{durandal::{bin::*, chunk::*, err::*, file::*, image::*, text::*}, marathon::{machdr, map, pict, shp, term, wad}}; use std::{collections::HashSet, fs, io::{self, Write}}; fn make_tga(fname: &str, im: &impl Image) -> ResultS<()> { let out = fs::File::create(fname)?; let mut out = io::BufWriter::new(out); 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 out = fs::File::create(&fname)?; let mut out = io::BufWriter::new(out); out.write(cnk)?; Ok(()) } fn make_yaml(data: &impl serde::Serialize) -> ResultS<()> { serde_yaml::to_writer(io::stdout(), &data)?; println!(); Ok(()) } fn dump_chunk(opt: &Options, cid: &Ident, cnk: &[u8], eid: u16) -> ResultS<()> { if opt.wad_all { make_chunk(opt, cid, cnk, eid)?; return Ok(()); } match cid { b"PICT" => { if opt.wad_chunks.contains(cid) { let im = pict::load_pict(cnk)?; make_tga(&format!("{}/pict_{}.tga", opt.out_dir, eid), &im)?; } } b"Minf" => { if opt.wad_chunks.contains(cid) { make_yaml(&map::Minf::chunk(cnk)?)?; } } b"EPNT" => { if opt.wad_chunks.contains(cid) { make_yaml(&map::Endpoint::chunk(cnk)?)?; } } b"PNTS" => { if opt.wad_chunks.contains(cid) { make_yaml(&map::Point::chunk(cnk)?)?; } } b"LINS" => { if opt.wad_chunks.contains(cid) { make_yaml(&map::Line::chunk(cnk)?)?; } } b"SIDS" => { if opt.wad_chunks.contains(cid) { make_yaml(&map::Side::chunk(cnk)?)?; } } b"term" => { if opt.wad_chunks.contains(cid) { make_yaml(&term::Terminal::chunk(cnk)?)?; } } cid => { 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(&wad)?; } 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(&cl.tabs)?; } if opt.shp_frm { make_yaml(&cl.frms)?; } if opt.shp_seq { make_yaml(&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 main() -> ResultS<()> { use argparse::*; use memmap::Mmap; let mut opt: Options = Default::default(); { let mut ap = ArgumentParser::new(); ap.set_description(env!("CARGO_PKG_DESCRIPTION")); ap.add_option(&["-v", "--version"], Print(format!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"))), "Show the version"); ap.refer(&mut opt.shp_tab) .add_option(&["--shp-write-tab"], StoreTrue, "shp: Dump all CLUTs as YAML to standard output"); ap.refer(&mut opt.shp_bmp) .add_option(&["--shp-dump-bitmaps"], StoreTrue, "shp: Dump bitmaps into a folder"); ap.refer(&mut opt.shp_bmp_all) .add_option(&["--shp-dump-more-bitmaps"], StoreTrue, "shp: Dump all color variations of each bitmap"); ap.refer(&mut opt.shp_frm) .add_option(&["--shp-write-frm"], StoreTrue, "shp: Dump all frames as YAML to standard output"); ap.refer(&mut opt.shp_seq) .add_option(&["--shp-write-seq"], StoreTrue, "shp: Dump all sequences as YAML to standard output"); ap.refer(&mut opt.wad_all) .add_option(&["--wad-dump-all"], StoreTrue, "wad: Dump all chunks into a folder"); ap.refer(&mut opt.wad_unknown) .add_option(&["--wad-dump-unknown"], StoreTrue, "wad: Dump all unknown chunks into a folder"); ap.refer(&mut opt.wad_header) .add_option(&["--wad-write-header"], StoreTrue, "wad: Dump header info as YAML to standard output"); ap.refer(&mut opt.wad_c_temp) .add_option(&["--wad-write-chunks"], Store, "wad: Dump specified chunks in various formats"); ap.refer(&mut opt.out_dir) .add_option(&["--out-dir"], Store, "Sets output directory for dump options"); ap.refer(&mut opt.inputs) .add_argument("inputs", Collect, "Input files"); 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(c_iden(ctyp.as_bytes(), 0)?); } } 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 = c_data(&mm, machdr::try_mac_header(&mm)..)?; match typ { "wad:" => process_wad(&opt, b), "shp:" => process_shp(&opt, b), _ => Err(err_msg("invalid file type specified on commandline")), }?; } Ok(()) } #[derive(Default)] struct Options { inputs: Vec, out_dir: String, shp_tab: bool, shp_bmp: bool, shp_bmp_all: bool, shp_frm: bool, shp_seq: bool, wad_all: bool, wad_unknown: bool, wad_header: bool, wad_chunks: HashSet, wad_c_temp: String, } // EOF