make the commandline application actually useful

png-branch
an 2019-02-12 17:32:16 -05:00
parent 3329fde767
commit 5b3c8fcbe1
11 changed files with 211 additions and 59 deletions

View File

@ -18,6 +18,8 @@ failure = "0.1"
generic-array = "0.12" generic-array = "0.12"
#gtk = "0.4" #gtk = "0.4"
memmap = "0.6" memmap = "0.6"
serde = {version = "1.0", features = ["derive"]}
serde_yaml = "0.8"
[profile.dev] [profile.dev]
opt-level = 1 opt-level = 1

View File

@ -1,6 +1,7 @@
//! Binary data conversion utilities. //! Binary data conversion utilities.
use crate::durandal::err::*; use crate::durandal::err::*;
use serde::Serialize;
use std::{fmt, num::NonZeroU16, slice::SliceIndex}; use std::{fmt, num::NonZeroU16, slice::SliceIndex};
/// Returns a byte array from `b` at `i`. /// 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 /// An object identified by a `u16` which may be `u16::max_value()` to
/// represent None. /// represent None.
#[derive(Serialize)]
pub struct ObjID(Option<NonZeroU16>); pub struct ObjID(Option<NonZeroU16>);
// EOF // EOF

16
src/durandal/file.rs Normal file
View File

@ -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

View File

@ -1,6 +1,8 @@
use serde::Serialize;
use std::{fmt::{self, Write}, use std::{fmt::{self, Write},
ops}; ops};
#[derive(Serialize)]
pub struct Fx32(i32); pub struct Fx32(i32);
impl Fx32 impl Fx32

View File

@ -8,6 +8,7 @@ pub mod err;
pub mod bin; pub mod bin;
pub mod chunk; pub mod chunk;
pub mod crc; pub mod crc;
pub mod file;
pub mod fx32; pub mod fx32;
pub mod image; pub mod image;
pub mod text; pub mod text;

View File

