diff --git a/source/durandal/cenum.rs b/source/durandal/cenum.rs index 8c0ffb2..ddbe60f 100644 --- a/source/durandal/cenum.rs +++ b/source/durandal/cenum.rs @@ -13,6 +13,7 @@ /// /// ``` /// use maraiah::{c_enum, durandal::err::ReprError}; +/// use std::convert::TryFrom; /// /// c_enum! { /// #[derive(Debug)] @@ -52,14 +53,14 @@ macro_rules! c_enum #[allow(unused_qualifications)] impl std::convert::TryFrom<$ti> for $t { - type Error = ReprError; + type Error = $crate::durandal::err::ReprError; /// Returns, if representable, the variant of `Self` from `n`. fn try_from(n: $ti) -> Result { match n { $($va => Ok($t::$en),)+ - n => Err(ReprError::new(n)) + n => Err(Self::Error::new(n)) } } } @@ -70,6 +71,7 @@ macro_rules! c_enum mod test { use crate::durandal::err::ReprError; + use std::convert::TryFrom; c_enum! { #[derive(Debug)] diff --git a/source/leela/main.rs b/source/leela/main.rs index f41cd52..cf7831f 100644 --- a/source/leela/main.rs +++ b/source/leela/main.rs @@ -1,5 +1,5 @@ use maraiah::{durandal::{err::*, file::*, image::*, sound::*}, - marathon::{machdr, ppm, shp, snd, tga, wad, wav}}; + marathon::{machdr, map, ppm, shp, snd, tga, wav}}; use std::{fs, io, slice::from_ref}; fn make_tga(_opt: &Options, fname: &str, im: &impl Image) -> ResultS<()> @@ -37,7 +37,7 @@ fn make_wav(fname: &str, snd: &impl Sound) -> ResultS<()> fn process_wad(opt: &Options, b: &[u8]) -> ResultS<()> { - let wad = wad::read_wad(b)?; + let wad = map::read(b)?; if opt.wad_wrt_all { make_yaml(opt, &wad)?; @@ -46,7 +46,8 @@ fn process_wad(opt: &Options, b: &[u8]) -> ResultS<()> Ok(()) } -fn dump_bitmaps(opt: &Options, c: &shp::Collection, i: usize) -> ResultS<()> +fn dump_bitmaps(opt: &Options, c: &shp::coll::Collection, i: usize) + -> ResultS<()> { if !opt.shp_bmp { return Ok(()); @@ -56,18 +57,18 @@ fn dump_bitmaps(opt: &Options, c: &shp::Collection, i: usize) -> ResultS<()> 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(opt, &fname, &shp::ImageShp::new(bmp, &tab))?; + make_tga(opt, &fname, &shp::bmap::ImageShp::new(bmp, &tab))?; } } else { let fname = format!("{}/shape{}_{}.tga", opt.out_dir, i, j); - make_tga(opt, &fname, &shp::ImageShp::new(bmp, &c.tabs[0]))?; + make_tga(opt, &fname, &shp::bmap::ImageShp::new(bmp, &c.tabs[0]))?; } } Ok(()) } -fn write_shp_objs(opt: &Options, cl: &shp::Collection) -> ResultS<()> +fn write_shp_objs(opt: &Options, cl: &shp::coll::Collection) -> ResultS<()> { if opt.shp_tab {make_yaml(opt, &cl.tabs)?;} if opt.shp_frm {make_yaml(opt, &cl.frms)?;} @@ -78,7 +79,7 @@ fn write_shp_objs(opt: &Options, cl: &shp::Collection) -> ResultS<()> fn process_shp(opt: &Options, b: &[u8]) -> ResultS<()> { - for (i, cl) in shp::read_shapes(b)?.iter().enumerate() { + for (i, cl) in shp::read(b)?.iter().enumerate() { if let Some(cl) = &cl.0 { dump_bitmaps(opt, cl, i)?; write_shp_objs(opt, cl)?; @@ -111,7 +112,7 @@ fn dump_sounds(opt: &Options, st: &snd::SoundTable, c: usize) -> ResultS<()> fn process_snd(opt: &Options, b: &[u8]) -> ResultS<()> { - for (c, st) in snd::read_sounds(b)?.iter().enumerate() { + for (c, st) in snd::read(b)?.iter().enumerate() { dump_sounds(opt, st, c)?; } diff --git a/source/marathon.rs b/source/marathon.rs index a11cddc..58ad930 100644 --- a/source/marathon.rs +++ b/source/marathon.rs @@ -3,14 +3,12 @@ pub mod defl; pub mod machdr; pub mod map; -pub mod phy; pub mod pict; pub mod ppm; pub mod shp; pub mod snd; pub mod text; pub mod tga; -pub mod wad; pub mod wav; pub mod xfer; diff --git a/source/marathon/map.rs b/source/marathon/map.rs index 6514101..0e9c6dc 100644 --- a/source/marathon/map.rs +++ b/source/marathon/map.rs @@ -1,25 +1,101 @@ -//! Structures used by Marathon's Map format. +//! Marathon Map format handling. pub mod ambi; +pub mod attk; pub mod bonk; +pub mod chnk; +pub mod damg; +pub mod entr; pub mod epnt; +pub mod fxpx; pub mod iidx; pub mod lins; pub mod lite; pub mod ltfn; pub mod medi; pub mod minf; +pub mod mnpx; pub mod note; pub mod objs; pub mod plac; pub mod plat; pub mod pnts; pub mod poly; +pub mod prpx; +pub mod pxpx; pub mod sids; pub mod stex; pub mod term; +pub mod trig; pub mod trmf; pub mod trmg; +pub mod wppx; + +use crate::{durandal::err::*, marathon::text::mac_roman_cstr}; +use std::convert::TryFrom; + +/// Reads a Map file. +pub fn read(b: &[u8]) -> ResultS +{ + read_data! { + endian: BIG, buf: b, size: 128, start: 0, data { + let ver_wad = u16[0] enum Ver; + let ver_dat = u16[2]; + let name = mac_roman_cstr[4; 64] no_try; + let siz_app = u16[78] usize; + let siz_wcnk = u16[80] usize; + let siz_went = u16[82] usize; + } + } + + let old_dat = ver_dat == 0; + let old_wad = match ver_wad { + Ver::Base => true, + _ => false, + }; + + let siz_ent = if old_wad {8 } else {10}; + let siz_cnk = if old_wad {12} else {16}; + + if !old_wad && siz_ent != siz_went { + bail!("invalid entry size"); + } + + if !old_wad && siz_cnk != siz_wcnk { + bail!("invalid chunk size"); + } + + let entries = entr::read(b, old_wad, old_dat, siz_app, siz_ent, siz_cnk)?; + + Ok(Wad{name, siz_app, entries}) +} + +/// A Map file containing entries. +#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] +#[derive(Debug, Eq, PartialEq)] +pub struct Wad +{ + /// The original name of this file. + pub name: String, + + /// The size of each `Entry`'s `appdata` field. + pub siz_app: usize, + + /// All of the entries in this `Wad`. + pub entries: entr::EntryMap, +} + +c_enum! { + /// The version of a `Wad`. + #[derive(Debug)] + enum Ver: u16 + { + Base = 0, + Dir = 1, + Over = 2, + Inf = 4, + } +} /// The number of game ticks per second. pub const TICKS_PER_SECOND: u16 = 30; diff --git a/source/marathon/phy/attk.rs b/source/marathon/map/attk.rs similarity index 100% rename from source/marathon/phy/attk.rs rename to source/marathon/map/attk.rs diff --git a/source/marathon/map/chnk.rs b/source/marathon/map/chnk.rs new file mode 100644 index 0000000..cd0df9c --- /dev/null +++ b/source/marathon/map/chnk.rs @@ -0,0 +1,93 @@ +//! Wad file chunk type. + +use crate::{durandal::{bin::*, err::*, image}, marathon::{map, pict}}; + +/// Reads all chunks in an entry. +pub fn read(b: &[u8], old: bool, siz_cnk: usize) -> ResultS> +{ + let mut chunks = Vec::new(); + let mut p = 0; + + let read_chunk_minf = if old {map::minf::read_old} else {map::minf::read}; + let read_chunk_sids = if old {map::sids::read_old} else {map::sids::read}; + let read_chunk_poly = if old {map::poly::read_old} else {map::poly::read}; + let read_chunk_lite = if old {map::lite::read_old} else {map::lite::read}; + + while p < b.len() { + read_data! { + endian: BIG, buf: b, size: siz_cnk, start: p, data { + let iden = Ident[0]; + let size = u32[8] usize; + } + } + + let beg = p + siz_cnk; + let end = beg + size; + let data = ok!(b.get(beg..end), "not enough data")?; + + chunks.push(match &iden.0 { + b"PICT" => Chunk::Pict(pict::load_pict(data)?), + b"Minf" => Chunk::Minf(read_chunk_minf(data)?), + b"iidx" => Chunk::Iidx(rd_array(data, map::iidx::read)?), + b"EPNT" => Chunk::Epnt(rd_array(data, map::epnt::read)?), + b"PNTS" => Chunk::Pnts(rd_array(data, map::pnts::read)?), + b"LINS" => Chunk::Lins(rd_array(data, map::lins::read)?), + b"SIDS" => Chunk::Sids(rd_array(data, read_chunk_sids)?), + b"POLY" => Chunk::Poly(rd_array(data, read_chunk_poly)?), + b"OBJS" => Chunk::Objs(rd_array(data, map::objs::read)?), + b"LITE" => Chunk::Lite(rd_array(data, read_chunk_lite)?), + b"plac" => Chunk::Plac(rd_array(data, map::plac::read)?), + b"ambi" => Chunk::Ambi(rd_array(data, map::ambi::read)?), + b"bonk" => Chunk::Bonk(rd_array(data, map::bonk::read)?), + b"medi" => Chunk::Medi(rd_array(data, map::medi::read)?), + b"plat" => Chunk::Plat(rd_array(data, map::plat::read)?), + b"NOTE" => Chunk::Note(rd_array(data, map::note::read)?), + b"term" => Chunk::Term(rd_array(data, map::term::read)?), + b"FXpx" => Chunk::Fxpx(rd_array(data, map::fxpx::read)?), + b"MNpx" => Chunk::Mnpx(rd_array(data, map::mnpx::read)?), + b"PRpx" => Chunk::Prpx(rd_array(data, map::prpx::read)?), + b"PXpx" => Chunk::Pxpx(rd_array(data, map::pxpx::read)?), + b"WPpx" => Chunk::Wppx(rd_array(data, map::wppx::read)?), + _ => Chunk::Data{iden, data: data.to_vec()}, + }); + + p = end; + } + + Ok(chunks) +} + +/// Any kind of chunk in an `Entry`. +#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] +#[derive(Debug, Eq, PartialEq)] +pub enum Chunk +{ + /** A `PICT` chunk. */ Pict(image::Image8), + /** A `Minf` chunk. */ Minf(map::minf::Minf), + /** An `iidx` chunk. */ Iidx(Vec), + /** An `EPNT` chunk. */ Epnt(Vec), + /** A `PNTS` chunk. */ Pnts(Vec), + /** A `LINS` chunk. */ Lins(Vec), + /** A `SIDS` chunk. */ Sids(Vec), + /** A `POLY` chunk. */ Poly(Vec), + /** A `LITE` chunk. */ Lite(Vec), + /** An `OBJS` chunk. */ Objs(Vec), + /** A `plac` chunk. */ Plac(Vec), + /** An `ambi` chunk. */ Ambi(Vec), + /** A `bonk` chunk. */ Bonk(Vec), + /** A `medi` chunk. */ Medi(Vec), + /** A `plat` chunk. */ Plat(Vec), + /** A `NOTE` chunk. */ Note(Vec), + /** A `term` chunk. */ Term(Vec), + /** A `FXpx` chunk. */ Fxpx(Vec), + /** A `MNpx` chunk. */ Mnpx(Vec), + /** A `PRpx` chunk. */ Prpx(Vec), + /** A `PXpx` chunk. */ Pxpx(Vec), + /** A `WPpx` chunk. */ Wppx(Vec), + + /// Any other type of chunk, which may have arbitrary data in it. + Data{/** The name of the chunk. */ iden: Ident, + /** The data. */ data: Vec}, +} + +// EOF diff --git a/source/marathon/phy/damg.rs b/source/marathon/map/damg.rs similarity index 100% rename from source/marathon/phy/damg.rs rename to source/marathon/map/damg.rs diff --git a/source/marathon/map/entr.rs b/source/marathon/map/entr.rs new file mode 100644 index 0000000..6f34db1 --- /dev/null +++ b/source/marathon/map/entr.rs @@ -0,0 +1,63 @@ +//! Wad file entry type. + +use crate::durandal::{bin::usize_from_u32, err::*}; +use super::chnk; +use std::collections::BTreeMap; + +/// Reads all entries in a `Wad`. +pub fn read(b: &[u8], + old_wad: bool, + old_dat: bool, + siz_app: usize, + siz_ent: usize, + siz_cnk: usize) + -> ResultS +{ + read_data! { + endian: BIG, buf: b, size: 128, start: 0, data { + let dirofs = u32[72] usize; + let numents = u16[76] usize; + } + } + + let mut entries = EntryMap::new(); + let mut p = dirofs; + + for i in 0..numents { + read_data! { + endian: BIG, buf: b, size: siz_ent, start: p, data { + let offset = u32[0] usize; + let size = u32[4] usize; + let index = u16[8]; + } + } + + let index = if old_wad {i as u16} else {index}; + + let chunks = chnk::read(&b[offset..offset + size], old_dat, siz_cnk)?; + let appdata = b[p..p + siz_app].to_vec(); + + entries.insert(index, Entry{chunks, appdata}); + + p += siz_ent + siz_app; + } + + Ok(entries) +} + +/// An entry containing chunks and application-specific data. +#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] +#[derive(Debug, Eq, PartialEq)] +pub struct Entry +{ + /// All of the chunks in this `Entry`. + pub chunks: Vec, + + /// The application specific data for this Entry. + pub appdata: Vec, +} + +/// A map of indexed entries. +pub type EntryMap = BTreeMap; + +// EOF diff --git a/source/marathon/phy/fxpx.rs b/source/marathon/map/fxpx.rs similarity index 100% rename from source/marathon/phy/fxpx.rs rename to source/marathon/map/fxpx.rs diff --git a/source/marathon/phy/mnpx.rs b/source/marathon/map/mnpx.rs similarity index 100% rename from source/marathon/phy/mnpx.rs rename to source/marathon/map/mnpx.rs diff --git a/source/marathon/phy/prpx.rs b/source/marathon/map/prpx.rs similarity index 100% rename from source/marathon/phy/prpx.rs rename to source/marathon/map/prpx.rs diff --git a/source/marathon/phy/pxpx.rs b/source/marathon/map/pxpx.rs similarity index 100% rename from source/marathon/phy/pxpx.rs rename to source/marathon/map/pxpx.rs diff --git a/source/marathon/phy/trig.rs b/source/marathon/map/trig.rs similarity index 100% rename from source/marathon/phy/trig.rs rename to source/marathon/map/trig.rs diff --git a/source/marathon/phy/wppx.rs b/source/marathon/map/wppx.rs similarity index 100% rename from source/marathon/phy/wppx.rs rename to source/marathon/map/wppx.rs diff --git a/source/marathon/phy.rs b/source/marathon/phy.rs deleted file mode 100644 index 33d71e2..0000000 --- a/source/marathon/phy.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Structures used by Marathon's Physics format. - -pub mod attk; -pub mod damg; -pub mod fxpx; -pub mod mnpx; -pub mod prpx; -pub mod pxpx; -pub mod trig; -pub mod wppx; - -// EOF diff --git a/source/marathon/shp.rs b/source/marathon/shp.rs index d7e17bc..c8ebc3d 100644 --- a/source/marathon/shp.rs +++ b/source/marathon/shp.rs @@ -1,203 +1,28 @@ //! Marathon Shapes format handling. -use crate::{durandal::{bin::*, err::*, fixed::*, image::*}, - marathon::{text::*, xfer::TransferMode}}; -use bitflags::bitflags; -use std::convert::TryFrom; +pub mod bmap; +pub mod clut; +pub mod coll; +pub mod fram; +pub mod sequ; -// Reads a color from a color table into `clut`. -fn read_color(b: &[u8], clut: &mut [ColorShp]) -> ResultS<()> +use crate::durandal::{bin::usize_from_u32, err::*}; + +/// Reads a collection at an offset provided by the Shapes header. +pub fn read_coll_at_offset(b: &[u8], ofs: u32, len: usize) + -> ResultS> { - read_data! { - endian: BIG, buf: b, size: 8, start: 0, data { - let flag = u8[0]; - let ind = u8[1]; - let r = u16[2]; - let g = u16[4]; - let b = u16[6]; - } - } - - let cr = ok!(clut.get_mut(usize::from(ind)), "bad index")?; - - *cr = match flag { - 128 => ColorShp::Lit {r, g, b}, - 0 => ColorShp::Opaque{r, g, b}, - _ => { - return Err(err_msg("invalid flag in color")); - } - }; - - Ok(()) -} - -// Reads all color tables. -fn color_tables(b: &[u8], - tab_ofs: usize, - tab_num: usize, - clr_num: usize) - -> ResultS>> -{ - let end = tab_num * clr_num * 8; - - let b = ok!(b.get(tab_ofs..tab_ofs + end), "bad offset")?; - - let mut v = vec![vec![ColorShp::Translucent; clr_num]; tab_num]; - let mut p = 0; - - for clut in v.iter_mut().take(tab_num) { - for _ in 0..clr_num { - read_color(ok!(b.get(p..p + 8), "not enough data")?, clut)?; - p += 8; - } - } - - Ok(v) -} - -/// Reads a `Bitmap`. -pub fn read_bitmap(b: &[u8]) -> ResultS -{ - read_data! { - endian: BIG, buf: b, size: 26, start: 0, data { - let width = u16[0] usize; - let height = u16[2] usize; - let compr = u16[4]; - let flags = u16[6] flag BmpFlags; - let depth = u16[8]; - } - } - - let compr = compr == u16::max_value(); - let alpha = flags.contains(BmpFlags::TRANSPARENT); - let cmajr = flags.contains(BmpFlags::COLUMN_MAJOR); - - if depth != 8 { - bail!("invalid bit depth (should always be 8)"); - } - - let mut bmp = Bitmap::new(width, height, alpha, cmajr); - let mut p = 30 + if cmajr {4 * width} else {4 * height}; - - let scanlines = if cmajr {width} else {height}; - let pitch = if cmajr {height} else {width}; - - if compr { - // compressed scanlines (transparency RLE) - for _ in 0..scanlines { - read_data! { - endian: BIG, buf: b, size: 4, start: p, data { - let fst = u16[0] usize; - let lst = u16[2] usize; - } - } - - let end = lst - fst; - - p += 4; - - if lst < fst || fst > pitch || lst > pitch { - bail!("invalid compressed scanline"); - } - - for _ in 0..fst { - bmp.cr.push(0); - } - - bmp.cr.extend(ok!(b.get(p..p + end), "not enough data")?); - - for _ in lst..pitch { - bmp.cr.push(0); - } - - p += end; - } + if ofs != u32::max_value() { + let ofs = usize_from_u32(ofs); + let dat = ok!(b.get(ofs..ofs + len), "bad offset")?; + Ok(Some(coll::read(dat)?)) } else { - // simple copy - bmp.cr.extend(ok!(b.get(p..p + width * height), "not enough data")?); + Ok(None) } - - Ok(bmp) -} - -/// Reads a `Frame`. -pub fn read_frame(b: &[u8]) -> ResultS -{ - read_data! { - endian: BIG, buf: b, size: 36, start: 0, data { - let flags = u16[0] flag FrameFlags; - let min_lt = Fixed[2]; - let bmp_ind = u16[6] usize; - let wrl_l = Unit[16]; - let wrl_r = Unit[18]; - let wrl_t = Unit[20]; - let wrl_b = Unit[22]; - let wrl_x = Unit[24]; - let wrl_y = Unit[26]; - } - } - - Ok(Frame{flags, min_lt, bmp_ind, wrl_l, wrl_r, wrl_t, wrl_b, wrl_x, wrl_y}) -} - -/// Reads a `Sequence`. -pub fn read_sequence(b: &[u8]) -> ResultS -{ - read_data! { - endian: BIG, buf: b, size: 88, start: 0, data { - let name = u8[4; 34]; - let v_type = u16[38] enum ViewType; - let frames = u16[40]; - let ticks = u16[42]; - let key = u16[44]; - let xfer = u16[46] enum TransferMode; - let xfer_pd = u16[48]; - let snd_beg = OptU16[50]; - let snd_key = OptU16[52]; - let snd_end = OptU16[54]; - let loop_f = u16[58]; - } - } - - let name = mac_roman_conv(ok!(pascal_str(name), "bad string")?); - - Ok(Sequence{name, v_type, frames, ticks, key, xfer, xfer_pd, snd_beg, - snd_key, snd_end, loop_f}) -} - -/// Reads a `Collection`. -pub fn read_collection(b: &[u8]) -> ResultS -{ - read_data! { - endian: BIG, buf: b, size: 544, start: 0, data { - let version = u16[0]; - let cl_type = u16[2] enum CollectionType; - let clr_num = u16[6] usize; - let tab_num = u16[8] usize; - let tab_ofs = u32[10] usize; - let seq_num = u16[14] usize; - let seq_ofs = u32[16] usize; - let frm_num = u16[20] usize; - let frm_ofs = u32[22] usize; - let bmp_num = u16[26] usize; - let bmp_ofs = u32[28] usize; - } - } - - if version != 3 { - bail!("invalid collection definition"); - } - - let tabs = color_tables(b, tab_ofs, tab_num, clr_num)?; - let bmps = rd_ofstable(b, bmp_ofs, bmp_num, read_bitmap)?; - let frms = rd_ofstable(b, frm_ofs, frm_num, read_frame)?; - let seqs = rd_ofstable(b, seq_ofs, seq_num, read_sequence)?; - - Ok(Collection{ctyp: cl_type, tabs, bmps, frms, seqs}) } /// Read all of the collections in a Shapes file. -pub fn read_shapes(b: &[u8]) -> ResultS> +pub fn read(b: &[u8]) -> ResultS> { let mut cl = Vec::with_capacity(32); let mut p = 0; @@ -205,28 +30,17 @@ pub fn read_shapes(b: &[u8]) -> ResultS> for _ in 0..32 { read_data! { endian: BIG, buf: b, size: 32, start: p, data { - let lo_ofs = u32[4] usize; + let lo_ofs = u32[4]; let lo_len = u32[8] usize; - let hi_ofs = u32[12] usize; + let hi_ofs = u32[12]; let hi_len = u32[16] usize; } } - let c_lo = if lo_ofs == usize_from_u32(u32::max_value()) { - None - } else { - let dat = ok!(b.get(lo_ofs..lo_ofs + lo_len), "bad offset")?; - Some(read_collection(dat)?) - }; + let lo = read_coll_at_offset(b, lo_ofs, lo_len)?; + let hi = read_coll_at_offset(b, hi_ofs, hi_len)?; - let c_hi = if hi_ofs == usize_from_u32(u32::max_value()) { - None - } else { - let dat = ok!(b.get(hi_ofs..hi_ofs + hi_len), "bad offset")?; - Some(read_collection(dat)?) - }; - - cl.push((c_lo, c_hi)); + cl.push((lo, hi)); p += 32; } @@ -234,274 +48,7 @@ pub fn read_shapes(b: &[u8]) -> ResultS> Ok(cl) } -impl Bitmap -{ - /// Creates an empty bitmap. - pub fn new(w: usize, h: usize, alpha: bool, cmajr: bool) -> Self - { - Self{w, h, alpha, cmajr, cr: Vec::with_capacity(w * h)} - } -} - -impl<'a, 'b> ImageShp<'a, 'b> -{ - /// Creates an `ImageShp` with the given bitmap. - pub fn new(bmp: &'a Bitmap, clut: &'b [ColorShp]) -> Self - { - Self{bmp, clut} - } -} - -impl Image for ImageShp<'_, '_> -{ - type Output = ColorShp; - - fn w(&self) -> usize {self.bmp.w} - fn h(&self) -> usize {self.bmp.h} - - fn index(&self, x: usize, y: usize) -> &Self::Output - { - static TRANSLUCENT_COLOR: ColorShp = ColorShp::Translucent; - - let cr = usize::from(if self.bmp.cmajr { - self.bmp.cr[y + x * self.bmp.h] - } else { - self.bmp.cr[x + y * self.bmp.w] - }); - - if self.bmp.alpha && cr == 0 { - &TRANSLUCENT_COLOR - } else { - self.clut.get(cr).unwrap_or(&TRANSLUCENT_COLOR) - } - } -} - -impl Color for ColorShp -{ - fn r(&self) -> u16 - { - match *self { - ColorShp::Translucent => 0, - ColorShp::Opaque{r, ..} | - ColorShp::Lit {r, ..} => r, - } - } - - fn g(&self) -> u16 - { - match *self { - ColorShp::Translucent => 0, - ColorShp::Opaque{g, ..} | - ColorShp::Lit {g, ..} => g, - } - } - - fn b(&self) -> u16 - { - match *self { - ColorShp::Translucent => 0, - ColorShp::Opaque{b, ..} | - ColorShp::Lit {b, ..} => b, - } - } - - fn a(&self) -> u16 - { - match *self { - ColorShp::Translucent => 0, - ColorShp::Opaque{..} | - ColorShp::Lit {..} => u16::max_value(), - } - } -} - -/// A color in an `ImageShp`'s color table. -#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum ColorShp -{ - /// A completely translucent color. - Translucent, - - /// An opaque color which may be shaded. - Opaque{/** The red component. */ r: u16, - /** The green component. */ g: u16, - /** The blue component. */ b: u16}, - - /// An opaque color which may not be shaded. - Lit{/** The red component. */ r: u16, - /** The green component. */ g: u16, - /** The blue component. */ b: u16}, -} - -/// An unpacked Shape bitmap. -#[derive(Debug, Eq, PartialEq)] -pub struct Bitmap -{ - w: usize, - h: usize, - cr: Vec, - alpha: bool, - cmajr: bool, -} - -/// An image from a Shape. This mainly just exists so that `Bitmap` can use the -/// `Image` trait. -#[derive(Debug, Eq, PartialEq)] -pub struct ImageShp<'a, 'b> -{ - bmp: &'a Bitmap, - clut: &'b [ColorShp], -} - -/// A frame, also known as a low level shape. -#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] -#[derive(Debug, Eq, PartialEq)] -pub struct Frame -{ - /// The flags for this frame. - pub flags: FrameFlags, - - /// The minimum light level for this frame. - pub min_lt: Fixed, - - /// The index of the bitmap this frame uses. - pub bmp_ind: usize, - - /// The left translation for this frame. - pub wrl_l: Unit, - - /// The right translation for this frame. - pub wrl_r: Unit, - - /// The top translation for this frame. - pub wrl_t: Unit, - - /// The bottom translation for this frame. - pub wrl_b: Unit, - - /// The X translation for this frame. - pub wrl_x: Unit, - - /// The Y translation for this frame. - pub wrl_y: Unit, -} - -/// A sequence, also known as a high level shape. -#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] -#[derive(Debug, Eq, PartialEq)] -pub struct Sequence -{ - /// The display name for this sequence. - pub name: String, - - /// The view type for each frame in this sequence. - pub v_type: ViewType, - - /// The number of frames in this sequence. - pub frames: u16, - - /// The number of ticks each frame in this sequence takes. - pub ticks: u16, - - /// The key frame index for this sequence. - pub key: u16, - - /// The transfer mode to play over this sequence. - pub xfer: TransferMode, - - /// The period in game ticks the transfer mode plays over. - pub xfer_pd: u16, - - /// The sound to play at the beginning of this sequence. - pub snd_beg: OptU16, - - /// The sound to play at the key frame of this sequence. - pub snd_key: OptU16, - - /// The sound to play at the end of this sequence. - pub snd_end: OptU16, - - /// Which frame to loop on. - pub loop_f: u16, -} - -/// A collection of color tables, bitmaps, frames and sequences. -#[derive(Debug, Eq, PartialEq)] -pub struct Collection -{ - /// The type of collection this is. - pub ctyp: CollectionType, - - /// All of the color tables in this collection. - pub tabs: Vec>, - - /// All of the bitmaps in this collection. - pub bmps: Vec, - - /// All of the frames in this collection. - pub frms: Vec, - - /// All of the sequences in this collection. - pub seqs: Vec, -} - /// A collection, which may have low- and high-definition variations, or none. -pub type CollectionDef = (Option, Option); - -bitflags! { - struct BmpFlags: u16 - { - const TRANSPARENT = 1 << 14; - const COLUMN_MAJOR = 1 << 15; - } -} - -bitflags! { - /// Flags for `Frame`. - #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] - pub struct FrameFlags: u16 - { - /// The player's torso will obscure the player's legs. - const OBSCURE = 1 << 13; - /// The bitmap will be flipped on the vertical axis. - const FLIP_Y = 1 << 14; - /// The bitmap will be flipped on the horizontal axis. - const FLIP_X = 1 << 15; - } -} - -c_enum! { - /// The type of a collection. - #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] - #[derive(Debug)] - pub enum CollectionType: u16 - { - Unused = 0, - Wall = 1, - Object = 2, - Interface = 3, - Scenery = 4, - } -} - -c_enum! { - /// The type of or number of views for a sequence. - #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] - #[derive(Debug)] - pub enum ViewType: u16 - { - Anim = 1, - Anim8from2 = 2, - Anim4from3 = 3, - Anim4 = 4, - Anim8from5 = 5, - Anim8 = 8, - Anim5from3 = 9, - Still = 10, - Anim5 = 11, - } -} +pub type CollectionDef = (Option, Option); // EOF diff --git a/source/marathon/shp/bmap.rs b/source/marathon/shp/bmap.rs new file mode 100644 index 0000000..f4bff3a --- /dev/null +++ b/source/marathon/shp/bmap.rs @@ -0,0 +1,143 @@ +//! Shapes file bitmap type. + +use crate::durandal::{err::*, image::Image}; +use super::clut; +use bitflags::bitflags; + +/// Reads a `Bitmap`. +pub fn read(b: &[u8]) -> ResultS +{ + read_data! { + endian: BIG, buf: b, size: 26, start: 0, data { + let width = u16[0] usize; + let height = u16[2] usize; + let compr = u16[4]; + let flags = u16[6] flag BmpFlags; + let depth = u16[8]; + } + } + + let compr = compr == u16::max_value(); + let alpha = flags.contains(BmpFlags::TRANSPARENT); + let cmajr = flags.contains(BmpFlags::COLUMN_MAJOR); + + if depth != 8 { + bail!("invalid bit depth (should always be 8)"); + } + + let mut bmp = Bitmap::new(width, height, alpha, cmajr); + let mut p = 30 + if cmajr {4 * width} else {4 * height}; + + let scanlines = if cmajr {width} else {height}; + let pitch = if cmajr {height} else {width}; + + if compr { + // compressed scanlines (transparency RLE) + for _ in 0..scanlines { + read_data! { + endian: BIG, buf: b, size: 4, start: p, data { + let fst = u16[0] usize; + let lst = u16[2] usize; + } + } + + let end = lst - fst; + + p += 4; + + if lst < fst || fst > pitch || lst > pitch { + bail!("invalid compressed scanline"); + } + + for _ in 0..fst { + bmp.cr.push(0); + } + + bmp.cr.extend(ok!(b.get(p..p + end), "not enough data")?); + + for _ in lst..pitch { + bmp.cr.push(0); + } + + p += end; + } + } else { + // simple copy + bmp.cr.extend(ok!(b.get(p..p + width * height), "not enough data")?); + } + + Ok(bmp) +} + +impl Bitmap +{ + /// Creates an empty bitmap. + pub fn new(w: usize, h: usize, alpha: bool, cmajr: bool) -> Self + { + Self{w, h, alpha, cmajr, cr: Vec::with_capacity(w * h)} + } +} + +impl<'a, 'b> ImageShp<'a, 'b> +{ + /// Creates an `ImageShp` with the given bitmap. + pub fn new(bmp: &'a Bitmap, clut: &'b [clut::ColorShp]) -> Self + { + Self{bmp, clut} + } +} + +impl Image for ImageShp<'_, '_> +{ + type Output = clut::ColorShp; + + fn w(&self) -> usize {self.bmp.w} + fn h(&self) -> usize {self.bmp.h} + + fn index(&self, x: usize, y: usize) -> &Self::Output + { + static TRANSLUCENT_COLOR: clut::ColorShp = clut::ColorShp::Translucent; + + let cr = usize::from(if self.bmp.cmajr { + self.bmp.cr[y + x * self.bmp.h] + } else { + self.bmp.cr[x + y * self.bmp.w] + }); + + if self.bmp.alpha && cr == 0 { + &TRANSLUCENT_COLOR + } else { + self.clut.get(cr).unwrap_or(&TRANSLUCENT_COLOR) + } + } +} + +/// An unpacked Shape bitmap. +#[derive(Debug, Eq, PartialEq)] +pub struct Bitmap +{ + w: usize, + h: usize, + cr: Vec, + alpha: bool, + cmajr: bool, +} + +/// An image from a Shape. This mainly just exists so that `Bitmap` can use the +/// `Image` trait. +#[derive(Debug, Eq, PartialEq)] +pub struct ImageShp<'a, 'b> +{ + bmp: &'a Bitmap, + clut: &'b [clut::ColorShp], +} + +bitflags! { + struct BmpFlags: u16 + { + const TRANSPARENT = 1 << 14; + const COLUMN_MAJOR = 1 << 15; + } +} + +// EOF diff --git a/source/marathon/shp/clut.rs b/source/marathon/shp/clut.rs new file mode 100644 index 0000000..71ff8f0 --- /dev/null +++ b/source/marathon/shp/clut.rs @@ -0,0 +1,113 @@ +//! Shapes file color lookup tables. + +use crate::durandal::{err::*, image::Color}; + +/// Reads a color from a color table into `clut`. +pub fn read_color(b: &[u8], clut: &mut [ColorShp]) -> ResultS<()> +{ + read_data! { + endian: BIG, buf: b, size: 8, start: 0, data { + let flag = u8[0]; + let ind = u8[1]; + let r = u16[2]; + let g = u16[4]; + let b = u16[6]; + } + } + + let cr = ok!(clut.get_mut(usize::from(ind)), "bad index")?; + + *cr = match flag { + 128 => ColorShp::Lit {r, g, b}, + 0 => ColorShp::Opaque{r, g, b}, + _ => { + return Err(err_msg("invalid flag in color")); + } + }; + + Ok(()) +} + +/// Reads color tables from `b`. +pub fn read(b: &[u8], tab_ofs: usize, tab_num: usize, clr_num: usize) + -> ResultS> +{ + let end = tab_num * clr_num * 8; + + let b = ok!(b.get(tab_ofs..tab_ofs + end), "bad offset")?; + + let mut v = vec![vec![ColorShp::Translucent; clr_num]; tab_num]; + let mut p = 0; + + for clut in v.iter_mut().take(tab_num) { + for _ in 0..clr_num { + read_color(ok!(b.get(p..p + 8), "not enough data")?, clut)?; + p += 8; + } + } + + Ok(v) +} + +impl Color for ColorShp +{ + fn r(&self) -> u16 + { + match *self { + ColorShp::Translucent => 0, + ColorShp::Opaque{r, ..} | + ColorShp::Lit {r, ..} => r, + } + } + + fn g(&self) -> u16 + { + match *self { + ColorShp::Translucent => 0, + ColorShp::Opaque{g, ..} | + ColorShp::Lit {g, ..} => g, + } + } + + fn b(&self) -> u16 + { + match *self { + ColorShp::Translucent => 0, + ColorShp::Opaque{b, ..} | + ColorShp::Lit {b, ..} => b, + } + } + + fn a(&self) -> u16 + { + match *self { + ColorShp::Translucent => 0, + ColorShp::Opaque{..} | + ColorShp::Lit {..} => u16::max_value(), + } + } +} + +/// A color in a `Clut`. +#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ColorShp +{ + /// A completely translucent color. + Translucent, + + /// An opaque color which may be shaded. + Opaque{/** The red component. */ r: u16, + /** The green component. */ g: u16, + /** The blue component. */ b: u16}, + + /// An opaque color which may not be shaded. + Lit{/** The red component. */ r: u16, + /** The green component. */ g: u16, + /** The blue component. */ b: u16}, +} + +/// A color collection. +pub type Clut = Vec; + +// EOF diff --git a/source/marathon/shp/coll.rs b/source/marathon/shp/coll.rs new file mode 100644 index 0000000..79314ad --- /dev/null +++ b/source/marathon/shp/coll.rs @@ -0,0 +1,72 @@ +//! Shapes file collection type. + +use crate::durandal::{bin::{rd_ofstable, usize_from_u32}, err::*}; +use super::{bmap, clut, fram, sequ}; +use std::convert::TryFrom; + +/// Reads a `Collection`. +pub fn read(b: &[u8]) -> ResultS +{ + read_data! { + endian: BIG, buf: b, size: 544, start: 0, data { + let version = u16[0]; + let cl_type = u16[2] enum CollectionType; + let clr_num = u16[6] usize; + let tab_num = u16[8] usize; + let tab_ofs = u32[10] usize; + let seq_num = u16[14] usize; + let seq_ofs = u32[16] usize; + let frm_num = u16[20] usize; + let frm_ofs = u32[22] usize; + let bmp_num = u16[26] usize; + let bmp_ofs = u32[28] usize; + } + } + + if version != 3 { + bail!("invalid collection definition"); + } + + let tabs = clut::read(b, tab_ofs, tab_num, clr_num)?; + let bmps = rd_ofstable(b, bmp_ofs, bmp_num, bmap::read)?; + let frms = rd_ofstable(b, frm_ofs, frm_num, fram::read)?; + let seqs = rd_ofstable(b, seq_ofs, seq_num, sequ::read)?; + + Ok(Collection{ctyp: cl_type, tabs, bmps, frms, seqs}) +} + +/// A collection of color tables, bitmaps, frames and sequences. +#[derive(Debug, Eq, PartialEq)] +pub struct Collection +{ + /// The type of collection this is. + pub ctyp: CollectionType, + + /// All of the color tables in this collection. + pub tabs: Vec, + + /// All of the bitmaps in this collection. + pub bmps: Vec, + + /// All of the frames in this collection. + pub frms: Vec, + + /// All of the sequences in this collection. + pub seqs: Vec, +} + +c_enum! { + /// The type of a collection. + #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] + #[derive(Debug)] + pub enum CollectionType: u16 + { + Unused = 0, + Wall = 1, + Object = 2, + Interface = 3, + Scenery = 4, + } +} + +// EOF diff --git a/source/marathon/shp/fram.rs b/source/marathon/shp/fram.rs new file mode 100644 index 0000000..2a158ec --- /dev/null +++ b/source/marathon/shp/fram.rs @@ -0,0 +1,73 @@ +//! Shapes file frame type. + +use crate::durandal::{err::*, fixed::*}; +use bitflags::bitflags; + +/// Reads a `Frame`. +pub fn read(b: &[u8]) -> ResultS +{ + read_data! { + endian: BIG, buf: b, size: 36, start: 0, data { + let flags = u16[0] flag FrameFlags; + let min_lt = Fixed[2]; + let bmp_ind = u16[6] usize; + let wrl_l = Unit[16]; + let wrl_r = Unit[18]; + let wrl_t = Unit[20]; + let wrl_b = Unit[22]; + let wrl_x = Unit[24]; + let wrl_y = Unit[26]; + } + } + + Ok(Frame{flags, min_lt, bmp_ind, wrl_l, wrl_r, wrl_t, wrl_b, wrl_x, wrl_y}) +} + +/// A frame, also known as a low level shape. +#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] +#[derive(Debug, Eq, PartialEq)] +pub struct Frame +{ + /// The flags for this frame. + pub flags: FrameFlags, + + /// The minimum light level for this frame. + pub min_lt: Fixed, + + /// The index of the bitmap this frame uses. + pub bmp_ind: usize, + + /// The left translation for this frame. + pub wrl_l: Unit, + + /// The right translation for this frame. + pub wrl_r: Unit, + + /// The top translation for this frame. + pub wrl_t: Unit, + + /// The bottom translation for this frame. + pub wrl_b: Unit, + + /// The X translation for this frame. + pub wrl_x: Unit, + + /// The Y translation for this frame. + pub wrl_y: Unit, +} + +bitflags! { + /// Flags for `Frame`. + #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] + pub struct FrameFlags: u16 + { + /// The player's torso will obscure the player's legs. + const OBSCURE = 1 << 13; + /// The bitmap will be flipped on the vertical axis. + const FLIP_Y = 1 << 14; + /// The bitmap will be flipped on the horizontal axis. + const FLIP_X = 1 << 15; + } +} + +// EOF diff --git a/source/marathon/shp/sequ.rs b/source/marathon/shp/sequ.rs new file mode 100644 index 0000000..1147433 --- /dev/null +++ b/source/marathon/shp/sequ.rs @@ -0,0 +1,90 @@ +//! Shapes file sequence type. + +use crate::{durandal::{bin::OptU16, err::*}, + marathon::{text::{mac_roman_conv, pascal_str}, + xfer::TransferMode}}; +use std::convert::TryFrom; + +/// Reads a `Sequence`. +pub fn read(b: &[u8]) -> ResultS +{ + read_data! { + endian: BIG, buf: b, size: 88, start: 0, data { + let name = u8[4; 34]; + let v_type = u16[38] enum ViewType; + let frames = u16[40]; + let ticks = u16[42]; + let key = u16[44]; + let xfer = u16[46] enum TransferMode; + let xfer_pd = u16[48]; + let snd_beg = OptU16[50]; + let snd_key = OptU16[52]; + let snd_end = OptU16[54]; + let loop_f = u16[58]; + } + } + + let name = mac_roman_conv(ok!(pascal_str(name), "bad string")?); + + Ok(Sequence{name, v_type, frames, ticks, key, xfer, xfer_pd, snd_beg, + snd_key, snd_end, loop_f}) +} + +/// A sequence, also known as a high level shape. +#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] +#[derive(Debug, Eq, PartialEq)] +pub struct Sequence +{ + /// The display name for this sequence. + pub name: String, + + /// The view type for each frame in this sequence. + pub v_type: ViewType, + + /// The number of frames in this sequence. + pub frames: u16, + + /// The number of ticks each frame in this sequence takes. + pub ticks: u16, + + /// The key frame index for this sequence. + pub key: u16, + + /// The transfer mode to play over this sequence. + pub xfer: TransferMode, + + /// The period in game ticks the transfer mode plays over. + pub xfer_pd: u16, + + /// The sound to play at the beginning of this sequence. + pub snd_beg: OptU16, + + /// The sound to play at the key frame of this sequence. + pub snd_key: OptU16, + + /// The sound to play at the end of this sequence. + pub snd_end: OptU16, + + /// Which frame to loop on. + pub loop_f: u16, +} + +c_enum! { + /// The type of or number of views for a sequence. + #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] + #[derive(Debug)] + pub enum ViewType: u16 + { + Anim = 1, + Anim8from2 = 2, + Anim4from3 = 3, + Anim4 = 4, + Anim8from5 = 5, + Anim8 = 8, + Anim5from3 = 9, + Still = 10, + Anim5 = 11, + } +} + +// EOF diff --git a/source/marathon/snd.rs b/source/marathon/snd.rs index ea1d344..7a816bd 100644 --- a/source/marathon/snd.rs +++ b/source/marathon/snd.rs @@ -1,91 +1,13 @@ //! Marathon Sounds format handling. -use crate::durandal::{bin::*, err::*, fixed::*, sound::*}; -use bitflags::bitflags; -use std::{collections::BTreeMap, convert::TryFrom}; +pub mod defs; +pub mod snds; -/// Reads a sound. -pub fn read_sound(b: &[u8]) -> ResultS -{ - read_data! { - endian: BIG, buf: b, size: 21, start: 0, data { - let len = u32[4] usize; - let rate = u16[8]; - let lp_beg = u32[12] usize; - let lp_end = u32[16] usize; - let magic = u8[20]; - } - } - - match magic { - 0 => { - let stream = &b[22..22 + len]; - Ok(Sound16::new_from_8(rate, lp_beg, lp_end, stream)) - } - 0xFF => { - read_data! { - endian: BIG, buf: b, size: 42, start: 22, data { - let len = u32[0] usize; - let bps = u16[26]; - } - } - - match bps { - 16 => { - let stream = &b[63..63 + len * 2]; - Ok(Sound16::new_from_16(rate, lp_beg, lp_end, stream)) - } - 8 => { - let stream = &b[63..63 + len]; - Ok(Sound16::new_from_8(rate, lp_beg, lp_end, stream)) - } - _ => bail!("bad bits per sample"), - } - } - _ => bail!("invalid magic number"), - } -} - -/// Reads a sound definition. -pub fn read_sound_def(b: &[u8]) -> ResultS, u16, SoundDef)>> -{ - read_data! { - endian: BIG, buf: b, size: 64, start: 0, data { - let index = u16[0]; - let volume = u16[2] enum Volume; - let flags = u16[4] flag SoundFlags; - let chance = u16[6]; - let pitch_lo = Fixed[8]; - let pitch_hi = Fixed[12]; - let n_sounds = u16[16] usize; - let grp_ofs = u32[20] usize; - } - } - - if index == u16::max_value() { - return Ok(None); - } - - if n_sounds > 5 { - bail!("too many sounds"); - } - - let mut ofs = Vec::with_capacity(n_sounds); - let mut p = 36; - - for _ in 0..n_sounds { - ofs.push(grp_ofs + usize_from_u32(u32b(&b[p..]))); - p += 4; - } - - let sounds = Vec::with_capacity(n_sounds); - - Ok(Some((ofs, index, - SoundDef{volume, flags, chance, pitch_lo, pitch_hi, sounds}))) -} +use crate::durandal::{bin::Ident, err::*}; +use std::collections::BTreeMap; /// Reads all sounds from a Sound file. -pub fn read_sounds(b: &[u8]) -> ResultS> +pub fn read(b: &[u8]) -> ResultS> { read_data! { endian: BIG, buf: b, size: 260, start: 0, data { @@ -104,12 +26,12 @@ pub fn read_sounds(b: &[u8]) -> ResultS> let mut p = 260; for _ in 0..src_num { - let mut st = BTreeMap::new(); + let mut st = SoundTable::new(); for _ in 0..snd_num { - if let Some((ofs, idx, mut def)) = read_sound_def(&b[p..p + 64])? { + if let Some((ofs, idx, mut def)) = defs::read(&b[p..p + 64])? { for &ofs in &ofs { - def.sounds.push(read_sound(&b[ofs..])?); + def.sounds.push(snds::read(&b[ofs..])?); } st.insert(idx, def); @@ -124,64 +46,7 @@ pub fn read_sounds(b: &[u8]) -> ResultS> Ok(sc) } -/// A sound definition containing one, many or no sounds. -#[derive(Debug, Eq, PartialEq)] -pub struct SoundDef -{ - /// The volume type for this sound. - pub volume: Volume, - - /// The flags for this sound. - pub flags: SoundFlags, - - /// The chance out of `u16::max_value()` that this sound will not play. - pub chance: u16, - - /// The low random pitch bound. - pub pitch_lo: Fixed, - - /// The high random pitch bound. - pub pitch_hi: Fixed, - - /// All of the sounds in this collection. - pub sounds: Vec, -} - /// A table of sound definitions. -pub type SoundTable = BTreeMap; - -bitflags! { - /// Flags for `SoundDef`. - #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] - pub struct SoundFlags: u16 - { - /// The sound will not restart when trying to play over itself. - const NO_RESTART = 1; - /// The sound will not switch channels when trying to play over itself. - const NO_CHANNEL_SWITCH = 1 << 1; - /// The pitch variance will be halved. - const LESS_PITCH_CHANGE = 1 << 2; - /// The pitch variance will be nullified. - const NO_PITCH_CHANGE = 1 << 3; - /// The sound will play even when completely obstructed by walls. - const NO_OBSTRUCTION = 1 << 4; - /// The sound will play even when completely obstructed by media. - const NO_MEDIA_OBSTRUCT = 1 << 5; - /// The sound will have special stereo effects. - const AMBIENT = 1 << 6; - } -} - -c_enum! { - /// The type of volume this sound has. - #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] - #[derive(Debug)] - pub enum Volume: u16 - { - Quiet = 0, - Normal = 1, - Loud = 2, - } -} +pub type SoundTable = BTreeMap; // EOF diff --git a/source/marathon/snd/defs.rs b/source/marathon/snd/defs.rs new file mode 100644 index 0000000..bb6f8ce --- /dev/null +++ b/source/marathon/snd/defs.rs @@ -0,0 +1,105 @@ +//! Sounds format definition type. + +use crate::durandal::{bin::{u32b, usize_from_u32}, + err::*, + fixed::*, + sound::Sound16}; +use std::convert::TryFrom; +use bitflags::bitflags; + +/// Reads a sound definition. +pub fn read(b: &[u8]) -> ResultS, u16, SoundDef)>> +{ + read_data! { + endian: BIG, buf: b, size: 64, start: 0, data { + let index = u16[0]; + let volume = u16[2] enum Volume; + let flags = u16[4] flag SoundFlags; + let chance = u16[6]; + let pitch_lo = Fixed[8]; + let pitch_hi = Fixed[12]; + let n_sounds = u16[16] usize; + let grp_ofs = u32[20] usize; + } + } + + if index == u16::max_value() { + return Ok(None); + } + + if n_sounds > 5 { + bail!("too many sounds"); + } + + let mut ofs = Vec::with_capacity(n_sounds); + let mut p = 36; + + for _ in 0..n_sounds { + ofs.push(grp_ofs + usize_from_u32(u32b(&b[p..]))); + p += 4; + } + + let sounds = Vec::with_capacity(n_sounds); + + Ok(Some((ofs, index, + SoundDef{volume, flags, chance, pitch_lo, pitch_hi, sounds}))) +} + +/// A sound definition containing one, many or no sounds. +#[derive(Debug, Eq, PartialEq)] +pub struct SoundDef +{ + /// The volume type for this sound. + pub volume: Volume, + + /// The flags for this sound. + pub flags: SoundFlags, + + /// The chance out of `u16::max_value()` that this sound will not play. + pub chance: u16, + + /// The low random pitch bound. + pub pitch_lo: Fixed, + + /// The high random pitch bound. + pub pitch_hi: Fixed, + + /// All of the sounds in this collection. + pub sounds: Vec, +} + +bitflags! { + /// Flags for `SoundDef`. + #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] + pub struct SoundFlags: u16 + { + /// The sound will not restart when trying to play over itself. + const NO_RESTART = 1; + /// The sound will not switch channels when trying to play over itself. + const NO_CHANNEL_SWITCH = 1 << 1; + /// The pitch variance will be halved. + const LESS_PITCH_CHANGE = 1 << 2; + /// The pitch variance will be nullified. + const NO_PITCH_CHANGE = 1 << 3; + /// The sound will play even when completely obstructed by walls. + const NO_OBSTRUCTION = 1 << 4; + /// The sound will play even when completely obstructed by media. + const NO_MEDIA_OBSTRUCT = 1 << 5; + /// The sound will have special stereo effects. + const AMBIENT = 1 << 6; + } +} + +c_enum! { + /// The type of volume this sound has. + #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] + #[derive(Debug)] + pub enum Volume: u16 + { + Quiet = 0, + Normal = 1, + Loud = 2, + } +} + +// EOF diff --git a/source/marathon/snd/snds.rs b/source/marathon/snd/snds.rs new file mode 100644 index 0000000..7ed0511 --- /dev/null +++ b/source/marathon/snd/snds.rs @@ -0,0 +1,47 @@ +//! Sounds file audio type. + +use crate::durandal::{bin::usize_from_u32, err::*, sound::Sound16}; + +/// Reads a sound. +pub fn read(b: &[u8]) -> ResultS +{ + read_data! { + endian: BIG, buf: b, size: 21, start: 0, data { + let len = u32[4] usize; + let rate = u16[8]; + let lp_beg = u32[12] usize; + let lp_end = u32[16] usize; + let magic = u8[20]; + } + } + + match magic { + 0 => { + let stream = &b[22..22 + len]; + Ok(Sound16::new_from_8(rate, lp_beg, lp_end, stream)) + } + 0xFF => { + read_data! { + endian: BIG, buf: b, size: 42, start: 22, data { + let len = u32[0] usize; + let bps = u16[26]; + } + } + + match bps { + 16 => { + let stream = &b[63..63 + len * 2]; + Ok(Sound16::new_from_16(rate, lp_beg, lp_end, stream)) + } + 8 => { + let stream = &b[63..63 + len]; + Ok(Sound16::new_from_8(rate, lp_beg, lp_end, stream)) + } + _ => bail!("bad bits per sample"), + } + } + _ => bail!("invalid magic number"), + } +} + +// EOF diff --git a/source/marathon/wad.rs b/source/marathon/wad.rs deleted file mode 100644 index 3118bb6..0000000 --- a/source/marathon/wad.rs +++ /dev/null @@ -1,211 +0,0 @@ -//! Marathon Wad format handling. - -use crate::{durandal::{bin::*, err::*, image}, - marathon::{map, phy, pict, text::mac_roman_cstr}}; -use std::{collections::BTreeMap, convert::TryFrom}; - -/// Reads all chunks in an entry. -pub fn read_chunks(b: &[u8], old: bool, siz_cnk: usize) -> ResultS> -{ - let mut chunks = Vec::new(); - let mut p = 0; - - let read_chunk_minf = if old {map::minf::read_old} else {map::minf::read}; - let read_chunk_sids = if old {map::sids::read_old} else {map::sids::read}; - let read_chunk_poly = if old {map::poly::read_old} else {map::poly::read}; - let read_chunk_lite = if old {map::lite::read_old} else {map::lite::read}; - - while p < b.len() { - read_data! { - endian: BIG, buf: b, size: siz_cnk, start: p, data { - let iden = Ident[0]; - let size = u32[8] usize; - } - } - - let beg = p + siz_cnk; - let end = beg + size; - let data = &b[beg..end]; - - chunks.push(match &iden.0 { - b"PICT" => Chunk::Pict(pict::load_pict(data)?), - b"Minf" => Chunk::Minf(read_chunk_minf(data)?), - b"iidx" => Chunk::Iidx(rd_array(data, map::iidx::read)?), - b"EPNT" => Chunk::Epnt(rd_array(data, map::epnt::read)?), - b"PNTS" => Chunk::Pnts(rd_array(data, map::pnts::read)?), - b"LINS" => Chunk::Lins(rd_array(data, map::lins::read)?), - b"SIDS" => Chunk::Sids(rd_array(data, read_chunk_sids)?), - b"POLY" => Chunk::Poly(rd_array(data, read_chunk_poly)?), - b"OBJS" => Chunk::Objs(rd_array(data, map::objs::read)?), - b"LITE" => Chunk::Lite(rd_array(data, read_chunk_lite)?), - b"plac" => Chunk::Plac(rd_array(data, map::plac::read)?), - b"ambi" => Chunk::Ambi(rd_array(data, map::ambi::read)?), - b"bonk" => Chunk::Bonk(rd_array(data, map::bonk::read)?), - b"medi" => Chunk::Medi(rd_array(data, map::medi::read)?), - b"plat" => Chunk::Plat(rd_array(data, map::plat::read)?), - b"NOTE" => Chunk::Note(rd_array(data, map::note::read)?), - b"term" => Chunk::Term(rd_array(data, map::term::read)?), - b"FXpx" => Chunk::Fxpx(rd_array(data, phy::fxpx::read)?), - b"MNpx" => Chunk::Mnpx(rd_array(data, phy::mnpx::read)?), - b"PRpx" => Chunk::Prpx(rd_array(data, phy::prpx::read)?), - b"PXpx" => Chunk::Pxpx(rd_array(data, phy::pxpx::read)?), - b"WPpx" => Chunk::Wppx(rd_array(data, phy::wppx::read)?), - _ => Chunk::Data{iden, data: data.to_vec()}, - }); - - p = end; - } - - Ok(chunks) -} - -/// Reads all entries in a `Wad`. -pub fn read_entries(b: &[u8], - old_wad: bool, - old_dat: bool, - siz_app: usize, - siz_ent: usize, - siz_cnk: usize) - -> ResultS> -{ - read_data! { - endian: BIG, buf: b, size: 128, start: 0, data { - let dirofs = u32[72] usize; - let numents = u16[76] usize; - } - } - - let mut entries = BTreeMap::new(); - let mut p = dirofs; - - for i in 0..numents { - read_data! { - endian: BIG, buf: b, size: siz_ent, start: p, data { - let offset = u32[0] usize; - let size = u32[4] usize; - let index = u16[8]; - } - } - - let index = if old_wad {i as u16} else {index}; - - let chunks = read_chunks(&b[offset..offset + size], old_dat, siz_cnk)?; - let appdata = b[p..p + siz_app].to_vec(); - - entries.insert(index, Entry{chunks, appdata}); - - p += siz_ent + siz_app; - } - - Ok(entries) -} - -/// Reads a Map file. -pub fn read_wad(b: &[u8]) -> ResultS -{ - read_data! { - endian: BIG, buf: b, size: 128, start: 0, data { - let ver_wad = u16[0] enum Ver; - let ver_dat = u16[2]; - let name = mac_roman_cstr[4; 64] no_try; - let siz_app = u16[78] usize; - let siz_wcnk = u16[80] usize; - let siz_went = u16[82] usize; - } - } - - let old_dat = ver_dat == 0; - let old_wad = match ver_wad { - Ver::Base => true, - _ => false, - }; - - let siz_ent = if old_wad {8 } else {10}; - let siz_cnk = if old_wad {12} else {16}; - - if !old_wad && siz_ent != siz_went { - bail!("invalid entry size"); - } - - if !old_wad && siz_cnk != siz_wcnk { - bail!("invalid chunk size"); - } - - let entries = read_entries(b, old_wad, old_dat, siz_app, siz_ent, siz_cnk)?; - - Ok(Wad{name, siz_app, entries}) -} - -/// Any kind of chunk in an `Entry`. -#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] -#[derive(Debug, Eq, PartialEq)] -pub enum Chunk -{ - /** A `PICT` chunk. */ Pict(image::Image8), - /** A `Minf` chunk. */ Minf(map::minf::Minf), - /** An `iidx` chunk. */ Iidx(Vec), - /** An `EPNT` chunk. */ Epnt(Vec), - /** A `PNTS` chunk. */ Pnts(Vec), - /** A `LINS` chunk. */ Lins(Vec), - /** A `SIDS` chunk. */ Sids(Vec), - /** A `POLY` chunk. */ Poly(Vec), - /** A `LITE` chunk. */ Lite(Vec), - /** An `OBJS` chunk. */ Objs(Vec), - /** A `plac` chunk. */ Plac(Vec), - /** An `ambi` chunk. */ Ambi(Vec), - /** A `bonk` chunk. */ Bonk(Vec), - /** A `medi` chunk. */ Medi(Vec), - /** A `plat` chunk. */ Plat(Vec), - /** A `NOTE` chunk. */ Note(Vec), - /** A `term` chunk. */ Term(Vec), - /** A `FXpx` chunk. */ Fxpx(Vec), - /** A `MNpx` chunk. */ Mnpx(Vec), - /** A `PRpx` chunk. */ Prpx(Vec), - /** A `PXpx` chunk. */ Pxpx(Vec), - /** A `WPpx` chunk. */ Wppx(Vec), - - /// Any other type of chunk, which may have arbitrary data in it. - Data{/** The name of the chunk. */ iden: Ident, - /** The data. */ data: Vec}, -} - -/// An entry containing chunks and application-specific data. -#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] -#[derive(Debug, Eq, PartialEq)] -pub struct Entry -{ - /// All of the chunks in this `Entry`. - pub chunks: Vec, - - /// The application specific data for this Entry. - pub appdata: Vec, -} - -/// A Map file containing entries. -#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] -#[derive(Debug, Eq, PartialEq)] -pub struct Wad -{ - /// The original name of this file. - pub name: String, - - /// The size of each `Entry`'s `appdata` field. - pub siz_app: usize, - - /// All of the entries in this `Wad`. - pub entries: BTreeMap, -} - -c_enum! { - /// The version of a `Wad`. - #[derive(Debug)] - enum Ver: u16 - { - Base = 0, - Dir = 1, - Over = 2, - Inf = 4, - } -} - -// EOF diff --git a/source/marathon/xfer.rs b/source/marathon/xfer.rs index cd86be7..3cb1070 100644 --- a/source/marathon/xfer.rs +++ b/source/marathon/xfer.rs @@ -1,7 +1,5 @@ //! Transfer Mode type. -use crate::durandal::err::*; - impl Default for TransferMode { fn default() -> Self {TransferMode::Normal} diff --git a/source/rozinante/editor.rs b/source/rozinante/editor.rs index eaf3cda..e2c7820 100644 --- a/source/rozinante/editor.rs +++ b/source/rozinante/editor.rs @@ -9,7 +9,7 @@ mod block; pub use block::*; use super::{color, draw::*}; -use crate::{durandal::image::*, marathon::{machdr, wad}}; +use crate::{durandal::image::*, marathon::{machdr, map}}; impl MapEditor { @@ -24,12 +24,12 @@ impl MapEditor { // TODO: handle errors gracefully let b = &b[machdr::try_mac_header(b)..]; - let wad = wad::read_wad(b).unwrap(); + let wad = map::read(b).unwrap(); let ent = wad.entries.iter().nth(0).unwrap().1; let info = ent.chunks.iter().find_map(|cnk| { match cnk { - wad::Chunk::Minf(info) => Some(info), + map::chnk::Chunk::Minf(info) => Some(info), _ => None, } }).unwrap().clone(); diff --git a/tests/map.rs b/tests/map.rs index 5d7dc6c..a2da088 100644 --- a/tests/map.rs +++ b/tests/map.rs @@ -87,4 +87,23 @@ fn map_must_not_panic() } } +#[test] +fn phy_must_not_process() +{ + for inp in &RANDOM { + bin::rd_array(inp, map::fxpx::read).err().unwrap(); + bin::rd_array(inp, map::mnpx::read).err().unwrap(); + bin::rd_array(inp, map::prpx::read).err().unwrap(); + bin::rd_array(inp, map::wppx::read).err().unwrap(); + } +} + +#[test] +fn phy_must_not_panic() +{ + for inp in &RANDOM { + drop(bin::rd_array(inp, map::pxpx::read)); + } +} + // EOF diff --git a/tests/phy.rs b/tests/phy.rs deleted file mode 100644 index 0e809ff..0000000 --- a/tests/phy.rs +++ /dev/null @@ -1,24 +0,0 @@ -use maraiah::{durandal::bin, marathon::phy}; - -include!("data/rand.rs"); - -#[test] -fn phy_must_not_process() -{ - for inp in &RANDOM { - bin::rd_array(inp, phy::fxpx::read).err().unwrap(); - bin::rd_array(inp, phy::mnpx::read).err().unwrap(); - bin::rd_array(inp, phy::prpx::read).err().unwrap(); - bin::rd_array(inp, phy::wppx::read).err().unwrap(); - } -} - -#[test] -fn phy_must_not_panic() -{ - for inp in &RANDOM { - drop(bin::rd_array(inp, phy::pxpx::read)); - } -} - -// EOF diff --git a/tests/shp.rs b/tests/shp.rs index 619353e..10faea2 100644 --- a/tests/shp.rs +++ b/tests/shp.rs @@ -6,11 +6,11 @@ include!("data/rand.rs"); fn shp_must_not_process() { for inp in &RANDOM { - assert!(shp::read_bitmap(inp).is_err()); - assert!(shp::read_collection(inp).is_err()); - assert!(shp::read_frame(inp).is_err()); - assert!(shp::read_sequence(inp).is_err()); - assert!(shp::read_shapes(inp).is_err()); + assert!(shp::bmap::read(inp).is_err()); + assert!(shp::coll::read(inp).is_err()); + assert!(shp::fram::read(inp).is_err()); + assert!(shp::sequ::read(inp).is_err()); + assert!(shp::read(inp).is_err()); } } diff --git a/tests/snd.rs b/tests/snd.rs index 452284a..8b7897d 100644 --- a/tests/snd.rs +++ b/tests/snd.rs @@ -6,9 +6,9 @@ include!("data/rand.rs"); fn snd_must_not_process() { for inp in &RANDOM { - assert!(snd::read_sound(inp).is_err()); - assert!(snd::read_sound_def(inp).is_err()); - assert!(snd::read_sounds(inp).is_err()); + assert!(snd::defs::read(inp).is_err()); + assert!(snd::snds::read(inp).is_err()); + assert!(snd::read(inp).is_err()); } }