diff --git a/Cargo.toml b/Cargo.toml index ef54be1..4c3859f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,8 @@ failure = "0.1" generic-array = "0.12" #gtk = "0.4" memmap = "0.6" +serde = {version = "1.0", features = ["derive"]} +serde_yaml = "0.8" [profile.dev] opt-level = 1 diff --git a/src/durandal/bin.rs b/src/durandal/bin.rs index 6dc5c17..b5e0aba 100644 --- a/src/durandal/bin.rs +++ b/src/durandal/bin.rs @@ -1,6 +1,7 @@ //! Binary data conversion utilities. use crate::durandal::err::*; +use serde::Serialize; use std::{fmt, num::NonZeroU16, slice::SliceIndex}; /// Returns a byte array from `b` at `i`. @@ -144,6 +145,7 @@ pub type Ident = [u8; 4]; /// An object identified by a `u16` which may be `u16::max_value()` to /// represent None. +#[derive(Serialize)] pub struct ObjID(Option); // EOF diff --git a/src/durandal/file.rs b/src/durandal/file.rs new file mode 100644 index 0000000..c9830fc --- /dev/null +++ b/src/durandal/file.rs @@ -0,0 +1,16 @@ +//! File utilities. + +use crate::durandal::err::*; +use std::fs; + +pub fn validate_folder_path(p: &str) -> ResultS<()> +{ + let at = fs::metadata(p)?; + if !at.is_dir() { + Err(err_msg("not a directory")) + } else { + Ok(()) + } +} + +// EOF diff --git a/src/durandal/fx32.rs b/src/durandal/fx32.rs index dee3560..da49092 100644 --- a/src/durandal/fx32.rs +++ b/src/durandal/fx32.rs @@ -1,6 +1,8 @@ +use serde::Serialize; use std::{fmt::{self, Write}, ops}; +#[derive(Serialize)] pub struct Fx32(i32); impl Fx32 diff --git a/src/durandal/mod.rs b/src/durandal/mod.rs index a7dc90d..c12ccfc 100644 --- a/src/durandal/mod.rs +++ b/src/durandal/mod.rs @@ -8,6 +8,7 @@ pub mod err; pub mod bin; pub mod chunk; pub mod crc; +pub mod file; pub mod fx32; pub mod image; pub mod text; diff --git a/src/main.rs b/src/main.rs index 0371dff..765f58f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,98 +1,149 @@ -use maraiah::{durandal::{bin::*, chunk::*, err::*, image::*, text::*}, +use maraiah::{durandal::{bin::*, chunk::*, err::*, file::*, image::*, text::*}, marathon::{machdr, map, pict, shp, term, wad}}; -use std::{fs, +use std::{collections::HashSet, + fs, io::{self, Write}}; -fn write_chunk(cid: &Ident, cnk: &[u8], eid: u16) -> ResultS<()> +fn make_tga(fname: &str, im: &impl Image) -> ResultS<()> { - let fname = format!("out/{:04}{}.bin", eid, mac_roman_conv(cid)); + 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 read_chunk(cid: &Ident, cnk: &[u8], eid: u16) -> ResultS<()> +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" => { - let im = pict::load_pict(cnk)?; - eprintln!("entry {} has PICT {}x{}", eid, im.w(), im.h()); - let out = fs::File::create(&format!("out/{}.ppm", eid))?; - let mut out = io::BufWriter::new(out); - write_ppm(&mut out, &im)?; + if opt.wad_chunks.contains(cid) { + let im = pict::load_pict(cnk)?; + make_tga(&format!("{}/pict_{}.tga", opt.out_dir, eid), &im)?; + } } b"Minf" => { - let minf = map::Minf::chunk(cnk)?; - eprintln!("entry {} has {:#?}", eid, minf); + if opt.wad_chunks.contains(cid) { + make_yaml(&map::Minf::chunk(cnk)?)?; + } } b"EPNT" => { - let epnt = map::Endpoint::chunk(cnk)?; - eprintln!("entry {} has EPNT {:#?}", eid, epnt); + if opt.wad_chunks.contains(cid) { + make_yaml(&map::Endpoint::chunk(cnk)?)?; + } } b"PNTS" => { - let epnt = map::Point::chunk(cnk)?; - eprintln!("entry {} has PNTS {:#?}", eid, epnt); + if opt.wad_chunks.contains(cid) { + make_yaml(&map::Point::chunk(cnk)?)?; + } } b"LINS" => { - let line = map::Line::chunk(cnk)?; - eprintln!("entry {} has LINS {:#?}", eid, line); + if opt.wad_chunks.contains(cid) { + make_yaml(&map::Line::chunk(cnk)?)?; + } } b"SIDS" => { - let line = map::Side::chunk(cnk)?; - eprintln!("entry {} has SIDS {:#?}", eid, line); + if opt.wad_chunks.contains(cid) { + make_yaml(&map::Side::chunk(cnk)?)?; + } } b"term" => { - let term = term::Terminal::chunk(cnk)?; - eprintln!("entry {} has term {:#?}", eid, term); + if opt.wad_chunks.contains(cid) { + make_yaml(&term::Terminal::chunk(cnk)?)?; + } } cid => { - write_chunk(cid, cnk, eid)?; + if opt.wad_unknown { + make_chunk(opt, cid, cnk, eid)?; + } } } Ok(()) } -fn process_wad(b: &[u8]) -> ResultS<()> +fn process_wad(opt: &Options, b: &[u8]) -> ResultS<()> { let wad = wad::Wad::new(b)?; - eprintln!("{:#?}", wad); + if opt.wad_header { + make_yaml(&wad)?; + } for (eid, ent) in wad.entries { for (cid, cnk) in ent.chunks { - read_chunk(&cid, cnk, eid)?; + dump_chunk(opt, &cid, cnk, eid)?; } } Ok(()) } -fn dump_bitmaps(c: &shp::Collection, i: usize) -> ResultS<()> +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() { - for (k, tab) in c.tabs.iter().enumerate() { - let fname = format!("out/shape{}_{}_{}.tga", i, j, k); - let out = fs::File::create(&fname)?; - let mut out = io::BufWriter::new(out); - write_tga(&mut out, &shp::ImageShp::new(bmp, &tab))?; + 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 process_shp(b: &[u8]) -> ResultS<()> +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(cl, i)?; - eprintln!("<{} lo> {:#?}\n{:#?}", i, cl.frms, cl.seqs); + dump_bitmaps(opt, cl, i)?; + write_shp_objs(opt, cl)?; } if let Some(cl) = &cl.1 { - dump_bitmaps(cl, i + 100)?; - eprintln!("<{} hi> {:#?}\n{:#?}", i, cl.frms, cl.seqs); + dump_bitmaps(opt, cl, i + 100)?; + write_shp_objs(opt, cl)?; } } @@ -104,7 +155,7 @@ fn main() -> ResultS<()> use argparse::*; use memmap::Mmap; - let mut args: Vec = Vec::new(); + let mut opt: Options = Default::default(); { let mut ap = ArgumentParser::new(); ap.set_description(env!("CARGO_PKG_DESCRIPTION")); @@ -113,12 +164,58 @@ fn main() -> ResultS<()> env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"))), "Show the version"); - ap.refer(&mut args) - .add_argument("inputs", List, "Input files"); + 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(); } - for arg in &args { + 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 { @@ -130,8 +227,8 @@ fn main() -> ResultS<()> let b = c_data(&mm, machdr::try_mac_header(&mm)..)?; match typ { - "wad:" => process_wad(b), - "shp:" => process_shp(b), + "wad:" => process_wad(&opt, b), + "shp:" => process_shp(&opt, b), _ => Err(err_msg("invalid file type specified on commandline")), }?; } @@ -139,4 +236,21 @@ fn main() -> ResultS<()> 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 diff --git a/src/marathon/map.rs b/src/marathon/map.rs index 2001da5..aa87676 100644 --- a/src/marathon/map.rs +++ b/src/marathon/map.rs @@ -2,6 +2,7 @@ use crate::{durandal::{bin::*, chunk::*, err::*, fx32::*, text::mac_roman_conv}, marathon::xfer::TransferMode}; use bitflags::bitflags; +use serde::Serialize; use std::fmt; impl Chunked for Point @@ -118,13 +119,14 @@ impl Chunker for Minf type Unit = i16; +#[derive(Serialize)] pub struct Point { x: Unit, y: Unit, } -#[derive(Debug)] +#[derive(Debug, Serialize)] pub struct Endpoint { flags: EndpFlags, @@ -134,7 +136,7 @@ pub struct Endpoint support: u16, } -#[derive(Debug)] +#[derive(Debug, Serialize)] pub struct Line { flags: LineFlags, @@ -149,14 +151,14 @@ pub struct Line poly_b: u16, } -#[derive(Debug)] +#[derive(Debug, Serialize)] pub struct SideTex { offs: Point, tex_id: ObjID, } -#[derive(Debug)] +#[derive(Debug, Serialize)] pub struct Side { stype: u16, @@ -176,7 +178,7 @@ pub struct Side shade: Fx32, } -#[derive(Debug)] +#[derive(Debug, Serialize)] pub struct Minf { env_code: u16, @@ -189,6 +191,7 @@ pub struct Minf } bitflags! { + #[derive(Serialize)] pub struct EndpFlags: u16 { const Solid = 0x00_01; @@ -198,6 +201,7 @@ bitflags! { } bitflags! { + #[derive(Serialize)] pub struct LineFlags: u16 { const TransSide = 0x02_00; @@ -210,6 +214,7 @@ bitflags! { } bitflags! { + #[derive(Serialize)] pub struct SideFlags: u16 { const Status = 0x00_01; @@ -224,6 +229,7 @@ bitflags! { } bitflags! { + #[derive(Serialize)] pub struct EnvFlags: u16 { const Vacuum = 0x00_01; @@ -243,6 +249,7 @@ bitflags! { } bitflags! { + #[derive(Serialize)] pub struct EntFlags: u32 { const Solo = 0x00_01; @@ -257,6 +264,7 @@ bitflags! { } bitflags! { + #[derive(Serialize)] pub struct MsnFlags: u16 { const Extermination = 0x00_01; diff --git a/src/marathon/shp.rs b/src/marathon/shp.rs index 0527db6..5283b16 100644 --- a/src/marathon/shp.rs +++ b/src/marathon/shp.rs @@ -3,6 +3,7 @@ use crate::{durandal::{bin::*, err::*, fx32::*, image::*, text::*}, marathon::xfer::TransferMode}; use bitflags::bitflags; +use serde::Serialize; fn color(b: &[u8]) -> ResultS<(usize, ColorShp)> { @@ -313,7 +314,7 @@ impl Color for ColorShp } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize)] pub enum ColorShp { Translucent, @@ -342,7 +343,7 @@ pub struct ImageShp<'a, 'b> clut: &'b [ColorShp], } -#[derive(Debug)] +#[derive(Debug, Serialize)] pub struct Frame { flags: FrameFlags, @@ -356,7 +357,7 @@ pub struct Frame wrl_y: i16, } -#[derive(Debug)] +#[derive(Debug, Serialize)] pub struct Sequence { name: String, @@ -393,6 +394,7 @@ bitflags! { } bitflags! { + #[derive(Serialize)] pub struct FrameFlags: u16 { const Obscure = 0x20_00; @@ -402,7 +404,7 @@ bitflags! { } c_enum! { - #[derive(Debug)] + #[derive(Debug, Serialize)] pub enum CollectionType: u16 { 0 => Unused, @@ -414,7 +416,7 @@ c_enum! { } c_enum! { - #[derive(Debug)] + #[derive(Debug, Serialize)] pub enum ViewType: u16 { 1 => Anim, diff --git a/src/marathon/term.rs b/src/marathon/term.rs index eba851e..8dc100b 100644 --- a/src/marathon/term.rs +++ b/src/marathon/term.rs @@ -1,4 +1,5 @@ use crate::durandal::{bin::*, chunk::*, err::*, text::*}; +use serde::Serialize; use std::fmt; fn read_group(b: &[u8], text: &[u8]) -> ResultS @@ -78,7 +79,7 @@ impl Chunker> for Terminal } } -#[derive(Debug)] +#[derive(Debug, Serialize)] pub struct Terminal { lines: u16, @@ -86,7 +87,7 @@ pub struct Terminal faces: Vec, } -#[derive(Debug)] +#[derive(Debug, Serialize)] pub struct Face { start: usize, @@ -94,6 +95,7 @@ pub struct Face color: u16, } +#[derive(Serialize)] pub struct Group { ttype: GroupType, @@ -103,7 +105,7 @@ pub struct Group } c_enum! { - #[derive(Debug)] + #[derive(Debug, Serialize)] pub enum GroupType: u16 { 0 => Logon, diff --git a/src/marathon/wad.rs b/src/marathon/wad.rs index efe235a..5f9a516 100644 --- a/src/marathon/wad.rs +++ b/src/marathon/wad.rs @@ -1,6 +1,7 @@ //! Marathon Wad format handling. use crate::durandal::{bin::*, err::*, text::mac_roman_conv}; +use serde::Serialize; use std::{collections::BTreeMap, fmt}; impl Wad<'_> @@ -85,18 +86,19 @@ pub struct Entry<'a> pub appdata: &'a [u8], } -#[derive(Debug)] +#[derive(Debug, Serialize)] pub struct Wad<'a> { wadver: Ver, dataver: u16, origname: String, appsize: usize, + #[serde(skip)] pub entries: EntryMap<'a>, } c_enum! { - #[derive(Debug)] + #[derive(Debug, Serialize)] pub enum Ver: u16 { 0 => Base, diff --git a/src/marathon/xfer.rs b/src/marathon/xfer.rs index 4d9ebe0..ab71c4d 100644 --- a/src/marathon/xfer.rs +++ b/src/marathon/xfer.rs @@ -1,7 +1,8 @@ use crate::durandal::err::*; +use serde::Serialize; c_enum! { - #[derive(Debug)] + #[derive(Debug, Serialize)] pub enum TransferMode: u16 { 0 => Normal,