//! Marathon Shapes format handling. use crate::{durandal::{bin::*, err::*, fixed::*, image::*, text::*}, marathon::xfer::TransferMode}; use bitflags::bitflags; /// Reads a color from a color table into `clut`. 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 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_from_slice(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_from_slice(ok!(b.get(p..p + width * height), "not enough data")?); } 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> { let mut cl = Vec::with_capacity(32); let mut p = 0; for _ in 0..32 { read_data! { endian: BIG, buf: b, size: 32, start: p, data { let lo_ofs = u32[4] usize; let lo_len = u32[8] usize; let hi_ofs = u32[12] usize; 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 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)); p += 32; } 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, } } // EOF