extra sanity checks and tests
parent
6e3fff0080
commit
f3e98c0c05
|
@ -299,6 +299,38 @@ pub fn rd_array<T, F>(b: &[u8], read: F) -> ResultS<Vec<T>>
|
|||
Ok(v)
|
||||
}
|
||||
|
||||
/// Applies a read function a number of times over a slice.
|
||||
///
|
||||
/// Applies `read` over `b`, resulting in a vector of its return values. Each
|
||||
/// iteration will pass a slice of `b` to `read` for it to read from, and then
|
||||
/// increments the slice index by the second return value. When `n` elements
|
||||
/// have been read, the function returns.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// A panic will occur if the `read` function returns a disjoint index or
|
||||
/// otherwise panics (by an out of bounds index to `b` or otherwise.)
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Execution will return the result of `read` if `read` returns an error.
|
||||
pub fn rd_array_num<T, F>(b: &[u8], n: usize, read: F)
|
||||
-> ResultS<(Vec<T>, usize)>
|
||||
where T: Sized,
|
||||
F: Fn(&[u8]) -> ResultS<(T, usize)>
|
||||
{
|
||||
let mut v = Vec::with_capacity(n);
|
||||
let mut p = 0;
|
||||
|
||||
for _ in 0..n {
|
||||
let (r, s) = read(&b[p..])?;
|
||||
v.push(r);
|
||||
p += s;
|
||||
}
|
||||
|
||||
Ok((v, p))
|
||||
}
|
||||
|
||||
/// Applies a read function over a slice with an offset table.
|
||||
///
|
||||
/// Applies `read` over each offset in `b`, of which there are `num` amount of
|
||||
|
|
|
@ -207,6 +207,10 @@ pub fn load_pict(b: &[u8]) -> ResultS<Image8>
|
|||
w = u16[8] usize;
|
||||
}
|
||||
|
||||
if w * h > 16_000_000 {
|
||||
bail!("image is too large");
|
||||
}
|
||||
|
||||
let im = Image8::new(w, h);
|
||||
|
||||
let mut p = 10; // size of header
|
||||
|
@ -425,7 +429,7 @@ impl ReadRleData for u8
|
|||
}
|
||||
|
||||
/// Expand packed pixel data based on bit depth.
|
||||
pub fn expand_data(b: Vec<u8>, depth: u16) -> ResultS<Vec<u8>>
|
||||
fn expand_data(b: Vec<u8>, depth: u16) -> ResultS<Vec<u8>>
|
||||
{
|
||||
let mut o = Vec::with_capacity(match depth {
|
||||
4 => b.len() * 2,
|
||||
|
|
|
@ -23,12 +23,12 @@ fn read_color(b: &[u8], clut: &mut [ColorShp]) -> ResultS<()>
|
|||
}?;
|
||||
|
||||
let cr = ColorShp::Opaque{r, g, b, l};
|
||||
clut[usize::from(i)] = cr;
|
||||
*ok!(clut.get_mut(usize::from(i)), "bad index")? = cr;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads all color tables.
|
||||
pub fn color_tables(b: &[u8],
|
||||
fn color_tables(b: &[u8],
|
||||
tab_ofs: usize,
|
||||
tab_num: usize,
|
||||
clr_num: usize)
|
||||
|
@ -36,14 +36,14 @@ pub fn color_tables(b: &[u8],
|
|||
{
|
||||
let end = tab_num * clr_num * 8;
|
||||
|
||||
let b = &b[tab_ofs..tab_ofs + end];
|
||||
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(&b[p..p + 8], clut)?;
|
||||
read_color(ok!(b.get(p..p + 8), "not enough data")?, clut)?;
|
||||
p += 8;
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ pub fn read_bitmap(b: &[u8]) -> ResultS<Bitmap>
|
|||
bmp.cr.push(0);
|
||||
}
|
||||
|
||||
bmp.cr.extend_from_slice(&b[p..p + end]);
|
||||
bmp.cr.extend_from_slice(ok!(b.get(p..p + end), "not enough data")?);
|
||||
|
||||
for _ in lst..pitch {
|
||||
bmp.cr.push(0);
|
||||
|
@ -112,7 +112,8 @@ pub fn read_bitmap(b: &[u8]) -> ResultS<Bitmap>
|
|||
bail!("invalid scanline");
|
||||
}
|
||||
|
||||
bmp.cr.extend_from_slice(&b[p..p + width * height]);
|
||||
bmp.cr.extend_from_slice(ok!(b.get(p..p + width * height),
|
||||
"not enough data")?);
|
||||
}
|
||||
|
||||
Ok(bmp)
|
||||
|
@ -215,13 +216,15 @@ pub fn read_shapes(b: &[u8]) -> ResultS<Vec<CollectionDef>>
|
|||
let c_lo = if lo_ofs == usize_from_u32(u32::max_value()) {
|
||||
None
|
||||
} else {
|
||||
Some(read_collection(&b[lo_ofs..lo_ofs + lo_len])?)
|
||||
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 {
|
||||
Some(read_collection(&b[hi_ofs..hi_ofs + hi_len])?)
|
||||
let dat = ok!(b.get(hi_ofs..hi_ofs + hi_len), "bad offset")?;
|
||||
Some(read_collection(dat)?)
|
||||
};
|
||||
|
||||
cl.push((c_lo, c_hi));
|
||||
|
|
|
@ -1,23 +1,21 @@
|
|||
//! Structures used by Marathon's Map format's terminal definitions.
|
||||
|
||||
use crate::durandal::{err::*, text::*};
|
||||
use crate::durandal::{bin::*, err::*, text::*};
|
||||
use bitflags::bitflags;
|
||||
|
||||
/// Reads a `Group`.
|
||||
pub fn read_group(b: &[u8], text: &[u8]) -> ResultS<Group>
|
||||
/// Reads an `InterGroup`.
|
||||
pub fn read_group(b: &[u8]) -> ResultS<(InterGroup, usize)>
|
||||
{
|
||||
read_data! {
|
||||
12, BE in b =>
|
||||
flags = u16[0];
|
||||
ttype = u16[2];
|
||||
pdata = u16[4];
|
||||
start = u16[6] usize;
|
||||
size = u16[8] usize;
|
||||
beg = u16[6] usize;
|
||||
len = u16[8] usize;
|
||||
lines = u16[10];
|
||||
}
|
||||
|
||||
let text = ok!(text.get(start..start + size), "not enough data")?;
|
||||
let text = mac_roman_cstr(text)?;
|
||||
let flags = flag_ok!(GroupFlags, flags)?;
|
||||
let ttype = match ttype {
|
||||
0 => GroupType::Logon(pdata),
|
||||
|
@ -40,11 +38,11 @@ pub fn read_group(b: &[u8], text: &[u8]) -> ResultS<Group>
|
|||
n => return Err(ReprError::new(n).into()),
|
||||
};
|
||||
|
||||
Ok(Group{flags, ttype, lines, text})
|
||||
Ok((InterGroup{flags, ttype, lines, beg, len}, 12))
|
||||
}
|
||||
|
||||
/// Reads a `Face`.
|
||||
pub fn read_face(b: &[u8]) -> ResultS<Face>
|
||||
pub fn read_face(b: &[u8]) -> ResultS<(Face, usize)>
|
||||
{
|
||||
read_data! {
|
||||
6, BE in b =>
|
||||
|
@ -53,15 +51,12 @@ pub fn read_face(b: &[u8]) -> ResultS<Face>
|
|||
color = u16[4];
|
||||
}
|
||||
|
||||
Ok(Face{start, face, color})
|
||||
Ok((Face{start, face, color}, 6))
|
||||
}
|
||||
|
||||
/// Reads a `term` chunk.
|
||||
pub fn read_term(b: &[u8]) -> ResultS<(Terminal, usize)>
|
||||
{
|
||||
const SIZE_GROUP: usize = 12;
|
||||
const SIZE_FACE: usize = 6;
|
||||
|
||||
read_data! {
|
||||
10, BE in b =>
|
||||
end = u16[0] usize;
|
||||
|
@ -73,32 +68,34 @@ pub fn read_term(b: &[u8]) -> ResultS<(Terminal, usize)>
|
|||
|
||||
let encoded = encoded != 0;
|
||||
|
||||
let (i_grp, x) = rd_array_num(&b[10..], group_n, read_group)?;
|
||||
let (faces, y) = rd_array_num(&b[10 + x..], face_n, read_face)?;
|
||||
|
||||
let text = ok!(b.get(10 + x + y..end), "not enough data")?;
|
||||
let text = if encoded {fuck_string(text)} else {text.to_vec()};
|
||||
|
||||
let mut groups = Vec::with_capacity(group_n);
|
||||
let mut faces = Vec::with_capacity(face_n);
|
||||
|
||||
let mut p = 10;
|
||||
for grp in &i_grp {
|
||||
let flags = grp.flags;
|
||||
let ttype = grp.ttype;
|
||||
let lines = grp.lines;
|
||||
let beg = grp.beg;
|
||||
let len = grp.len;
|
||||
let text = ok!(text.get(beg..beg + len), "bad offset")?;
|
||||
let text = mac_roman_cstr(text)?;
|
||||
|
||||
let text_st = p + SIZE_GROUP * group_n + SIZE_FACE * face_n;
|
||||
let text = ok!(b.get(text_st..end), "bad offset")?;
|
||||
let text = if encoded {
|
||||
fuck_string(text)
|
||||
} else {
|
||||
text.to_vec()
|
||||
};
|
||||
|
||||
for _ in 0..group_n {
|
||||
groups.push(read_group(ok!(b.get(p..), "not enough data")?, &text)?);
|
||||
p += SIZE_GROUP;
|
||||
}
|
||||
|
||||
for _ in 0..face_n {
|
||||
faces.push(read_face(ok!(b.get(p..), "not enough data")?)?);
|
||||
p += SIZE_FACE;
|
||||
groups.push(Group{flags, ttype, lines, text});
|
||||
}
|
||||
|
||||
Ok((Terminal{lines, groups, faces}, end))
|
||||
}
|
||||
|
||||
impl Default for GroupType
|
||||
{
|
||||
fn default() -> Self {GroupType::Unfinished}
|
||||
}
|
||||
|
||||
/// A terminal definition, with collections of groups and faces.
|
||||
#[derive(Debug, PartialEq, serde::Serialize)]
|
||||
pub struct Terminal
|
||||
|
@ -127,8 +124,19 @@ pub struct Group
|
|||
pub text: String,
|
||||
}
|
||||
|
||||
/// Interim structure.
|
||||
#[derive(Debug)]
|
||||
pub struct InterGroup
|
||||
{
|
||||
flags: GroupFlags,
|
||||
ttype: GroupType,
|
||||
lines: u16,
|
||||
beg: usize,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
/// The command of a `Group`.
|
||||
#[derive(Debug, PartialEq, serde::Serialize)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize)]
|
||||
pub enum GroupType
|
||||
{
|
||||
Logon(u16),
|
||||
|
@ -152,7 +160,7 @@ pub enum GroupType
|
|||
|
||||
bitflags! {
|
||||
/// Flags for `Group`.
|
||||
#[derive(serde::Serialize)]
|
||||
#[derive(Default, serde::Serialize)]
|
||||
pub struct GroupFlags: u16
|
||||
{
|
||||
const DrawOnRight = 1;
|
||||
|
|
34
tests/map.rs
34
tests/map.rs
|
@ -1,39 +1,7 @@
|
|||
use maraiah::{durandal::{bin, fixed::*}, marathon::{map, trm}};
|
||||
use maraiah::{durandal::{bin, fixed::*}, marathon::map};
|
||||
|
||||
include!("data/rand.rs");
|
||||
|
||||
#[test]
|
||||
fn read_term_must_process()
|
||||
{
|
||||
const INPUT: &[u8] = include_bytes!("data/term.in");
|
||||
|
||||
let inp = bin::rd_array(INPUT, trm::read_term).unwrap();
|
||||
let out = include!("data/term.out");
|
||||
|
||||
// for better debug output, we iterate over each item
|
||||
assert_eq!(inp.len(), out.len());
|
||||
|
||||
for (itrm, otrm) in inp.iter().zip(&out) {
|
||||
assert_eq!(itrm.groups.len(), otrm.groups.len());
|
||||
for (igrp, ogrp) in itrm.groups.iter().zip(&otrm.groups) {
|
||||
assert_eq!(igrp, ogrp);
|
||||
}
|
||||
|
||||
assert_eq!(itrm.faces.len(), otrm.faces.len());
|
||||
for (ifac, ofac) in itrm.faces.iter().zip(&otrm.faces) {
|
||||
assert_eq!(ifac, ofac);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trm_must_not_process()
|
||||
{
|
||||
for inp in &RANDOM {
|
||||
assert!(bin::rd_array(inp, trm::read_term).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_minf_must_process()
|
||||
{
|
||||
|
|
|
@ -1,24 +1,7 @@
|
|||
use maraiah::{durandal::image::Color8, marathon::{machdr, pict}};
|
||||
use maraiah::marathon::machdr;
|
||||
|
||||
include!("data/rand.rs");
|
||||
|
||||
#[test]
|
||||
fn get_clut_must_process()
|
||||
{
|
||||
const INPUT: &[u8] = include_bytes!("data/clut.in");
|
||||
const OUTPUT: [Color8; 256] = include!("data/clut.out");
|
||||
|
||||
assert_eq!(pict::get_clut(INPUT).unwrap(), (OUTPUT.to_vec(), 2056));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_clut_must_not_process()
|
||||
{
|
||||
for inp in &RANDOM {
|
||||
assert!(pict::get_clut(inp).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn machdr_must_process()
|
||||
{
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
use maraiah::{durandal::bin, marathon::phy};
|
||||
|
||||
include!("data/rand.rs");
|
||||
|
||||
#[test]
|
||||
fn phy_must_not_process()
|
||||
{
|
||||
// these functions must not succeed
|
||||
for inp in &RANDOM {
|
||||
assert!(bin::rd_array(inp, phy::read_fxpx).is_err());
|
||||
assert!(bin::rd_array(inp, phy::read_mnpx).is_err());
|
||||
assert!(bin::rd_array(inp, phy::read_prpx).is_err());
|
||||
assert!(bin::rd_array(inp, phy::read_wppx).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(unused_must_use)]
|
||||
fn phy_wont_panic()
|
||||
{
|
||||
// these functions can succeed but must never panic
|
||||
for inp in &RANDOM {
|
||||
bin::rd_array(inp, phy::read_pxpx);
|
||||
}
|
||||
}
|
||||
|
||||
// EOF
|
|
@ -0,0 +1,23 @@
|
|||
use maraiah::{durandal::image::Color8, marathon::pict};
|
||||
|
||||
include!("data/rand.rs");
|
||||
|
||||
#[test]
|
||||
fn get_clut_must_process()
|
||||
{
|
||||
const INPUT: &[u8] = include_bytes!("data/clut.in");
|
||||
const OUTPUT: [Color8; 256] = include!("data/clut.out");
|
||||
|
||||
assert_eq!(pict::get_clut(INPUT).unwrap(), (OUTPUT.to_vec(), 2056));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pict_must_not_process()
|
||||
{
|
||||
for inp in &RANDOM {
|
||||
assert!(pict::get_clut(inp).is_err());
|
||||
assert!(pict::load_pict(inp).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
// EOF
|
|
@ -0,0 +1,17 @@
|
|||
use maraiah::marathon::shp;
|
||||
|
||||
include!("data/rand.rs");
|
||||
|
||||
#[test]
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
// EOF
|
|
@ -0,0 +1,15 @@
|
|||
use maraiah::marathon::snd;
|
||||
|
||||
include!("data/rand.rs");
|
||||
|
||||
#[test]
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
// EOF
|
|
@ -0,0 +1,47 @@
|
|||
use maraiah::{durandal::bin, marathon::trm};
|
||||
|
||||
include!("data/rand.rs");
|
||||
|
||||
#[test]
|
||||
fn read_term_must_process()
|
||||
{
|
||||
const INPUT: &[u8] = include_bytes!("data/term.in");
|
||||
|
||||
let inp = bin::rd_array(INPUT, trm::read_term).unwrap();
|
||||
let out = include!("data/term.out");
|
||||
|
||||
// for better debug output, we iterate over each item
|
||||
assert_eq!(inp.len(), out.len());
|
||||
|
||||
for (itrm, otrm) in inp.iter().zip(&out) {
|
||||
assert_eq!(itrm.groups.len(), otrm.groups.len());
|
||||
for (igrp, ogrp) in itrm.groups.iter().zip(&otrm.groups) {
|
||||
assert_eq!(igrp, ogrp);
|
||||
}
|
||||
|
||||
assert_eq!(itrm.faces.len(), otrm.faces.len());
|
||||
for (ifac, ofac) in itrm.faces.iter().zip(&otrm.faces) {
|
||||
assert_eq!(ifac, ofac);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trm_must_not_process()
|
||||
{
|
||||
for inp in &RANDOM {
|
||||
assert!(bin::rd_array(inp, trm::read_group).is_err());
|
||||
assert!(bin::rd_array(inp, trm::read_term).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(unused_must_use)]
|
||||
fn trm_wont_panic()
|
||||
{
|
||||
for inp in &RANDOM {
|
||||
bin::rd_array(inp, trm::read_face);
|
||||
}
|
||||
}
|
||||
|
||||
// EOF
|
Loading…
Reference in New Issue