Add initial test code

png-branch
an 2018-09-06 12:01:52 -04:00
commit f5984c9739
11 changed files with 737 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/target
**/*.rs.bk
Cargo.lock
perf.data*

8
Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "maraiah"
version = "0.1.0"
authors = ["marrub"]
[dependencies]
memmap = "0.6"
#gtk = "0.4"

24
src/durandal/bin.rs Normal file
View File

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

59
src/durandal/image.rs Normal file
View File

@ -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<Color>,
}
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

53
src/durandal/machead.rs Normal file
View File

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

10
src/durandal/mod.rs Normal file
View File

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

336
src/durandal/pict.rs Normal file
View File

@ -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<Color>,
}
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<T, F>(b: &[u8], long: bool, rd: F) -> Vec<T>
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<u8>
{read_rle(b, long, |p| (b[*p], *p += 1).0)}
fn read_rle16(b: &[u8], long: bool) -> Vec<u16>
{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<u8>, depth: u16) -> Result<Vec<u8>, &'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<Image, &str>
{
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<Image, &str>
{Err("compressed quicktime format not implemented")}
/// Load a PICT image.
pub fn load_pict(b: &[u8]) -> Result<Image, &str>
{
// 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")
}

91
src/durandal/text.rs Normal file
View File

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

47
src/main.rs Normal file
View File

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

5
src/marathon/mod.rs Normal file
View File

@ -0,0 +1,5 @@
//! Library for Marathon data formats.
pub mod wad;
// EOF

100
src/marathon/wad.rs Normal file
View File

@ -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<Ident, Chunk<'a>>;
type EntryMap<'a> = BTreeMap<u16 , Entry<'a>>;
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