From f5984c9739e5af629fb4e6daa28e0d63386d08df Mon Sep 17 00:00:00 2001 From: Marrub Date: Thu, 6 Sep 2018 12:01:52 -0400 Subject: [PATCH] Add initial test code --- .gitignore | 4 + Cargo.toml | 8 + src/durandal/bin.rs | 24 +++ src/durandal/image.rs | 59 +++++++ src/durandal/machead.rs | 53 +++++++ src/durandal/mod.rs | 10 ++ src/durandal/pict.rs | 336 ++++++++++++++++++++++++++++++++++++++++ src/durandal/text.rs | 91 +++++++++++ src/main.rs | 47 ++++++ src/marathon/mod.rs | 5 + src/marathon/wad.rs | 100 ++++++++++++ 11 files changed, 737 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/durandal/bin.rs create mode 100644 src/durandal/image.rs create mode 100644 src/durandal/machead.rs create mode 100644 src/durandal/mod.rs create mode 100644 src/durandal/pict.rs create mode 100644 src/durandal/text.rs create mode 100644 src/main.rs create mode 100644 src/marathon/mod.rs create mode 100644 src/marathon/wad.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..212280d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +**/*.rs.bk +Cargo.lock +perf.data* diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7863731 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "maraiah" +version = "0.1.0" +authors = ["marrub"] + +[dependencies] +memmap = "0.6" +#gtk = "0.4" diff --git a/src/durandal/bin.rs b/src/durandal/bin.rs new file mode 100644 index 0000000..849a4b6 --- /dev/null +++ b/src/durandal/bin.rs @@ -0,0 +1,24 @@ +//! Binary data conversion utilities. + +pub type Ident = [u8; 4]; + +pub fn b_iden(b: &[u8]) -> Ident {[b[0], b[1], b[2], b[3]]} + +pub fn b_u32l(b: &[u8]) -> u32 {b[3] as (u32) << 24 | b[2] as (u32) << 16 | + b[1] as (u32) << 8 | b[0] as u32} +pub fn b_u16l(b: &[u8]) -> u16 {b[1] as (u16) << 8 | b[0] as u16} +pub fn b_u32b(b: &[u8]) -> u32 {b[0] as (u32) << 24 | b[1] as (u32) << 16 | + b[2] as (u32) << 8 | b[3] as u32} +pub fn b_u16b(b: &[u8]) -> u16 {b[0] as (u16) << 8 | b[1] as u16} +pub fn b_i32l(b: &[u8]) -> i32 {b_u32l(b) as i32} +pub fn b_i16l(b: &[u8]) -> i16 {b_u16l(b) as i16} +pub fn b_i32b(b: &[u8]) -> i32 {b_u32b(b) as i32} +pub fn b_i16b(b: &[u8]) -> i16 {b_u16b(b) as i16} + +pub fn d_u32b(n: u32) -> [u8; 4] {[(n >> 24) as u8, (n >> 16) as u8, + (n >> 8) as u8, (n >> 0) as u8]} +pub fn d_u16b(n: u16) -> [u8; 2] {[(n >> 8) as u8, (n >> 0) as u8]} +pub fn d_i32b(n: i32) -> [u8; 4] {d_u32b(n as u32)} +pub fn d_i16b(n: i16) -> [u8; 2] {d_u16b(n as u16)} + +// EOF diff --git a/src/durandal/image.rs b/src/durandal/image.rs new file mode 100644 index 0000000..d3ca2b7 --- /dev/null +++ b/src/durandal/image.rs @@ -0,0 +1,59 @@ +//! Image and color representations. + +use std::ops::{Index, IndexMut}; + +/// RGBA8 color. +#[derive(Clone)] +pub struct Color +{ + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +/// Image with width and height. +pub struct Image +{ + w: usize, + h: usize, + cr: Vec, +} + +impl Color +{ + /// Converts a R5G5B5 format color to RGBA8. + pub fn from_r5g5b5(rgb: u16) -> Color + { + Color{r: (rgb >> 10 ) as u8 * 8, + g: (rgb >> 5 & 31) as u8 * 8, + b: (rgb & 31) as u8 * 8, + a: 255} + } +} + +impl Image +{ + /// Creates a new Image structure. + pub fn new(w: usize, h: usize) -> Image + {Image{w, h, cr: Vec::with_capacity(w as usize * h as usize)}} + + pub fn w(&self) -> usize {self.w} + pub fn h(&self) -> usize {self.h} +} + +impl Index<(usize, usize)> for Image +{ + type Output = Color; + + fn index(&self, (x, y): (usize, usize)) -> &Color + {&self.cr[x + y * self.w]} +} + +impl IndexMut<(usize, usize)> for Image +{ + fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut Color + {&mut self.cr[x + y * self.w]} +} + +// EOF diff --git a/src/durandal/machead.rs b/src/durandal/machead.rs new file mode 100644 index 0000000..9e60889 --- /dev/null +++ b/src/durandal/machead.rs @@ -0,0 +1,53 @@ +//! Macintosh archived format header utilities. + +use durandal::bin::*; + +/// Checks for an AppleSingle header. Returns offset to the resource fork. +pub fn check_apple_single(b: &[u8]) -> usize +{ + if b_u32b(&b[0..4]) != 0x51600 || b_u32b(&b[4..8]) != 0x20000 + {return 0} + + let num = b_u16b(&b[24..26]) as usize; + + for i in 0..num + { + let p = 26 + (12 * i); + let fid = b_u32b(&b[p+0..p+ 4]); + let ofs = b_u32b(&b[p+4..p+ 8]) as usize; + let len = b_u32b(&b[p+8..p+12]) as usize; + + if fid == 1 {return if ofs + len > b.len() {0} else {ofs}} + } + + 0 +} + +/// Checks for a MacBin header. Returns offset to the resource fork. +pub fn check_mac_bin(b: &[u8]) -> usize +{ + if b[0] != 0 || b[1] > 63 || b[74] != 0 || b[123] > 0x81 {return 0} + + let mut crc = 0; + + for i in 0..124 { + for j in 8..16 + { + let d = b[i] as (u16) << j; + if (d ^ crc) & 0x8000 != 0 {crc = crc << 1 ^ 0x1021} + else {crc <<= 1} + } + } + + if crc == b_u16b(&b[124..126]) {128} else {0} +} + +/// Reads a MacBin or AppleSingle header if there is one and returns the +/// offset from the start of the header to the resource fork (if one is found.) +pub fn try_mac_header(b: &[u8]) -> usize +{ + let ofs = check_mac_bin(b); + if ofs != 0 {ofs} else {check_apple_single(b)} +} + +// EOF diff --git a/src/durandal/mod.rs b/src/durandal/mod.rs new file mode 100644 index 0000000..c0ccaf3 --- /dev/null +++ b/src/durandal/mod.rs @@ -0,0 +1,10 @@ +//! Library for file reading utilities and mac format utilities. + +#[allow(dead_code)] +pub mod bin; +pub mod machead; +pub mod text; +pub mod image; +pub mod pict; + +// EOF diff --git a/src/durandal/pict.rs b/src/durandal/pict.rs new file mode 100644 index 0000000..6af6ed8 --- /dev/null +++ b/src/durandal/pict.rs @@ -0,0 +1,336 @@ +//! QuickDraw PICT format loader. + +use durandal::image::*; +use durandal::bin::*; + +const PACK_DEFAULT: u16 = 0; +const PACK_NONE : u16 = 1; +const PACK_NOALPHA: u16 = 2; +const PACK_RLE16 : u16 = 3; +const PACK_RLE32 : u16 = 4; + +struct PixMap +{ + pack: u16, + dept: u16, + rle : bool, + cmap: Vec, +} + +impl PixMap +{ + fn empty() -> PixMap + {PixMap{pack: PACK_DEFAULT, dept: 1, rle: false, cmap: Vec::new()}} + + fn new(b: &[u8], packed: bool) -> (usize, PixMap) + { + // version = b_u16b(&b[ 0.. 2]); + let pack = b_u16b(&b[ 2.. 4]); + // packSize = b_u32b(&b[ 4.. 8]) as usize; + // horzDPI = b_u32b(&b[ 8..12]); + // vertDPI = b_u32b(&b[12..16]); + // pixelType = b_u16b(&b[16..18]); + let dept = b_u16b(&b[18..20]); + // components = b_u16b(&b[20..22]); + // compDepth = b_u16b(&b[22..24]); + // planeOffs = b_u32b(&b[24..28]); + // colorTable = b_u32b(&b[28..32]); + // reserved = b_u32b(&b[32..36]); + + let mut px = PixMap{ + pack, + dept, + rle: pack == PACK_DEFAULT || + (dept == 16 && pack == PACK_RLE16) || + (dept == 32 && pack == PACK_RLE32), + cmap: Vec::new(), + }; + + // if it's packed we need to grab the color map + if packed + { + // table = b_u32b(&b[36..40]); + let dev = b_u16b(&b[40..42]) & 0x8000 != 0; + let colo = b_u16b(&b[42..44]) as usize + 1; + + px.cmap.resize(colo as usize, Color{r: 0, g: 0, b: 0, a: 0}); + for i in 0..colo + { + let p = 44 + i * 8; + let n = (b_u16b(&b[p+0..p+ 2]) & 0xff) as usize; + let r = (b_u16b(&b[p+2..p+ 4]) / 257) as u8; + let g = (b_u16b(&b[p+6..p+ 8]) / 257) as u8; + let b = (b_u16b(&b[p+8..p+10]) / 257) as u8; + + // with device mapping, we ignore the index entirely + let n = if dev {i} else {n}; + + px.cmap[n] = Color{r, g, b, a: 255}; + } + + (44 + colo * 8, px) + } + else + {(36, px)} + } +} + +/// Read run-length encoded data. +fn read_rle(b: &[u8], long: bool, rd: F) -> Vec + where T: Copy, + F: Fn(&mut usize) -> T +{ + let (st, sz) = if long {(2usize, b_u16b(&b[0..2]) as usize)} + else {(1usize, b[0] as usize)}; + + let mut o = Vec::with_capacity(sz); + let mut p = st; + + while p < sz + { + // size and flags are in one byte, we interpret it as a signed integer + // because it's easier to handle + let szf = b[(p, p += 1).0] as i8; + let sz = if szf < 0 {-szf + 1} else {szf + 1}; + + o.reserve(sz as usize); + + // either repeated or unique data + if szf < 0 {let d = rd(&mut p); for _ in 0..sz { o.push(d)}} + else {for _ in 0..sz {let d = rd(&mut p); o.push(d)}} + } + + o +} + +fn read_rle8(b: &[u8], long: bool) -> Vec + {read_rle(b, long, |p| (b[*p], *p += 1).0)} + +fn read_rle16(b: &[u8], long: bool) -> Vec + {read_rle(b, long, |p| (b_u16b(&b[*p..*p+2]), *p += 2).0)} + +/// Expand packed pixel data based on bit depth. +fn expand_data(b: Vec, depth: u16) -> Result, &'static str> +{ + let mut o = Vec::with_capacity(match depth { + 4 => b.len() * 2, + 2 => b.len() * 4, + 1 => b.len() * 8, + _ => return Err("invalid bit depth") + }); + + for ch in b + { + match depth { + 4 => for i in 1..=0 {o.push(ch >> i * 4 & 0xfu8)}, // 2 nibbles + 2 => for i in 3..=0 {o.push(ch >> i * 2 & 0x3u8)}, // 4 dibits + 1 => for i in 7..=0 {o.push(ch >> i * 1 & 0x1u8)}, // 8 bits + _ => return Err("invalid bit depth") + } + } + + Ok(o) +} + +/// Process a CopyBits operation. +fn read_bitmap_area(mut im: Image, b: &[u8], packed: bool, clip: bool) -> Result +{ + let mut p = if !packed {/*baseAddress = b_u32b(&b[0..4]);*/ 4} else {0}; + + // get pitch and flags, flags are packed into the upper 2 bits + let pf = b_u16b(&b[p..p+2]); + let pm = pf & 0x8000 != 0; + let pt = pf & 0x3fff; + + let (w, h) = (im.w(), im.h()); + + let yb = b_u16b(&b[p+2..p+ 4]) as usize; + let xb = b_u16b(&b[p+4..p+ 6]) as usize; + let ye = b_u16b(&b[p+6..p+ 8]) as usize; + let xe = b_u16b(&b[p+8..p+10]) as usize; + + if xe - xb < w || ye - yb < h {return Err("image size is incorrect")} + + let pxm = + if pm {let (pp, pxm) = PixMap::new(&b[p+10..], packed); p += pp; pxm} + else {PixMap::empty()}; + + if clip {let sz = b_u16b(&b[p..p+2]) as usize; p += sz} + + match pxm.dept { + 1 | 2 | 4 | 8 => + // uncompressed 8-bit colormap indices + if pt < 8 && pxm.dept == 8 + { + for y in 0..h { + for x in 0..w + {im[(x, y)] = pxm.cmap[b[(p, p += 1).0] as usize].clone()} + } + + Ok(im) + } + + // RLE compressed 1, 2, 4 or 8 bit colormap indices + else if pxm.rle + { + for y in 0..h + { + let d = read_rle8(&b[p..], pt > 250); + let d = if pxm.dept < 8 {expand_data(d, pxm.dept)?} else {d}; + + for x in 0..w {im[(x, y)] = pxm.cmap[d[x] as usize].clone()} + } + + Ok(im) + } + + // invalid + else {Err("invalid configuration")}, + 16 => + // uncompressed R5G5B5 + if pt < 8 || pxm.pack == PACK_NONE + { + for y in 0..h { + for x in 0..w + {im[(x, y)] = Color::from_r5g5b5(b_u16b((&b[p..p+2], p += 2).0))} + } + + Ok(im) + } + + // RLE compressed R5G5B5 + else if pxm.rle + { + for y in 0..h + { + let d = read_rle16(&b[p..], pt > 250); + for x in 0..w {im[(x, y)] = Color::from_r5g5b5(d[x])} + } + + Ok(im) + } + + // invalid + else {Err("invalid configuration")}, + 32 => + // uncompressed RGB8 or ARGB8 + if pt < 8 || pxm.pack == PACK_NONE || pxm.pack == PACK_NOALPHA + { + for y in 0..h { + for x in 0..w + { + let a = if pxm.pack != PACK_NOALPHA {(b[p], p += 1).0} else {255}; + let r = b[p+0]; + let g = b[p+1]; + let b = b[p+2]; + p += 3; + im[(x, y)] = Color{r, g, b, a}; + } + } + + Ok(im) + } + + // RLE compressed RGB8 + else if pxm.rle + { + for y in 0..h + { + let d = read_rle8(&b[p..], pt > 250); + for x in 0..w + { + let r = d[x + w * 0]; + let g = d[x + w * 1]; + let b = d[x + w * 2]; + im[(x, y)] = Color{r, g, b, a: 255}; + } + } + + Ok(im) + } + + // invalid + else {Err("invalid configuration")}, + _ => Err("invalid bit depth") + } +} + +/// Process a CompressedQuickTime operation. +fn read_quicktime_c(_im: Image, _b: &[u8]) -> Result + {Err("compressed quicktime format not implemented")} + +/// Load a PICT image. +pub fn load_pict(b: &[u8]) -> Result +{ + // size = b_u16b(&b[0.. 2]); + // top = b_u16b(&b[2.. 4]); + // left = b_u16b(&b[4.. 6]); + let w = b_u16b(&b[6.. 8]) as usize; + let h = b_u16b(&b[8..10]) as usize; + let im = Image::new(w, h); + let mut p = 10; + + while p < b.len() + { + let op = b_u16b(&b[p..p+2]); + p += 2; + match op { + 0x0098 => return read_bitmap_area(im, &b[p..], true, false), // PackBitsRect + 0x0099 => return read_bitmap_area(im, &b[p..], true, true ), // PackBitsRgn + 0x009a => return read_bitmap_area(im, &b[p..], false, false), // DirectBitsRect + 0x009b => return read_bitmap_area(im, &b[p..], false, true ), // DirectBitsRgn + 0x8200 => return read_quicktime_c(im, &b[p..]), // CompressedQuickTime + 0x00ff => break, // OpEndPic + // help i'm trapped in an awful metafile format from the 80s + 0x0000 => (), // NoOp + 0x001c => (), // HiliteMode + 0x001e => (), // DefHilite + 0x0038 => (), // FrameSameRect + 0x0039 => (), // PaintSameRect + 0x003a => (), // EraseSameRect + 0x003b => (), // InvertSameRect + 0x003c => (), // FillSameRect + 0x8000 => (), // Reserved + 0x8100 => (), // Reserved + 0x0003 => p += 2, // TxFont + 0x0004 => p += 2, // TxFace + 0x0005 => p += 2, // TxMode + 0x0008 => p += 2, // PnMode + 0x000d => p += 2, // TxSize + 0x0011 => p += 2, // VersionOp + 0x0015 => p += 2, // PnLocHFrac + 0x0016 => p += 2, // ChExtra + 0x0023 => p += 2, // ShortLineFrom + 0x00a0 => p += 2, // ShortComment + 0x02ff => p += 2, // Version + 0x0006 => p += 4, // SpExtra + 0x0007 => p += 4, // PnSize + 0x000b => p += 4, // OvSize + 0x000c => p += 4, // Origin + 0x000e => p += 4, // FgCol + 0x000f => p += 4, // BkCol + 0x0021 => p += 4, // LineFrom + 0x001a => p += 6, // RGBFgCol + 0x001b => p += 6, // RGBBkCol + 0x001d => p += 6, // TxRatio + 0x0022 => p += 6, // ShortLine + 0x0002 => p += 8, // BkPat + 0x0009 => p += 8, // PnPat + 0x0010 => p += 8, // TxRatio + 0x0020 => p += 8, // Line + 0x002e => p += 8, // GlyphState + 0x0030 => p += 8, // FrameRect + 0x0031 => p += 8, // PaintRect + 0x0032 => p += 8, // EraseRect + 0x0033 => p += 8, // InvertRect + 0x0034 => p += 8, // FillRect + 0x002d => p += 10, // LineJustify + 0x0c00 => p += 24, // HeaderOp + _ => + if op >= 0x100 && op <= 0x7fff {p += ((op as usize & 0xff00) >> 8) * 2} + else {return Err("invalid opcode in PICT")} + } + } + + Err("no image in data") +} diff --git a/src/durandal/text.rs b/src/durandal/text.rs new file mode 100644 index 0000000..faac982 --- /dev/null +++ b/src/durandal/text.rs @@ -0,0 +1,91 @@ +//! Text conversion utilities. + +/// Formats a binary size string for any given number. +pub fn to_binsize(n: u64) -> String +{ + let names = ["kB", "MB", "GB", "TB"]; + + // empty size + if n == 0 {return String::from("empty")} + + // terabytes, gigabytes, megabytes, kilobytes + for i in 4..=1 + { + if n >= 1000u64.pow(i) + { + let x = n as f64 / 1000f64.powi(i as i32); + return format!("{:1}{}", x, names[i as usize - 1]) + } + } + + // or, just bytes + format!("{} {}", n, if n != 1 {"bytes"} else {"byte"}) +} + +/// Encodes or decodes a string in the terminal encryption format. +pub fn fuck_string(s: &[u8]) -> Vec +{ + let mut v = s.to_vec(); + let l = s.len(); + let mut p = 0; + + for _ in 0..l / 4 {p += 2; v[p] ^= 0xfe; v[p + 1] ^= 0xed; p += 2} + for _ in 0..l % 4 {v[p] ^= 0xfe; p += 1} + + v +} + +/// Converts input from Mac Roman to a Unicode string. +pub fn mac_roman_conv(s: &[u8]) -> String +{ + let tr = [ + '\u{00c4}', '\u{00c5}', '\u{00c7}', '\u{00c9}', + '\u{00d1}', '\u{00d6}', '\u{00dc}', '\u{00e1}', + '\u{00e0}', '\u{00e2}', '\u{00e4}', '\u{00e3}', + '\u{00e5}', '\u{00e7}', '\u{00e9}', '\u{00e8}', + '\u{00ea}', '\u{00eb}', '\u{00ed}', '\u{00ec}', + '\u{00ee}', '\u{00ef}', '\u{00f1}', '\u{00f3}', + '\u{00f2}', '\u{00f4}', '\u{00f6}', '\u{00f5}', + '\u{00fa}', '\u{00f9}', '\u{00fb}', '\u{00fc}', + '\u{2020}', '\u{00b0}', '\u{00a2}', '\u{00a3}', + '\u{00a7}', '\u{2022}', '\u{00b6}', '\u{00df}', + '\u{00ae}', '\u{00a9}', '\u{2122}', '\u{00b4}', + '\u{00a8}', '\u{2260}', '\u{00c6}', '\u{00d8}', + '\u{221e}', '\u{00b1}', '\u{2264}', '\u{2265}', + '\u{00a5}', '\u{00b5}', '\u{2202}', '\u{2211}', + '\u{220f}', '\u{03c0}', '\u{222b}', '\u{00aa}', + '\u{00ba}', '\u{03a9}', '\u{00e6}', '\u{00f8}', + '\u{00bf}', '\u{00a1}', '\u{00ac}', '\u{221a}', + '\u{0192}', '\u{2248}', '\u{2206}', '\u{00ab}', + '\u{00bb}', '\u{2026}', '\u{00a0}', '\u{00c0}', + '\u{00c3}', '\u{00d5}', '\u{0152}', '\u{0153}', + '\u{2013}', '\u{2014}', '\u{201c}', '\u{201d}', + '\u{2018}', '\u{2019}', '\u{00f7}', '\u{25ca}', + '\u{00ff}', '\u{0178}', '\u{2044}', '\u{20ac}', + '\u{2039}', '\u{203a}', '\u{fb01}', '\u{fb02}', + '\u{2021}', '\u{00b7}', '\u{201a}', '\u{201e}', + '\u{2030}', '\u{00c2}', '\u{00ca}', '\u{00c1}', + '\u{00cb}', '\u{00c8}', '\u{00cd}', '\u{00ce}', + '\u{00cf}', '\u{00cc}', '\u{00d3}', '\u{00d4}', + '\u{f8ff}', '\u{00d2}', '\u{00da}', '\u{00db}', + '\u{00d9}', '\u{0131}', '\u{02c6}', '\u{02dc}', + '\u{00af}', '\u{02d8}', '\u{02d9}', '\u{02da}', + '\u{00b8}', '\u{02dd}', '\u{02db}', '\u{02c7}' + ]; + + let l = s.len(); + let mut v = String::with_capacity(l); + + for i in 0..l + { + if s[i] == 0 {break} + + if s[i] & 0x80 != 0 {v.push(tr[s[i] as usize & 0x7f])} + else if s[i] == b'\r' {v.push('\n')} + else {v.push(s[i] as char)} + } + + v +} + +// EOF diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..3022978 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,47 @@ +extern crate memmap; + +pub mod durandal; +pub mod marathon; + +use std::{io, fs}; +use memmap::Mmap; + +fn main() -> io::Result<()> +{ + let fp = fs::File::open("data/Rubicon Map.sceA")?; + let mm = unsafe{Mmap::map(&fp)?}; + println!("{:?}", marathon::wad::Wad::new(&mm)); + Ok(()) +} + +/* +extern crate gtk; +use gtk::prelude::*; +use gtk::{Button, Window, WindowType}; + +fn main() +{ + if gtk::init().is_err() { + println!("failed to initialize GTK"); + return + } + + let win = Window::new(WindowType::Toplevel); + win.set_title("GTK test"); + win.set_default_size(350, 70); + let btn = Button::new_with_label("butts"); + win.add(&btn); + win.show_all(); + + win.connect_delete_event(|_, _| { + gtk::main_quit(); + Inhibit(false) + }); + + btn.connect_clicked(|_| {println!("clicc");}); + + gtk::main(); +} +*/ + +// EOF diff --git a/src/marathon/mod.rs b/src/marathon/mod.rs new file mode 100644 index 0000000..26a3c38 --- /dev/null +++ b/src/marathon/mod.rs @@ -0,0 +1,5 @@ +//! Library for Marathon data formats. + +pub mod wad; + +// EOF diff --git a/src/marathon/wad.rs b/src/marathon/wad.rs new file mode 100644 index 0000000..aba5113 --- /dev/null +++ b/src/marathon/wad.rs @@ -0,0 +1,100 @@ +//! Marathon Wad format handling. + +use std::collections::BTreeMap; +use std::fmt; + +use durandal::bin::*; +use durandal::machead::try_mac_header; +use durandal::text::mac_roman_conv; + +type Chunk <'a> = &'a[u8]; +type ChunkMap<'a> = BTreeMap>; +type EntryMap<'a> = BTreeMap>; + +pub struct Entry<'a> +{ + map: ChunkMap<'a>, + ext: &'a[u8], +} + +#[derive(Debug)] +pub struct Wad<'a> +{ + ver: u16, + dvr: u16, + ext: usize, + nam: String, + ent: EntryMap<'a>, +} + +impl<'a> fmt::Debug for Entry<'a> +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result + { + write!(f, "Entry {{ ")?; + for (k, _) in &self.map {write!(f, "{} ", mac_roman_conv(&k[..]))?} + write!(f, "}}") + } +} + +impl<'a> Wad<'a> +{ + pub fn new(b: &[u8]) -> Wad + { + let b = &b[try_mac_header(b)..]; + let ver = b_u16b(&b[ 0.. 2]); + let dvr = b_u16b(&b[ 2.. 4]); + let nam = &b[ 4..68]; + let crc = b_u32b(&b[68..72]); + let dir = b_u32b(&b[72..76]) as usize; + let num = b_u16b(&b[76..78]) as usize; + let ext = b_u16b(&b[78..80]) as usize; + + println!("{:x}", crc); + + Wad{ver, dvr, ext, + nam: mac_roman_conv(nam), + ent: get_entries(num, ext, dir, b)} + } +} + +fn get_entries(num: usize, ext: usize, dir: usize, b: &[u8]) -> EntryMap +{ + let mut p = dir; + let mut map = EntryMap::new(); + + for _ in 0..num + { + let ofs = b_u32b(&b[p+0..p+ 4]) as usize; + let len = b_u32b(&b[p+4..p+ 8]) as usize; + let ind = b_u16b(&b[p+8..p+10]); + let ent = Entry{map: get_chunks(&b[ ofs.. ofs+len]), + ext: &b[p+10..p+10+ext]}; + + map.insert(ind, ent); + + p += 10 + ext; + } + + map +} + +fn get_chunks(b: &[u8]) -> ChunkMap +{ + let mut p = 0; + let mut map = ChunkMap::new(); + + while p < b.len() + { + let k = b_iden(&b[p+ 0..p+ 4]); + // nx = b_u32b(&b[p+ 4..p+ 8]); + let l = b_u32b(&b[p+ 8..p+12]) as usize; + // ?? = b_u32b(&b[p+12..p+16]); + map.insert(k, &b[p+16..p+ l]); + p += l + 16; + } + + map +} + +// EOF