@ -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}}; marathon::{machdr, map, pict, shp, term, wad}};
use std::{fs, use std::{collections::HashSet,
fs,
io::{self, Write}}; 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 out = fs::File::create(&fname)?;
let mut out = io::BufWriter::new(out); let mut out = io::BufWriter::new(out);
out.write(cnk)?; out.write(cnk)?;
Ok(()) 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 { match cid {
b"PICT" => { b"PICT" => {
let im = pict::load_pict(cnk)?; if opt.wad_chunks.contains(cid) {
eprintln!("entry {} has PICT {}x{}", eid, im.w(), im.h()); let im = pict::load_pict(cnk)?;
let out = fs::File::create(&format!("out/{}.ppm", eid))?; make_tga(&format!("{}/pict_{}.tga", opt.out_dir, eid), &im)?;
let mut out = io::BufWriter::new(out); }
write_ppm(&mut out, &im)?;
} }
b"Minf" => { b"Minf" => {
let minf = map::Minf::chunk(cnk)?; if opt.wad_chunks.contains(cid) {
eprintln!("entry {} has {:#?}", eid, minf); make_yaml(&map::Minf::chunk(cnk)?)?;
}
} }
b"EPNT" => { b"EPNT" => {
let epnt = map::Endpoint::chunk(cnk)?; if opt.wad_chunks.contains(cid) {
eprintln!("entry {} has EPNT {:#?}", eid, epnt); make_yaml(&map::Endpoint::chunk(cnk)?)?;
}
} }
b"PNTS" => { b"PNTS" => {
let epnt = map::Point::chunk(cnk)?; if opt.wad_chunks.contains(cid) {
eprintln!("entry {} has PNTS {:#?}", eid, epnt); make_yaml(&map::Point::chunk(cnk)?)?;
}
} }
b"LINS" => { b"LINS" => {
let line = map::Line::chunk(cnk)?; if opt.wad_chunks.contains(cid) {
eprintln!("entry {} has LINS {:#?}", eid, line); make_yaml(&map::Line::chunk(cnk)?)?;
}
} }
b"SIDS" => { b"SIDS" => {
let line = map::Side::chunk(cnk)?; if opt.wad_chunks.contains(cid) {
eprintln!("entry {} has SIDS {:#?}", eid, line); make_yaml(&map::Side::chunk(cnk)?)?;
}
} }
b"term" => { b"term" => {
let term = term::Terminal::chunk(cnk)?; if opt.wad_chunks.contains(cid) {
eprintln!("entry {} has term {:#?}", eid, term); make_yaml(&term::Terminal::chunk(cnk)?)?;
}
} }
cid => { cid => {
write_chunk(cid, cnk, eid)?; if opt.wad_unknown {
make_chunk(opt, cid, cnk, eid)?;
}
} }
} }
Ok(()) Ok(())
} }
fn process_wad(b: &[u8]) -> ResultS<()> fn process_wad(opt: &Options, b: &[u8]) -> ResultS<()>
{ {
let wad = wad::Wad::new(b)?; let wad = wad::Wad::new(b)?;
eprintln!("{:#?}", wad); if opt.wad_header {
make_yaml(&wad)?;
}
for (eid, ent) in wad.entries { for (eid, ent) in wad.entries {
for (cid, cnk) in ent.chunks { for (cid, cnk) in ent.chunks {
read_chunk(&cid, cnk, eid)?; dump_chunk(opt, &cid, cnk, eid)?;
} }
} }
Ok(()) 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 (j, bmp) in c.bmps.iter().enumerate() {
for (k, tab) in c.tabs.iter().enumerate() { if opt.shp_bmp_all {
let fname = format!("out/shape{}_{}_{}.tga", i, j, k); for (k, tab) in c.tabs.iter().enumerate() {
let out = fs::File::create(&fname)?; let fname = format!("{}/shape{}_{}_{}.tga", opt.out_dir, i, j, k);
let mut out = io::BufWriter::new(out); make_tga(&fname, &shp::ImageShp::new(bmp, &tab))?;
write_tga(&mut out, &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(()) 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() { for (i, cl) in shp::read_shapes(b)?.iter().enumerate() {
if let Some(cl) = &cl.0 { if let Some(cl) = &cl.0 {
dump_bitmaps(cl, i)?; dump_bitmaps(opt, cl, i)?;
eprintln!("<{} lo> {:#?}\n{:#?}", i, cl.frms, cl.seqs); write_shp_objs(opt, cl)?;
} }
if let Some(cl) = &cl.1 { if let Some(cl) = &cl.1 {
dump_bitmaps(cl, i + 100)?; dump_bitmaps(opt, cl, i + 100)?;
eprintln!("<{} hi> {:#?}\n{:#?}", i, cl.frms, cl.seqs); write_shp_objs(opt, cl)?;
} }
} }
@ -104,7 +155,7 @@ fn main() -> ResultS<()>
use argparse::*; use argparse::*;
use memmap::Mmap; use memmap::Mmap;
let mut args: Vec<String> = Vec::new(); let mut opt: Options = Default::default();
{ {
let mut ap = ArgumentParser::new(); let mut ap = ArgumentParser::new();
ap.set_description(env!("CARGO_PKG_DESCRIPTION")); ap.set_description(env!("CARGO_PKG_DESCRIPTION"));
@ -113,12 +164,58 @@ fn main() -> ResultS<()>
env!("CARGO_PKG_NAME"), env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"))), env!("CARGO_PKG_VERSION"))),
"Show the version"); "Show the version");
ap.refer(&mut args) ap.refer(&mut opt.shp_tab)
.add_argument("inputs", List, "Input files"); .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(); 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(':') { let (typ, fna) = if let Some(st) = arg.find(':') {
arg.split_at(st + 1) arg.split_at(st + 1)
} else { } else {
@ -130,8 +227,8 @@ fn main() -> ResultS<()>
let b = c_data(&mm, machdr::try_mac_header(&mm)..)?; let b = c_data(&mm, machdr::try_mac_header(&mm)..)?;
match typ { match typ {
"wad:" => process_wad(b), "wad:" => process_wad(&opt, b),
"shp:" => process_shp(b), "shp:" => process_shp(&opt, b),
_ => Err(err_msg("invalid file type specified on commandline")), _ => Err(err_msg("invalid file type specified on commandline")),
}?; }?;
} }
@ -139,4 +236,21 @@ fn main() -> ResultS<()>
Ok(()) Ok(())
} }
#[derive(Default)]
struct Options
{
inputs: Vec<String>,
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<Ident>,
wad_c_temp: String,
}
// EOF // EOF

View File

@ -2,6 +2,7 @@ use crate::{durandal::{bin::*, chunk::*, err::*, fx32::*,
text::mac_roman_conv}, text::mac_roman_conv},
marathon::xfer::TransferMode}; marathon::xfer::TransferMode};
use bitflags::bitflags; use bitflags::bitflags;
use serde::Serialize;
use std::fmt; use std::fmt;
impl Chunked<Point> for Point impl Chunked<Point> for Point
@ -118,13 +119,14 @@ impl Chunker<Minf> for Minf
type Unit = i16; type Unit = i16;
#[derive(Serialize)]
pub struct Point pub struct Point
{ {
x: Unit, x: Unit,
y: Unit, y: Unit,
} }
#[derive(Debug)] #[derive(Debug, Serialize)]
pub struct Endpoint pub struct Endpoint
{ {
flags: EndpFlags, flags: EndpFlags,
@ -134,7 +136,7 @@ pub struct Endpoint
support: u16, support: u16,
} }
#[derive(Debug)] #[derive(Debug, Serialize)]
pub struct Line pub struct Line
{ {
flags: LineFlags, flags: LineFlags,
@ -149,14 +151,14 @@ pub struct Line
poly_b: u16, poly_b: u16,
} }
#[derive(Debug)] #[derive(Debug, Serialize)]
pub struct SideTex pub struct SideTex
{ {
offs: Point, offs: Point,
tex_id: ObjID, tex_id: ObjID,
} }
#[derive(Debug)] #[derive(Debug, Serialize)]
pub struct Side pub struct Side
{ {
stype: u16, stype: u16,
@ -176,7 +178,7 @@ pub struct Side
shade: Fx32, shade: Fx32,
} }
#[derive(Debug)] #[derive(Debug, Serialize)]
pub struct Minf pub struct Minf
{ {
env_code: u16, env_code: u16,
@ -189,6 +191,7 @@ pub struct Minf
} }
bitflags! { bitflags! {
#[derive(Serialize)]
pub struct EndpFlags: u16 pub struct EndpFlags: u16
{ {
const Solid = 0x00_01; const Solid = 0x00_01;
@ -198,6 +201,7 @@ bitflags! {
} }
bitflags! { bitflags! {
#[derive(Serialize)]
pub struct LineFlags: u16 pub struct LineFlags: u16
{ {
const TransSide = 0x02_00; const TransSide = 0x02_00;
@ -210,6 +214,7 @@ bitflags! {
} }
bitflags! { bitflags! {
#[derive(Serialize)]
pub struct SideFlags: u16 pub struct SideFlags: u16
{ {
const Status = 0x00_01; const Status = 0x00_01;
@ -224,6 +229,7 @@ bitflags! {
} }
bitflags! { bitflags! {
#[derive(Serialize)]
pub struct EnvFlags: u16 pub struct EnvFlags: u16
{ {
const Vacuum = 0x00_01; const Vacuum = 0x00_01;
@ -243,6 +249,7 @@ bitflags! {
} }
bitflags! { bitflags! {
#[derive(Serialize)]
pub struct EntFlags: u32 pub struct EntFlags: u32
{ {
const Solo = 0x00_01; const Solo = 0x00_01;
@ -257,6 +264,7 @@ bitflags! {
} }
bitflags! { bitflags! {
#[derive(Serialize)]
pub struct MsnFlags: u16 pub struct MsnFlags: u16
{ {
const Extermination = 0x00_01; const Extermination = 0x00_01;

View File

@ -3,6 +3,7 @@
use crate::{durandal::{bin::*, err::*, fx32::*, image::*, text::*}, use crate::{durandal::{bin::*, err::*, fx32::*, image::*, text::*},
marathon::xfer::TransferMode}; marathon::xfer::TransferMode};
use bitflags::bitflags; use bitflags::bitflags;
use serde::Serialize;
fn color(b: &[u8]) -> ResultS<(usize, ColorShp)> 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 pub enum ColorShp
{ {
Translucent, Translucent,
@ -342,7 +343,7 @@ pub struct ImageShp<'a, 'b>
clut: &'b [ColorShp], clut: &'b [ColorShp],
} }
#[derive(Debug)] #[derive(Debug, Serialize)]
pub struct Frame pub struct Frame
{ {
flags: FrameFlags, flags: FrameFlags,
@ -356,7 +357,7 @@ pub struct Frame
wrl_y: i16, wrl_y: i16,
} }
#[derive(Debug)] #[derive(Debug, Serialize)]
pub struct Sequence pub struct Sequence
{ {
name: String, name: String,
@ -393,6 +394,7 @@ bitflags! {
} }
bitflags! { bitflags! {
#[derive(Serialize)]
pub struct FrameFlags: u16 pub struct FrameFlags: u16
{ {
const Obscure = 0x20_00; const Obscure = 0x20_00;
@ -402,7 +404,7 @@ bitflags! {
} }
c_enum! { c_enum! {
#[derive(Debug)] #[derive(Debug, Serialize)]
pub enum CollectionType: u16 pub enum CollectionType: u16
{ {
0 => Unused, 0 => Unused,
@ -414,7 +416,7 @@ c_enum! {
} }
c_enum! { c_enum! {
#[derive(Debug)] #[derive(Debug, Serialize)]
pub enum ViewType: u16 pub enum ViewType: u16
{ {
1 => Anim, 1 => Anim,

View File

@ -1,4 +1,5 @@
use crate::durandal::{bin::*, chunk::*, err::*, text::*}; use crate::durandal::{bin::*, chunk::*, err::*, text::*};
use serde::Serialize;
use std::fmt; use std::fmt;
fn read_group(b: &[u8], text: &[u8]) -> ResultS<Group> fn read_group(b: &[u8], text: &[u8]) -> ResultS<Group>
@ -78,7 +79,7 @@ impl Chunker<Vec<Terminal>> for Terminal
} }
} }
#[derive(Debug)] #[derive(Debug, Serialize)]
pub struct Terminal pub struct Terminal
{ {
lines: u16, lines: u16,
@ -86,7 +87,7 @@ pub struct Terminal
faces: Vec<Face>, faces: Vec<Face>,
} }
#[derive(Debug)] #[derive(Debug, Serialize)]
pub struct Face pub struct Face
{ {
start: usize, start: usize,
@ -94,6 +95,7 @@ pub struct Face
color: u16, color: u16,
} }
#[derive(Serialize)]
pub struct Group pub struct Group
{ {
ttype: GroupType, ttype: GroupType,
@ -103,7 +105,7 @@ pub struct Group
} }
c_enum! { c_enum! {
#[derive(Debug)] #[derive(Debug, Serialize)]
pub enum GroupType: u16 pub enum GroupType: u16
{ {
0 => Logon, 0 => Logon,

View File

@ -1,6 +1,7 @@
//! Marathon Wad format handling. //! Marathon Wad format handling.
use crate::durandal::{bin::*, err::*, text::mac_roman_conv}; use crate::durandal::{bin::*, err::*, text::mac_roman_conv};
use serde::Serialize;
use std::{collections::BTreeMap, fmt}; use std::{collections::BTreeMap, fmt};
impl Wad<'_> impl Wad<'_>
@ -85,18 +86,19 @@ pub struct Entry<'a>
pub appdata: &'a [u8], pub appdata: &'a [u8],
} }
#[derive(Debug)] #[derive(Debug, Serialize)]
pub struct Wad<'a> pub struct Wad<'a>
{ {
wadver: Ver, wadver: Ver,
dataver: u16, dataver: u16,
origname: String, origname: String,
appsize: usize, appsize: usize,
#[serde(skip)]
pub entries: EntryMap<'a>, pub entries: EntryMap<'a>,
} }
c_enum! { c_enum! {
#[derive(Debug)] #[derive(Debug, Serialize)]
pub enum Ver: u16 pub enum Ver: u16
{ {
0 => Base, 0 => Base,

View File

@ -1,7 +1,8 @@
use crate::durandal::err::*; use crate::durandal::err::*;
use serde::Serialize;
c_enum! { c_enum! {
#[derive(Debug)] #[derive(Debug, Serialize)]
pub enum TransferMode: u16 pub enum TransferMode: u16
{ {
0 => Normal, 0 => Normal,