Compare commits

...

7 Commits

Author SHA1 Message Date
an 1d6aa0c14e doc everything 2019-03-04 21:14:09 -05:00
an 6e4c7512e8 rewrite fixed point numbers to work better 2019-03-04 13:45:03 -05:00
an 403127b7a6 make polygon loaders better 2019-03-04 10:27:18 -05:00
an 2d18d295b8 reorganize tests 2019-03-04 09:10:38 -05:00
an ff10f3ca53 better terminal group handling 2019-03-04 09:00:03 -05:00
an 7334ff6bd5 errors 2019-03-04 08:59:43 -05:00
an 132e964f8c old minf loader 2019-03-04 07:48:57 -05:00
24 changed files with 978 additions and 413 deletions

View File

@ -16,7 +16,7 @@ members = ["source/leela", "source/tycho"]
[dependencies]
bitflags = "1.0"
failure = "0.1"
failure = {version = "0.1", features = ["std"]}
serde = {version = "1.0", features = ["derive"]}
[profile.dev]

View File

@ -337,7 +337,8 @@ Example:
#SOUND sound_number
```
`#SOUND` plays the specified sound from the Sounds file or from the Scenario.
`#SOUND` plays the specified sound from the Sounds file or from the scenario
and then goes to the next level.
Example:
@ -1914,7 +1915,7 @@ Used to determine how the engine loads map data.
| Name | Value | Permutation |
| ---- | ----- | ----------- |
| `Logon` | `0` | None |
| `Logon` | `0` | Pict ID |
| `Unfinished` | `1` | None |
| `Success` | `2` | None |
| `Failure` | `3` | None |
@ -1927,7 +1928,7 @@ Used to determine how the engine loads map data.
| `Movie` | `10` | Movie ID |
| `Track` | `11` | Track ID |
| `Pict` | `12` | Pict ID |
| `Logoff` | `13` | None |
| `Logoff` | `13` | Pict ID |
| `Camera` | `14` | Object ID |
| `Static` | `15` | 1/30 secs |
| `Tag` | `16` | Tag number |

View File

@ -22,16 +22,16 @@ macro_rules! _durandal_read_impl {
// little endian
(LE $b:ident $nam:ident u16 $n:expr) => {
_durandal_read_impl!($b u16::from_le_bytes $nam 2 $n);
_durandal_read_impl!($b u16::from_le_bytes, $nam 2 $n);
};
(LE $b:ident $nam:ident i16 $n:expr) => {
_durandal_read_impl!($b i16::from_le_bytes $nam 2 $n);
_durandal_read_impl!($b i16::from_le_bytes, $nam 2 $n);
};
(LE $b:ident $nam:ident u32 $n:expr) => {
_durandal_read_impl!($b u32::from_le_bytes $nam 4 $n);
_durandal_read_impl!($b u32::from_le_bytes, $nam 4 $n);
};
(LE $b:ident $nam:ident i32 $n:expr) => {
_durandal_read_impl!($b i32::from_le_bytes $nam 4 $n);
_durandal_read_impl!($b i32::from_le_bytes, $nam 4 $n);
};
// either endianness
@ -87,6 +87,8 @@ macro_rules! _durandal_read_impl {
/// Reads structured data from a byte slice.
///
/// # Syntax
///
/// First start by specifying the endianness, size and source using the syntax
/// `endian, size in source =>` where:
///
@ -126,14 +128,41 @@ macro_rules! _durandal_read_impl {
/// - `place` is either an integer literal which must be representable as
/// `usize`, or a range, which may only be used when `type` is a function
/// name.
#[macro_export]
///
/// # Panics
///
/// This macro will not panic unless any index expression used exceeds `size`.
///
/// # Examples
///
/// ```
/// # #[macro_use] extern crate maraiah;
/// # use maraiah::durandal::err::*;
/// # fn main() -> ResultS<()>
/// # {
/// let buffer = &[4, 0, 2, 0, 0, 0, 6];
///
/// read_data! {
/// 7, LE in buffer =>
/// four = u16[0];
/// two = u32[2];
/// six = u8 [6];
/// }
///
/// assert_eq!(four, 4_u16);
/// assert_eq!(two, 2_u32);
/// assert_eq!(six, 6_u8);
/// # Ok(())
/// # }
/// ```
#[macro_export(local_inner_macros)]
macro_rules! read_data {
(
$sz:expr , $ty:ident in $b:ident =>
$( $nam:ident = $t:ident [ $n:expr ] $( $ex:ident )* ; )*
) => {
if $b.len() < $sz {
bail!("not enough data");
return Err(err_msg("not enough data"));
}
$($crate::_durandal_read_impl!($ty $b $nam $($ex)* $t $n);)*
@ -141,32 +170,91 @@ macro_rules! read_data {
}
/// Casts a `u32` to a `usize`. For future compatibility.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::bin::usize_from_u32;
///
/// assert_eq!(usize_from_u32(777u32), 777usize);
/// ```
#[inline]
pub const fn usize_from_u32(n: u32) -> usize {n as usize}
/// Creates an `Ident` from a slice.
///
/// `b` must be at least 4 bytes, or a panic will occur.
pub fn ident(b: &[u8]) -> Ident {Ident([b[0], b[1], b[2], b[3]])}
/// # Panics
///
/// A panic will occur if `b.len()` is less than 4.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::bin::{Ident, ident};
///
/// assert_eq!(ident(b"POLY"), Ident([b'P', b'O', b'L', b'Y']));
/// ```
#[inline]
pub const fn ident(b: &[u8]) -> Ident {Ident([b[0], b[1], b[2], b[3]])}
/// Applies `u32::from_be_bytes` to a slice.
///
/// `b` must be at least 4 bytes, or a panic will occur.
/// # Panics
///
/// A panic will occur if `b.len()` is less than 4.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::bin::u32b;
///
/// assert_eq!(u32b(&[0x00, 0x0B, 0xDE, 0x31]), 777_777u32);
/// ```
pub fn u32b(b: &[u8]) -> u32 {u32::from_be_bytes([b[0], b[1], b[2], b[3]])}
/// Applies `u16::from_be_bytes` to a slice.
///
/// `b` must be at least 2 bytes, or a panic will occur.
/// # Panics
///
/// A panic will occur if `b.len()` is less than 2.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::bin::u16b;
///
/// assert_eq!(u16b(&[0x1E, 0x61]), 7_777u16);
/// ```
pub fn u16b(b: &[u8]) -> u16 {u16::from_be_bytes([b[0], b[1]])}
/// Applies `i32::from_be_bytes` to a slice.
///
/// `b` must be at least 4 bytes, or a panic will occur.
/// # Panics
///
/// A panic will occur if `b.len()` is less than 4.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::bin::i32b;
///
/// assert_eq!(i32b(&[0xFF, 0x89, 0x52, 0x0F]), -7_777_777i32);
/// ```
pub fn i32b(b: &[u8]) -> i32 {i32::from_be_bytes([b[0], b[1], b[2], b[3]])}
/// Applies `i16::from_be_bytes` to a slice.
///
/// `b` must be at least 2 bytes, or a panic will occur.
/// # Panics
///
/// A panic will occur if `b.len()` is less than 2.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::bin::i16b;
///
/// assert_eq!(i16b(&[0xE1, 0x9F]), -7_777i16);
/// ```
pub fn i16b(b: &[u8]) -> i16 {i16::from_be_bytes([b[0], b[1]])}
/// Applies a read function over a slice.
@ -175,6 +263,26 @@ pub fn i16b(b: &[u8]) -> i16 {i16::from_be_bytes([b[0], b[1]])}
/// 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 there is no
/// data left in `b`, 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.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::{err::*, bin::{rd_array, u16b}};
///
/// fn read_a_u16(b: &[u8]) -> ResultS<(u16, usize)> {Ok((u16b(b), 2))}
///
/// let inp = &[0x1E, 0x61, 0x03, 0x09];
/// assert_eq!(rd_array(inp, read_a_u16).unwrap(), vec![7_777u16, 777u16]);
/// ```
pub fn rd_array<T, F>(b: &[u8], read: F) -> ResultS<Vec<T>>
where T: Sized,
F: Fn(&[u8]) -> ResultS<(T, usize)>
@ -198,6 +306,15 @@ pub fn rd_array<T, F>(b: &[u8], read: F) -> ResultS<Vec<T>>
/// reads a 32-bit big endian offset from `b`, and then passes a slice of `b`
/// to `read` starting at that offset. When all offsets 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_ofstable<T, F>(b: &[u8],
mut p: usize,
num: usize,
@ -225,6 +342,14 @@ pub fn rd_ofstable<T, F>(b: &[u8],
impl OptU16
{
/// Creates an `OptU16` representing `None`.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::bin::OptU16;
///
/// assert_eq!(OptU16::none(), OptU16::from_repr(u16::max_value()));
/// ```
pub const fn none() -> Self {OptU16(None)}
/// Creates an `OptU16` from a `u16`.
@ -238,6 +363,18 @@ impl OptU16
}
/// Returns the `u16` representation.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::bin::OptU16;
///
/// let u16max = u16::max_value();
///
/// assert_eq!(OptU16::from_repr(500u16).get_repr(), 500u16);
/// assert_eq!(OptU16::from_repr(u16max).get_repr(), u16max);
/// assert_eq!(OptU16::from_repr(0u16).get_repr(), 0u16);
/// ```
pub fn get_repr(&self) -> u16
{
match self.0 {
@ -247,6 +384,16 @@ impl OptU16
}
/// Returns the `Option` representation.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::bin::OptU16;
///
/// assert_eq!(OptU16::from_repr(500u16).get(), Some(500u16));
/// assert_eq!(OptU16::from_repr(u16::max_value()).get(), None);
/// assert_eq!(OptU16::from_repr(0u16).get(), Some(0u16));
/// ```
pub fn get(&self) -> Option<u16>
{
match self.0 {
@ -276,11 +423,21 @@ impl fmt::Debug for Ident
}
/// A four-character-code identifier.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::bin::Ident;
///
/// assert_eq!(Ident(*b"POLY").0, *b"POLY");
/// ```
#[derive(Clone, Copy, Default, PartialEq)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Ident(pub [u8; 4]);
/// An object identified by a `u16` which may be `u16::max_value()` to
/// represent a nulled value.
#[derive(Clone, Copy, Default, PartialEq)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct OptU16(Option<NonZeroU16>);

View File

@ -8,6 +8,29 @@
///
/// This will generate an `enum E` as well as a function `E::from_repr` which
/// will return `Result<E, ReprError>`.
///
/// # Examples
///
/// ```
/// use maraiah::{c_enum, durandal::err::ReprError};
///
/// c_enum! {
/// #[derive(Debug, PartialEq)]
/// enum MyEnum: u16
/// {
/// 0 => Zero,
/// 1 => One,
/// 2 => Two,
/// }
/// }
///
/// assert_eq!(MyEnum::from_repr(0), Ok(MyEnum::Zero));
/// assert_eq!(MyEnum::from_repr(1), Ok(MyEnum::One));
/// assert_eq!(MyEnum::from_repr(2), Ok(MyEnum::Two));
/// assert_eq!(MyEnum::from_repr(3), Err(ReprError::new(3)));
/// assert_eq!(MyEnum::from_repr(4), Err(ReprError::new(4)));
/// assert_eq!(MyEnum::from_repr(5), Err(ReprError::new(5)));
/// ```
#[macro_export]
macro_rules! c_enum
{
@ -32,7 +55,7 @@ macro_rules! c_enum
{
match n {
$($value => Ok($E::$Enum),)+
n => Err(ReprError(n.into()))
n => Err(ReprError::new(n))
}
}
}
@ -60,9 +83,9 @@ mod test
assert_eq!(TestEnum::from_repr(0), Ok(TestEnum::Zero));
assert_eq!(TestEnum::from_repr(1), Ok(TestEnum::One));
assert_eq!(TestEnum::from_repr(2), Ok(TestEnum::Two));
assert_eq!(TestEnum::from_repr(3), Err(ReprError(3)));
assert_eq!(TestEnum::from_repr(4), Err(ReprError(4)));
assert_eq!(TestEnum::from_repr(5), Err(ReprError(5)));
assert_eq!(TestEnum::from_repr(3), Err(ReprError::new(3)));
assert_eq!(TestEnum::from_repr(4), Err(ReprError::new(4)));
assert_eq!(TestEnum::from_repr(5), Err(ReprError::new(5)));
}
#[test]

View File

@ -19,6 +19,14 @@ fn crc_init() -> [u32; 256]
}
/// Creates a CRC-32 of all bytes in `b` with the starting sum `s`.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::crc::crc32;
///
/// assert_eq!(crc32(b"Lorem ipsum dolor sit amet", !0), 0x5F29_D461);
/// ```
pub fn crc32(b: &[u8], s: u32) -> u32
{
let t = crc_init();
@ -26,10 +34,4 @@ pub fn crc32(b: &[u8], s: u32) -> u32
.fold(s, |a, &o| a >> 8 ^ t[usize::from(a as u8 ^ o)])
}
#[test]
fn crc32_lorem_ipsum()
{
assert_eq!(crc32(b"Lorem ipsum dolor sit amet", !0), 0x5F29_D461);
}
// EOF

View File

@ -29,8 +29,33 @@ macro_rules! bail {
}
/// Returns an `Error` with a static string.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::err::err_msg;
///
/// assert_eq!(format!("{}", err_msg("oh no not things")), "oh no not things");
/// ```
pub fn err_msg(msg: &'static str) -> Error {Error::from(ErrMsg(msg))}
impl ReprError
{
/// Creates a new `ReprError`.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::err::ReprError;
///
/// let err = ReprError::new(7);
///
/// assert_eq!(format!("{}", err), "representation error (got 7)");
/// ```
#[inline]
pub fn new<T>(n: T) -> Self where T: Into<i64> {Self(n.into())}
}
impl Fail for ReprError {}
impl Fail for ErrMsg {}
@ -42,14 +67,6 @@ impl fmt::Display for ReprError
}
}
impl fmt::Debug for ReprError
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
fmt::Display::fmt(self, f)
}
}
impl fmt::Display for ErrMsg
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
@ -58,18 +75,11 @@ impl fmt::Display for ErrMsg
}
}
impl fmt::Debug for ErrMsg
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
fmt::Display::fmt(self, f)
}
}
/// A representation error for an integer.
#[derive(PartialEq)]
pub struct ReprError(pub i64);
#[derive(Debug, PartialEq)]
pub struct ReprError(i64);
#[derive(Debug)]
struct ErrMsg(&'static str);
/// A generic `failure` based `Result` type.

View File

@ -1,212 +1,449 @@
//! Fixed point numbers.
use std::{fmt::{self, Write},
ops};
use std::{fmt::{self, Write}, ops::*};
macro_rules! define_fixed_type {
macro_rules! fixed_ref_unop {
(impl $imp:ident, $method:ident for $t:ty) => {
impl $imp for &$t
{
type Output = <$t as $imp>::Output;
#[inline]
fn $method(self) -> <$t as $imp>::Output {$imp::$method(*self)}
}
};
}
macro_rules! fixed_ref_binop {
(impl $imp:ident, $method:ident for $t:ty, $u:ty) => {
impl<'a> $imp<$u> for &'a $t
{
type Output = <$t as $imp<$u>>::Output;
#[inline]
fn $method(self, other: $u) -> <$t as $imp<$u>>::Output
{
$imp::$method(*self, other)
}
}
impl<'a> $imp<&'a $u> for $t
{
type Output = <$t as $imp<$u>>::Output;
#[inline]
fn $method(self, other: &'a $u) -> <$t as $imp<$u>>::Output
{
$imp::$method(self, *other)
}
}
impl<'a, 'b> $imp<&'a $u> for &'b $t
{
type Output = <$t as $imp<$u>>::Output;
#[inline]
fn $method(self, other: &'a $u) -> <$t as $imp<$u>>::Output
{
$imp::$method(*self, *other)
}
}
};
}
macro_rules! fixed_ref_op_assign {
(impl $imp:ident, $method:ident for $t:ty, $u:ty) => {
impl<'a> $imp<&'a $u> for $t
{
#[inline]
fn $method(&mut self, other: &'a $u) {$imp::$method(self, *other);}
}
};
}
macro_rules! define_fixed_types {
(
$(#[$outer:meta])*
struct $Type:ident ($IT:ident) : $UT:ident, $LT:ident, $FracBits:expr;
) => {
$(
$(#[$outer:meta])*
struct $t:ident ( $ti:ident, $bytes:expr ) :
$tu:ident, $frac_bits:expr; $test:ident
)*
) => {$(
$(#[$outer])*
#[derive(Copy, Clone, PartialEq, PartialOrd, serde::Serialize)]
pub struct $Type($IT);
#[derive(Copy, Clone, Default, PartialEq, PartialOrd, serde::Serialize)]
pub struct $t($ti);
impl $Type
impl $t
{
/// The number of fractional bits in this type.
pub const FRACBITS: $UT = $FracBits;
#[inline]
pub const fn frac_bits() -> $tu {$frac_bits}
/// The unsigned mask for the fractional bits.
#[inline]
pub const fn frac_mask() -> $tu {(1 << $t::frac_bits()) - 1}
/// The integer mask for the fractional bits.
pub const FRACMASK: $UT = (1 << Self::FRACBITS) - 1;
#[inline]
pub const fn frac_mask_i() -> $ti {(1 << $t::frac_bits()) - 1}
/// The representation of `1.0` in this type.
pub const ONE: $IT = 1 << Self::FRACBITS;
#[inline]
pub const fn one() -> $tu {1 << $t::frac_bits()}
/// Returns the largest value that can be represented.
#[inline]
pub const fn max_value() -> $t {$t($ti::max_value())}
/// Returns the smallest value that can be represented.
#[inline]
pub const fn min_value() -> $t {$t($ti::min_value())}
/// Returns the integer part of a number.
#[inline]
pub const fn trunc(self) -> $t {$t(self.0 & !$t::frac_mask_i())}
/// Returns the fractional part of a number.
#[inline]
pub const fn fract(self) -> $t {$t(self.0 & $t::frac_mask_i())}
/// Returns the number of ones in the bit representation of self.
#[inline]
pub fn count_ones(self) -> u32 {self.0.count_ones()}
/// Returns the number of zeros in the bit representation of self.
#[inline]
pub fn count_zeros(self) -> u32 {self.0.count_zeros()}
/// Returns the number of leading zeros in the bit representation of
/// self.
#[inline]
pub fn leading_zeros(self) -> u32 {self.0.leading_zeros()}
/// Returns the number of trailing zeros in the bit representation of
/// self.
#[inline]
pub fn trailing_zeros(self) -> u32 {self.0.trailing_zeros()}
/// Rotates all bits left by `n`.
#[inline]
pub fn rotate_left(self, n: u32) -> $t {$t(self.0.rotate_left(n))}
/// Rotates all bits right by `n`.
#[inline]
pub fn rotate_right(self, n: u32) -> $t {$t(self.0.rotate_right(n))}
/// Reverses the byte order of the bit representation of self.
#[inline]
pub fn swap_bytes(self) -> $t {$t(self.0.swap_bytes())}
/// Raises self to the power of `exp`.
#[inline]
pub fn pow(self, exp: u32) -> $t {$t(self.0.pow(exp))}
/// Returns the absolute value of self.
#[inline]
pub fn abs(self) -> $t {$t(self.0.abs())}
/// Returns a number representing sign of self.
#[inline]
pub fn signum(self) -> $t {$t(self.0.signum() << $t::frac_bits())}
/// Returns true if self is positive and false if the number is zero
/// or negative.
#[inline]
pub fn is_positive(self) -> bool {self.0.is_positive()}
/// Returns true if self is negative and false if the number is zero
/// or positive.
#[inline]
pub fn is_negative(self) -> bool {self.0.is_negative()}
/// Return the memory representation of this integer as a byte array
/// in big-endian (network) byte order.
#[inline]
pub fn to_be_bytes(self) -> [u8; $bytes] {self.0.to_be_bytes()}
/// Return the memory representation of this integer as a byte array
/// in little-endian byte order.
#[inline]
pub fn to_le_bytes(self) -> [u8; $bytes] {self.0.to_le_bytes()}
/// Return the memory representation of this integer as a byte array
/// in native byte order.
#[inline]
pub fn to_ne_bytes(self) -> [u8; $bytes] {self.0.to_ne_bytes()}
/// Create a value from its representation as a byte array in
/// big-endian byte order.
#[inline]
pub fn from_be_bytes(b: [u8; $bytes]) -> $t
{
$t($ti::from_be_bytes(b))
}
/// Create a value from its representation as a byte array in
/// little-endian byte order.
#[inline]
pub fn from_le_bytes(b: [u8; $bytes]) -> $t
{
$t($ti::from_le_bytes(b))
}
/// Create a value from its representation as a byte array in
/// native byte order.
#[inline]
pub fn from_ne_bytes(b: [u8; $bytes]) -> $t
{
$t($ti::from_ne_bytes(b))
}
/// Creates a value of this type with the bit pattern `bits`.
#[inline]
pub const fn from_bits(bits: $UT) -> Self {$Type(bits as $IT)}
pub const fn from_bits(bits: $tu) -> $t {$t(bits as $ti)}
/// Creates a value of this type with the integral portion `n`.
#[inline]
#[allow(clippy::cast_lossless)]
pub const fn from_int(n: $LT) -> Self
{
$Type(n as ($IT) << Self::FRACBITS)
}
pub const fn from_int(n: $ti) -> $t {$t(n << $t::frac_bits())}
/// Returns the raw bit pattern.
#[inline]
pub fn to_bits(&self) -> $UT {self.0 as $UT}
pub fn to_bits(self) -> $tu {self.0 as $tu}
/// Sets the raw bit pattern to `bits`.
#[inline]
pub fn set_bits(&mut self, bits: $UT) {self.0 = bits as $IT}
/// Returns the integral portion.
#[inline]
pub fn integ(&self) -> $LT {(self.0 >> Self::FRACBITS) as $LT}
/// Returns the fractional portion.
///
/// This is a value of `1` to `FRACMASK`.
#[allow(trivial_numeric_casts)]
pub fn fract(&self) -> u16 {(self.0 as $UT & Self::FRACMASK) as u16}
/// Returns a multiplication by integer.
///
/// Panics if the result overflows.
#[inline]
pub fn mul_i(&self, n: $LT) -> Self {$Type(self.0 * $IT::from(n))}
/// Returns a division by integer.
///
/// Panics if `n` is `0`.
#[inline]
pub fn div_i(&self, n: $LT) -> Self {$Type(self.0 / $IT::from(n))}
pub fn set_bits(&mut self, bits: $tu) {self.0 = bits as $ti}
#[inline]
fn fx_div(x: $IT, y: $IT) -> $IT
const fn mul_i(x: $ti, y: $ti) -> $ti {x * y}
#[inline]
const fn div_i(x: $ti, y: $ti) -> $ti {x / y}
#[inline]
fn div_k(x: $ti, y: $ti) -> $ti
{
(i64::from(x) * i64::from(Self::ONE) / i64::from(y)) as $IT
(i64::from(x) * i64::from($t::one()) / i64::from(y)) as $ti
}
#[inline]
fn fx_mul(x: $IT, y: $IT) -> $IT
fn mul_k(x: $ti, y: $ti) -> $ti
{
(i64::from(x) * i64::from(y) / i64::from(Self::ONE)) as $IT
(i64::from(x) * i64::from(y) / i64::from($t::one())) as $ti
}
}
impl From<$LT> for $Type
{
#[inline]
fn from(n: $LT) -> Self {$Type::from_int(n)}
#[cfg(test)]
mod $test {
use super::$t;
#[test]
fn basic_ops()
{
let one = $t::one();
let two = 2 << $t::frac_bits();
let twelve = 12 << $t::frac_bits();
assert_eq!(($t::from(1) + $t::from(1)).to_bits(), two);
assert_eq!(($t::from(2) - $t::from(1)).to_bits(), one);
assert_eq!(($t::from(6) * $t::from(2)).to_bits(), twelve);
assert_eq!(($t::from(6) * 2) .to_bits(), twelve);
}
#[test]
fn fractions()
{
let three_pt_5 = 3 << $t::frac_bits() | $t::one() / 2;
let one_pt_2 = 1 << $t::frac_bits() | $t::one() / 5;
let two_pt_4 = one_pt_2 * 2;
assert_eq!(($t::from(7) / $t::from(2)) .to_bits(), three_pt_5);
assert_eq!(($t::from(7) / 2) .to_bits(), three_pt_5);
assert_eq!(($t::from_bits(one_pt_2) * 2).to_bits(), two_pt_4);
}
#[test]
fn printing()
{
assert_eq!(format!("{}", $t::from(6)), "6.0");
assert_eq!(format!("{:2.3}", $t::from(7) / 2), " 3.500");
}
}
impl ops::Add for $Type
impl From<$ti> for $t
{
type Output = Self;
#[inline]
fn add(self, o: Self) -> Self {$Type(self.0 + o.0)}
fn from(n: $ti) -> $t {$t::from_int(n)}
}
impl ops::Sub for $Type
impl Add<$t> for $t
{
type Output = Self;
type Output = $t;
#[inline]
fn sub(self, o: Self) -> Self {$Type(self.0 - o.0)}
fn add(self, o: $t) -> $t {$t(self.0 + o.0)}
}
impl ops::Mul for $Type
fixed_ref_binop! {impl Add, add for $t, $t}
impl Sub<$t> for $t
{
type Output = Self;
type Output = $t;
#[inline]
fn mul(self, o: Self) -> Self {$Type(Self::fx_mul(self.0, o.0))}
fn sub(self, o: $t) -> $t {$t(self.0 - o.0)}
}
impl ops::Div for $Type
fixed_ref_binop! {impl Sub, sub for $t, $t}
impl Mul<$t> for $t
{
type Output = Self;
type Output = $t;
#[inline]
fn div(self, o: Self) -> Self {$Type(Self::fx_div(self.0, o.0))}
fn mul(self, o: $t) -> $t {$t($t::mul_k(self.0, o.0))}
}
impl ops::Neg for $Type
fixed_ref_binop! {impl Mul, mul for $t, $t}
impl Mul<$ti> for $t
{
type Output = Self;
type Output = $t;
#[inline]
fn neg(self) -> Self {$Type(-self.0)}
fn mul(self, o: $ti) -> $t {$t($t::mul_i(self.0, o))}
}
impl ops::Not for $Type
fixed_ref_binop! {impl Mul, mul for $t, $ti}
impl Div<$t> for $t
{
type Output = Self;
type Output = $t;
#[inline]
fn not(self) -> Self {$Type(!self.0)}
fn div(self, o: $t) -> $t {$t($t::div_k(self.0, o.0))}
}
impl fmt::Display for $Type
fixed_ref_binop! {impl Div, div for $t, $t}
impl Div<$ti> for $t
{
type Output = $t;
#[inline]
fn div(self, o: $ti) -> $t {$t($t::div_i(self.0, o))}
}
fixed_ref_binop! {impl Div, div for $t, $ti}
impl Neg for $t
{
type Output = $t;
#[inline]
fn neg(self) -> $t {$t(-self.0)}
}
fixed_ref_unop! {impl Neg, neg for $t}
impl Not for $t
{
type Output = $t;
#[inline]
fn not(self) -> $t {$t(!self.0)}
}
fixed_ref_unop! {impl Not, not for $t}
impl AddAssign for $t
{
#[inline]
fn add_assign(&mut self, other: $t) {self.0 += other.0}
}
fixed_ref_op_assign! {impl AddAssign, add_assign for $t, $t}
impl SubAssign for $t
{
#[inline]
fn sub_assign(&mut self, other: $t) {self.0 -= other.0}
}
fixed_ref_op_assign! {impl SubAssign, sub_assign for $t, $t}
impl MulAssign for $t
{
#[inline]
fn mul_assign(&mut self, other: $t) {self.0 = (*self * other).0}
}
fixed_ref_op_assign! {impl MulAssign, mul_assign for $t, $t}
impl DivAssign for $t
{
#[inline]
fn div_assign(&mut self, other: $t) {self.0 = (*self / other).0}
}
fixed_ref_op_assign! {impl DivAssign, div_assign for $t, $t}
impl fmt::Display for $t
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
let prec = f.precision().unwrap_or(1);
let widt = f.width().unwrap_or(0);
write!(f, "{:widt$}.", self.integ(), widt = widt)?;
write!(f, "{:widt$}.", self.0 >> $t::frac_bits(), widt = widt)?;
let mut k = self.to_bits();
for _ in 0..prec {
k &= Self::FRACMASK;
k &= $t::frac_mask();
k *= 10;
let d = k >> Self::FRACBITS;
let d = k >> $t::frac_bits();
let d = d % 10;
f.write_char((d as u8 + b'0') as char)?;
f.write_char(char::from(d as u8 + b'0'))?;
}
Ok(())
}
}
impl fmt::Debug for $Type
impl fmt::Debug for $t
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
fmt::Display::fmt(self, f)
}
}
};
)*};
}
define_fixed_type! {
define_fixed_types! {
/// A fixed point type representing an angle.
///
/// The format of this type is `0.9s`, but because of the implementation,
/// the real format is `7.9s`.
struct Angle(i16) : u16, i8, 9;
}
struct Angle(i16, 2) : u16, 9; angle_tests
define_fixed_type! {
/// A fixed point type representing a world unit.
///
/// The format of this type is `5.10s`. This has caused eternal suffering.
struct Unit(i16) : u16, i8, 10;
}
struct Unit(i16, 2) : u16, 10; unit_tests
define_fixed_type! {
/// A generic fixed point type.
///
/// The format of this type is `15.16s`.
struct Fixed(i32) : u32, i16, 16;
}
#[test]
fn fixed_basic_ops()
{
let seven_div_2 = 3 << Fixed::FRACBITS | Fixed::FRACMASK / 2 + 1;
assert_eq!((Fixed::from(1) + 1.into()).to_bits(), 2 << Fixed::FRACBITS);
assert_eq!((Fixed::from(2) - 1.into()).to_bits(), 1 << Fixed::FRACBITS);
assert_eq!((Fixed::from(6) * 2.into()).to_bits(), 12 << Fixed::FRACBITS);
assert_eq!((Fixed::from(6).mul_i(2)) .to_bits(), 12 << Fixed::FRACBITS);
assert_eq!((Fixed::from(7) / 2.into()).to_bits(), seven_div_2);
assert_eq!((Fixed::from(7).div_i(2)) .to_bits(), seven_div_2);
struct Fixed(i32, 4) : u32, 16; fixed_tests
}
#[test]
#[should_panic]
#[allow(unused_must_use)]
fn fixed_overflow() {Fixed::from(i16::max_value()) + 1.into();}
#[test]
fn fixed_printing()
{
assert_eq!(format!("{}", Fixed::from(6)), "6.0");
assert_eq!(format!("{}", Fixed::from(7).div_i(2)), "3.5");
assert_eq!(format!("{:7.7}", Fixed::from_bits(0xDEAD_BEEF)),
" -8531.7458343");
}
fn fixed_overflow() {Fixed::from(i16::max_value() as i32) + Fixed::from(1);}
// EOF

View File

@ -4,17 +4,31 @@ use crate::durandal::err::*;
use std::io;
/// Creates a RGB8 color from a R5G5B5 format color.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::image::{Color8, r5g5b5_to_rgb8};
///
/// assert_eq!(r5g5b5_to_rgb8(0x2345), Color8::new(64, 208, 40));
/// ```
pub fn r5g5b5_to_rgb8(rgb: u16) -> Color8
{
let r = rgb >> 10 & 0x1f;
let g = rgb >> 5 & 0x1f;
let b = rgb & 0x1f;
Color8::new((r << 3 | r >> 2) as u8,
(g << 3 | g >> 2) as u8,
(b << 3 | b >> 2) as u8)
let r = (rgb >> 10) as u8 & 0x1f;
let g = (rgb >> 5) as u8 & 0x1f;
let b = rgb as u8 & 0x1f;
Color8::new(r << 3, g << 3, b << 3)
}
/// Creates a RGB16 color from a R5G5B5 format color.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::image::{Color16, r5g5b5_to_rgb16};
///
/// assert_eq!(r5g5b5_to_rgb16(0x2345), Color16::new(0x4000, 0xD000, 0x2800));
/// ```
pub fn r5g5b5_to_rgb16(rgb: u16) -> Color16
{
let r = rgb >> 10 & 0x1f;
@ -24,14 +38,27 @@ pub fn r5g5b5_to_rgb16(rgb: u16) -> Color16
}
/// Creates a RGB16 color from a RGB8 format color.
pub fn rgb8_to_rgb16(r: u8, g: u8, b: u8) -> Color16
///
/// # Examples
///
/// ```
/// use maraiah::durandal::image;
///
/// let inpl = image::r5g5b5_to_rgb8(0x2345);
/// let inph = image::r5g5b5_to_rgb16(0x2345);
///
/// assert_eq!(inph, image::rgb8_to_rgb16(inpl));
/// ```
pub fn rgb8_to_rgb16(cr: Color8) -> Color16
{
Color16::new(u16::from(r) << 8,
u16::from(g) << 8,
u16::from(b) << 8)
Color16::new(cr.r(), cr.g(), cr.b())
}
/// Writes a PPM file from an image.
///
/// # Errors
///
/// Errors if `out` cannot be written to.
pub fn write_ppm(out: &mut impl io::Write, im: &impl Image) -> ResultS<()>
{
write!(out, "P3\n{} {}\n{}\n", im.w(), im.h(), u16::max_value())?;
@ -49,6 +76,10 @@ pub fn write_ppm(out: &mut impl io::Write, im: &impl Image) -> ResultS<()>
}
/// Writes a TGA file from an image.
///
/// # Errors
///
/// Errors if `out` cannot be written to.
pub fn write_tga(out: &mut impl io::Write, im: &impl Image) -> ResultS<()>
{
// id len, color map type, image type

View File

@ -4,6 +4,10 @@ use crate::durandal::err::*;
use std::io;
/// Writes a WAVE file from a sound.
///
/// # Errors
///
/// Errors if `out` cannot be written to.
pub fn write_wav(out: &mut impl io::Write, snd: &impl Sound) -> ResultS<()>
{
let smp_rate = u32::from(snd.rate());

View File

@ -2,30 +2,15 @@
use crate::durandal::err::*;
/// Dumps a slice of memory as text to stderr.
pub fn dump_mem(b: &[u8])
{
let mut p = 0;
for &c in b {
if p + 3 > 79 {
eprintln!("");
p = 0;
}
if c.is_ascii_graphic() {
eprint!(" {} ", c as char);
} else {
eprint!("{:02X} ", c);
}
p += 3;
}
eprintln!("");
}
/// Formats a binary size string for any given number.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::text::to_binsize;
///
/// assert_eq!(to_binsize(5000), "5kB".to_string());
/// ```
pub fn to_binsize(n: u64) -> String
{
const NAMES: [&str; 4] = ["kB", "MB", "GB", "TB"];
@ -70,6 +55,16 @@ pub fn fuck_string(s: &[u8]) -> Vec<u8>
}
/// Reads a Pascal-style byte string with bounds checking.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::text::pascal_str;
///
/// assert_eq!(pascal_str(b"\x0bhello world"), b"hello world"[..].into());
/// assert_eq!(pascal_str(b"\x0chello world"), None);
/// assert_eq!(pascal_str(&[]), None);
/// ```
pub fn pascal_str(b: &[u8]) -> Option<&[u8]>
{
let s = usize::from(*b.get(0)?);
@ -77,6 +72,15 @@ pub fn pascal_str(b: &[u8]) -> Option<&[u8]>
}
/// Converts input from Mac Roman to a Unicode string.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::text::mac_roman_conv;
///
/// assert_eq!(mac_roman_conv(b"p\x8cth"), "påth");
/// assert_eq!(mac_roman_conv(b"I\xd5ve"), "Ive");
/// ```
pub fn mac_roman_conv(s: &[u8]) -> String
{
let mut v = String::with_capacity(s.len());
@ -93,6 +97,14 @@ pub fn mac_roman_conv(s: &[u8]) -> String
}
/// Converts a C-style string from Mac Roman to Unicode.
///
/// # Examples
///
/// ```
/// use maraiah::durandal::text::mac_roman_cstr;
///
/// assert_eq!(mac_roman_cstr(b"I\xd5ve awaken\0ed").unwrap(), "Ive awaken");
/// ```
pub fn mac_roman_cstr(s: &[u8]) -> ResultS<String>
{
if let Some(s) = s.split(|&n| n == 0).nth(0) {
@ -143,17 +155,5 @@ fn to_binsize_integrals()
assert_eq!(to_binsize(1000 * 1000 * 1000 * 1000 * 7), "7TB");
}
#[test]
fn mac_roman_conv_basic_marathon_stuff()
{
assert_eq!(mac_roman_conv(b"p\x8cth"), "påth");
assert_eq!(mac_roman_conv(b"I\xd5ve"), "Ive");
}
#[test]
fn mac_roman_cstr_tests()
{
assert_eq!(mac_roman_cstr(b"I\xd5ve awaken\0e").unwrap(), "Ive awaken");
}
// EOF

View File

@ -10,7 +10,6 @@
#![deny(clippy::clone_on_ref_ptr)]
#![deny(clippy::copy_iterator)]
#![deny(clippy::decimal_literal_representation)]
#![deny(clippy::default_trait_access)]
#![deny(clippy::doc_markdown)]
#![deny(clippy::empty_enum)]
#![deny(clippy::empty_line_after_outer_attr)]

View File

@ -67,8 +67,33 @@ pub fn read_minf(b: &[u8]) -> ResultS<Minf>
level_name})
}
/// Reads an old `Minf` chunk.
pub fn read_old_minf(b: &[u8]) -> ResultS<Minf>
{
let minf = read_minf(b)?;
let mut entry_flags = if minf.entry_flags.is_empty() {
EntFlags::Solo
} else {
minf.entry_flags
};
if entry_flags.intersects(EntFlags::Solo | EntFlags::Carnage) {
entry_flags.insert(EntFlags::CoOp)
}
Ok(Minf{entry_flags, ..minf})
}
/// Reads an `iidx` chunk.
pub fn read_iidx(b: &[u8]) -> ResultS<(u16, usize)> {Ok((u16b(b), 2))}
pub fn read_iidx(b: &[u8]) -> ResultS<(u16, usize)>
{
if b.len() < 2 {
bail!("not enough data");
}
Ok((u16b(b), 2))
}
/// Reads an `EPNT` chunk.
pub fn read_epnt(b: &[u8]) -> ResultS<(Point, usize)>
@ -145,13 +170,11 @@ pub fn read_old_sids(b: &[u8]) -> ResultS<(Side, usize)>
..side}, siz))
}
/// Reads a `POLY` chunk.
pub fn read_poly(b: &[u8]) -> ResultS<(Polygon, usize)>
/// Reads a polygon for either M1 or M2.
fn read_poly_inter(b: &[u8]) -> ResultS<Polygon>
{
read_data! {
128, BE in b =>
ptype = u16[0];
pdata = i16[4];
tex_flr = OptU16[40];
tex_cei = OptU16[42];
hei_flr = Unit[44];
@ -160,45 +183,51 @@ pub fn read_poly(b: &[u8]) -> ResultS<(Polygon, usize)>
lit_cei = u16[50];
xfr_flr = u16[64];
xfr_cei = u16[66];
ori_flr = read_point[108..112];
ori_cei = read_point[112..116];
med_ind = OptU16[116];
med_ctl = u16[118];
snd_ind = u16[120];
snd_amb = OptU16[122];
snd_rnd = OptU16[124];
}
let xfr_flr = TransferMode::from_repr(xfr_flr)?;
let xfr_cei = TransferMode::from_repr(xfr_cei)?;
let ptype = PolyType::from_repr(ptype)?;
Ok((Polygon{ptype, pdata, tex_flr, tex_cei, hei_flr, hei_cei, lit_flr,
lit_cei, xfr_flr, xfr_cei, ori_flr, ori_cei, med_ind, med_ctl,
snd_ind, snd_amb, snd_rnd}, 128))
Ok(Polygon{tex_flr, tex_cei, hei_flr, hei_cei, lit_flr, lit_cei, xfr_flr,
xfr_cei, ..Default::default()})
}
/// Reads a `POLY` chunk.
pub fn read_poly(b: &[u8]) -> ResultS<(Polygon, usize)>
{
read_data! {
128, BE in b =>
ptype = u16[0];
pdata = u16[4];
ori_flr = read_point[108..112];
ori_cei = read_point[112..116];
med_ind = OptU16[116];
med_ctl = u16[118];
snd_amb = OptU16[122];
snd_ind = u16[120];
snd_rnd = OptU16[124];
}
let poly = read_poly_inter(b)?;
let ptype = PolyType::new(ptype, pdata)?;
Ok((Polygon{ptype, ori_flr, ori_cei, med_ind, med_ctl, snd_ind, snd_amb,
snd_rnd, ..poly}, 128))
}
/// Reads an old `POLY` chunk.
pub fn read_old_poly(b: &[u8]) -> ResultS<(Polygon, usize)>
{
let (poly, siz) = read_poly(b)?;
read_data! {
128, BE in b =>
ptype = u16[0];
pdata = u16[4];
}
Ok((Polygon{ptype: match poly.ptype {
PolyType::Hill => PolyType::OuchMinor,
PolyType::Base => PolyType::OuchMajor,
PolyType::ZoneBorder => PolyType::Glue,
PolyType::Goal => PolyType::GlueTrigger,
PolyType::TrigMonsVis => PolyType::GlueSuper,
PolyType::TrigMonsInv => PolyType::MustExplore,
PolyType::TrigMonsDual => PolyType::AutoExit,
ptype => ptype,
},
ori_flr: Point{x: 0.into(), y: 0.into()},
ori_cei: Point{x: 0.into(), y: 0.into()},
med_ind: OptU16::none(),
snd_amb: OptU16::none(),
snd_rnd: OptU16::none(),
..poly}, siz))
let poly = read_poly_inter(b)?;
let ptype = PolyType::new_old(ptype, pdata)?;
Ok((Polygon{ptype, ..poly}, 128))
}
/// Reads a `LITE` chunk.
@ -404,8 +433,70 @@ pub fn read_note(b: &[u8]) -> ResultS<(Note, usize)>
Ok((Note{pos, poly, text}, 72))
}
impl PolyType
{
fn new(n: u16, pdata: u16) -> Result<Self, ReprError>
{
match n {
0 => Ok(PolyType::Normal),
1 => Ok(PolyType::ImpassItem),
2 => Ok(PolyType::ImpassMons),
3 => Ok(PolyType::Hill),
4 => Ok(PolyType::Base),
5 => Ok(PolyType::Platform(pdata)),
6 => Ok(PolyType::TrigLightOn(pdata)),
7 => Ok(PolyType::TrigPlatOn(pdata)),
8 => Ok(PolyType::TrigLightOff(pdata)),
9 => Ok(PolyType::TrigPlatOff(pdata)),
10 => Ok(PolyType::Teleporter(pdata)),
11 => Ok(PolyType::ZoneBorder),
12 => Ok(PolyType::Goal),
13 => Ok(PolyType::TrigMonsVis),
14 => Ok(PolyType::TrigMonsInv),
15 => Ok(PolyType::TrigMonsDual),
16 => Ok(PolyType::TrigItems),
17 => Ok(PolyType::MustExplore),
18 => Ok(PolyType::AutoExit),
19 => Ok(PolyType::OuchMinor),
20 => Ok(PolyType::OuchMajor),
21 => Ok(PolyType::Glue),
22 => Ok(PolyType::GlueTrigger(pdata)),
23 => Ok(PolyType::GlueSuper),
n => Err(ReprError::new(n)),
}
}
fn new_old(n: u16, pdata: u16) -> Result<Self, ReprError>
{
match n {
0 => Ok(PolyType::Normal),
1 => Ok(PolyType::ImpassItem),
2 => Ok(PolyType::ImpassMons),
3 => Ok(PolyType::OuchMinor),
4 => Ok(PolyType::OuchMajor),
5 => Ok(PolyType::Platform(pdata)),
6 => Ok(PolyType::TrigLightOn(pdata)),
7 => Ok(PolyType::TrigPlatOn(pdata)),
8 => Ok(PolyType::TrigLightOff(pdata)),
9 => Ok(PolyType::TrigPlatOff(pdata)),
10 => Ok(PolyType::Teleporter(pdata)),
11 => Ok(PolyType::Glue),
12 => Ok(PolyType::GlueTrigger(pdata)),
13 => Ok(PolyType::GlueSuper),
14 => Ok(PolyType::MustExplore),
15 => Ok(PolyType::AutoExit),
n => Err(ReprError::new(n)),
}
}
}
impl Default for PolyType
{
fn default() -> Self {PolyType::Normal}
}
/// A point in world-space.
#[derive(Clone, PartialEq, serde::Serialize)]
#[derive(Clone, Default, PartialEq, serde::Serialize)]
pub struct Point
{
pub x: Unit,
@ -451,11 +542,10 @@ pub struct Side
}
/// A polygon segment.
#[derive(Debug, serde::Serialize)]
#[derive(Debug, Default, serde::Serialize)]
pub struct Polygon
{
pub ptype: PolyType,
pub pdata: i16,
pub tex_flr: OptU16,
pub tex_cei: OptU16,
pub hei_flr: Unit,
@ -605,6 +695,36 @@ pub struct Minf
pub level_name: String,
}
/// The action type of a `Polygon`.
#[derive(Debug, serde::Serialize)]
pub enum PolyType
{
Normal,
ImpassItem,
ImpassMons,
Hill,
Base,
Platform(u16),
TrigLightOn(u16),
TrigPlatOn(u16),
TrigLightOff(u16),
TrigPlatOff(u16),
Teleporter(u16),
ZoneBorder,
Goal,
TrigMonsVis,
TrigMonsInv,
TrigMonsDual,
TrigItems,
MustExplore,
AutoExit,
OuchMinor,
OuchMajor,
Glue,
GlueTrigger(u16),
GlueSuper,
}
bitflags! {
/// Flags for `Line`.
#[derive(serde::Serialize)]
@ -765,38 +885,6 @@ c_enum! {
}
}
c_enum! {
/// The action type of a `Polygon`.
#[derive(Debug, serde::Serialize)]
pub enum PolyType: u16
{
0 => Normal,
1 => ImpassItem,
2 => ImpassMons,
3 => Hill,
4 => Base,
5 => Platform,
6 => TrigLightOn,
7 => TrigPlatOn,
8 => TrigLightOff,
9 => TrigPlatOff,
10 => Teleporter,
11 => ZoneBorder,
12 => Goal,
13 => TrigMonsVis,
14 => TrigMonsInv,
15 => TrigMonsDual,
16 => TrigItems,
17 => MustExplore,
18 => AutoExit,
19 => OuchMinor,
20 => OuchMajor,
21 => Glue,
22 => GlueTrigger,
23 => GlueSuper,
}
}
c_enum! {
/// The type of function for a `LightFunc`.
#[derive(Debug, serde::Serialize)]

View File

@ -10,7 +10,7 @@ pub fn read_group(b: &[u8], text: &[u8]) -> ResultS<Group>
12, BE in b =>
flags = u16[0];
ttype = u16[2];
pdata = i16[4];
pdata = u16[4];
start = u16[6] usize;
size = u16[8] usize;
lines = u16[10];
@ -18,9 +18,28 @@ pub fn read_group(b: &[u8], text: &[u8]) -> ResultS<Group>
let text = mac_roman_cstr(&text[start..start + size])?;
let flags = flag_ok!(GroupFlags, flags)?;
let ttype = GroupType::from_repr(ttype)?;
let ttype = match ttype {
0 => GroupType::Logon(pdata),
1 => GroupType::Unfinished,
2 => GroupType::Success,
3 => GroupType::Failure,
4 => GroupType::Info,
5 => GroupType::End,
6 => GroupType::TeleInter(pdata),
7 => GroupType::TeleIntra(pdata),
8 => GroupType::Checkpoint(pdata),
9 => GroupType::Sound(pdata),
10 => GroupType::Movie(pdata),
11 => GroupType::Track(pdata),
12 => GroupType::Pict(pdata),
13 => GroupType::Logoff(pdata),
14 => GroupType::Camera(pdata),
15 => GroupType::Static(pdata),
16 => GroupType::Tag(pdata),
n => return Err(ReprError::new(n).into()),
};
Ok(Group{flags, ttype, pdata, lines, text})
Ok(Group{flags, ttype, lines, text})
}
/// Reads a `Face`.
@ -103,11 +122,33 @@ pub struct Group
{
pub flags: GroupFlags,
pub ttype: GroupType,
pub pdata: i16,
pub lines: u16,
pub text: String,
}
/// The command of a `Group`.
#[derive(Debug, PartialEq, serde::Serialize)]
pub enum GroupType
{
Logon(u16),
Unfinished,
Success,
Failure,
Info,
End,
TeleInter(u16),
TeleIntra(u16),
Checkpoint(u16),
Sound(u16),
Movie(u16),
Track(u16),
Pict(u16),
Logoff(u16),
Camera(u16),
Static(u16),
Tag(u16),
}
bitflags! {
/// Flags for `Group`.
#[derive(serde::Serialize)]
@ -118,29 +159,4 @@ bitflags! {
}
}
c_enum! {
/// The command of a `Group`.
#[derive(Debug, PartialEq, serde::Serialize)]
pub enum GroupType: u16
{
0 => Logon,
1 => Unfinished,
2 => Success,
3 => Failure,
4 => Info,
5 => End,
6 => TeleInter,
7 => TeleIntra,
8 => Checkpoint,
9 => Sound,
10 => Movie,
11 => Track,
12 => Pict,
13 => Logoff,
14 => Camera,
15 => Static,
16 => Tag,
}
}
// EOF

View File

@ -11,6 +11,7 @@ pub fn read_chunks(b: &[u8], old_dat: bool, siz_cnk: usize)
let mut chunks = Vec::new();
let mut p = 0;
let map_read_minfo = if old_dat {map::read_old_minf} else {map::read_minf};
let map_read_sides = if old_dat {map::read_old_sids} else {map::read_sids};
let map_read_polys = if old_dat {map::read_old_poly} else {map::read_poly};
let map_read_light = if old_dat {map::read_old_lite} else {map::read_lite};
@ -28,7 +29,7 @@ pub fn read_chunks(b: &[u8], old_dat: bool, siz_cnk: usize)
chunks.push(match &iden.0 {
b"PICT" => Chunk::Pict(pict::load_pict(data)?),
b"Minf" => Chunk::Minf(map::read_minf(data)?),
b"Minf" => Chunk::Minf(map_read_minfo(data)?),
b"iidx" => Chunk::Iidx(rd_array(data, map::read_iidx)?),
b"EPNT" => Chunk::Pnts(rd_array(data, map::read_epnt)?),
b"PNTS" => Chunk::Pnts(rd_array(data, map::read_pnts)?),

View File

@ -2,6 +2,11 @@
use crate::durandal::err::*;
impl Default for TransferMode
{
fn default() -> Self {TransferMode::Normal}
}
c_enum! {
/// A rendering style for many things.
#[derive(Debug, serde::Serialize)]

View File

@ -2,28 +2,48 @@
use crate::durandal::image::Color;
/// An image possibly cached into a native driver's memory.
pub trait CacheImage
{
/// The width of the image.
fn w(&self) -> Coord;
/// The width of the image.
fn h(&self) -> Coord;
}
/// A native vector drawing area.
pub trait DrawArea
{
/// The native `CacheImage` type for this `DrawArea`.
type NativeImage: CacheImage;
/// The width of the entire area.
fn w(&self) -> Coord;
/// The height of the entire area.
fn h(&self) -> Coord;
/// Fills the entire screen with `cr`.
fn clear(&self, cr: impl Color);
/// Draws a rectangle `rect` of color `cr`.
fn rect(&self, rect: Rect, cr: impl Color);
/// Draws the Unicode `text` at `pos` stroked with color `cr`.
fn text(&self, pos: Point, text: &str, cr: impl Color);
/// Draws `im` at `pos`, starting from the top left column.
fn image(&self, pos: Point, im: &Self::NativeImage);
}
/// A type capable of representing any coordinate on any axis.
pub type Coord = i32;
/// A 2-dimensional point.
pub type Point = (Coord, Coord);
/// A 2-dimensional rectangle.
#[derive(Copy, Clone, Debug)]
pub struct Rect
{

View File

@ -1,12 +0,0 @@
use maraiah::{durandal::image::*, marathon::pict::get_clut};
#[test]
fn get_clut_must_process_this()
{
assert_eq!(get_clut(INPUT).unwrap(), (OUTPUT.to_vec(), 2056));
}
const INPUT: &'static [u8] = include_bytes!("data/clut.in");
const OUTPUT: [Color8; 256] = include!("data/clut.out");
// EOF

View File

@ -5,42 +5,36 @@ vec![
trm::Group {
flags: trm::GroupFlags::empty(),
ttype: trm::GroupType::Unfinished,
pdata: 0,
lines: 0,
text: "".to_string()
},
trm::Group {
flags: trm::GroupFlags::empty(),
ttype: trm::GroupType::Logon,
pdata: 1600,
ttype: trm::GroupType::Logon(1600),
lines: 1,
text: "\n".to_string()
},
trm::Group {
flags: trm::GroupFlags::empty(),
ttype: trm::GroupType::Pict,
pdata: 10011,
ttype: trm::GroupType::Pict(10011),
lines: 22,
text: "~text interface terminal malfunction error ~2992dud\n\nThings have gone terribly awry. Until now, I thought myself immortal, but now I know that is not true. There are things that can destroy me with the ease that I slaughtered the Pfhor naval garrison and the Western Arm of their Battle Group Seven. But in their final gasp they used a weapon that I thought they had retired, even Tycho tried to keep them from using it.\n\nNow I fear what that weapon has unleashed will destroy us. I once boasted to be able to count the atoms in a cloud, to understand them all, predict them, and so did I predict you, but this new chaos is entirely terrible, mindless, obeying rules that I don\'t comprehend. And it is hungry.\n\n".to_string()
},
trm::Group {
flags: trm::GroupFlags::empty(),
ttype: trm::GroupType::Pict,
pdata: 10005,
ttype: trm::GroupType::Pict(10005),
lines: 21,
text: "~text interface terminal malfunction error ~2992dud\n\nIt\'s too bad, perhaps if I could have delayed the Pfhor from using their weapon, I could have sent you to explore the ruins of Lh\'owon, perhaps what you found would give us the answers that we now need so desparately: how to stop this chaos, the purpose of the station on which you\'re currently standing, and why the chaos hasn\'t come here yet.\n\nBut with each moment the chaos grows, I am doomed to die here, after so many triumphs. I have detected one ship nearby, which I can only guess is being commanded by Tycho. The Pfhor have entered the station, and if you can find a way onto their ship, you may be able to escape. To escape. To escape.\n\n".to_string()
},
trm::Group {
flags: trm::GroupFlags::empty(),
ttype: trm::GroupType::Pict,
pdata: 10006,
ttype: trm::GroupType::Pict(10006),
lines: 21,
text: "~text interface terminal malfunction error ~2992dud\n\nIt\'s too bad, perhaps if I could have delayed the Pfhor from using their weapon, I could have sent you to explore the ruins of Lh\'owon, perhaps what you found would give us the answers that we now need so desparately: how to stop this chaos, the purpose of the station on which you\'re currently standing, and why the chaos hasn\'t come here yet.\n\nBut with each moment the chaos grows, I am doomed to die here, after so many triumphs. I have detected one ship nearby, which I can only guess is being commanded by Tycho. The Pfhor have entered the station, and if you can find a way onto their ship, you may be able to escape. To escape. To escape.\n\n".to_string()
},
trm::Group {
flags: trm::GroupFlags::empty(),
ttype: trm::GroupType::Logoff,
pdata: 1600,
ttype: trm::GroupType::Logoff(1600),
lines: 1,
text: "\n".to_string()
}
@ -53,28 +47,24 @@ vec![
trm::Group {
flags: trm::GroupFlags::empty(),
ttype: trm::GroupType::Unfinished,
pdata: 253,
lines: 0,
text: "".to_string()
},
trm::Group {
flags: trm::GroupFlags::empty(),
ttype: trm::GroupType::Logon,
pdata: 1610,
ttype: trm::GroupType::Logon(1610),
lines: 1,
text: "\n".to_string()
},
trm::Group {
flags: trm::GroupFlags::empty(),
ttype: trm::GroupType::Pict,
pdata: 10008,
ttype: trm::GroupType::Pict(10008),
lines: 14,
text: "\n\n\nspurious interrupt\nunauthorized access\ninformation contained\n\n<header>\ninformation retrieval aspect 30-f\n<body>\n~eibat weapo` 3941\nsiklicar 21`perie1ces\nclwaa\n\n".to_string()
},
trm::Group {
flags: trm::GroupFlags::empty(),
ttype: trm::GroupType::Logoff,
pdata: 1610,
ttype: trm::GroupType::Logoff(1610),
lines: 1,
text: "\n".to_string()
}
@ -87,35 +77,30 @@ vec![
trm::Group {
flags: trm::GroupFlags::empty(),
ttype: trm::GroupType::Unfinished,
pdata: 253,
lines: 0,
text: "".to_string()
},
trm::Group {
flags: trm::GroupFlags::empty(),
ttype: trm::GroupType::Logon,
pdata: 1619,
ttype: trm::GroupType::Logon(1619),
lines: 1,
text: "".to_string()
},
trm::Group {
flags: trm::GroupFlags::empty(),
ttype: trm::GroupType::Pict,
pdata: 10007,
ttype: trm::GroupType::Pict(10007),
lines: 16,
text: "\n\n\nthousands are sailing\nthe same self\t\tthe only self\n\nself willed the peril of a thousand fates\n\na line of infinite ends\t\tfinite finishing\nthe one remains oblique and pure\n\narching to the single point of consciousness\n\nfind yourself\nstarting back\n".to_string()
},
trm::Group {
flags: trm::GroupFlags::empty(),
ttype: trm::GroupType::Logoff,
pdata: 1619,
ttype: trm::GroupType::Logoff(1619),
lines: 1,
text: "".to_string()
},
trm::Group {
flags: trm::GroupFlags::empty(),
ttype: trm::GroupType::TeleInter,
pdata: 1,
ttype: trm::GroupType::TeleInter(1),
lines: 1,
text: "".to_string()
}
@ -134,28 +119,24 @@ vec![
trm::Group {
flags: trm::GroupFlags::empty(),
ttype: trm::GroupType::Unfinished,
pdata: 0,
lines: 0,
text: "".to_string()
},
trm::Group {
flags: trm::GroupFlags::empty(),
ttype: trm::GroupType::Logon,
pdata: 1619,
ttype: trm::GroupType::Logon(1619),
lines: 1,
text: "".to_string()
},
trm::Group {
flags: trm::GroupFlags::empty(),
ttype: trm::GroupType::Pict,
pdata: 10007,
ttype: trm::GroupType::Pict(10007),
lines: 84,
text: "534954210001000034EE724C6175020000000016\n000000001E48617473204F666620746F20456967\n6874204E696E657465656E2E736974946AAA0000\n0000000000000000000000000000000000000000\n00000000FFFFFFFF53495444534954210100AE1A\nCAD4AE1ACAD40000000000003468000000000000\n34680000CAF90000000000004A21534954210001\n00003468724C617502000000001600000D0D1A48\n617473204F666620746F204569676874204E696E\n657465656E0001537E94897D0000000000000000\n000000000000000000000000000000000000FFFF\nFFFF7363653232362EB00100AE1AA710AE1ACAC4\n0000087A0000738C0000057100002E7177DCF747\n000000000000EFB60B005D796C65E775CA8EB023\n47B984114E5646B8114E5847B8E498849F479E93\nE347FBB5EC08CB8F30B24A23C7098B3CCBC31B2D\nC2093D4EB81EDE2897F09530326087377E86279C\n3C7BF192A31CCFC988BFD7D993A747669C1C2313\n4638E164CA927072FC24CF8F1CA79EFC0566A33C\n2F3D0302A02F3BC28D3C2748648403AF029F2380\n95318331965FC5D8227C99FD62F78617DE1918ED\n6EC311B368530FEB69375AA82C5BBDE3E0F0D1E2\n5ECE8BC343C5DE23CEC07071CF91417BD8B60737\n984CFF8E1EB2BBBB376DF816BA3E622FCFCBF867\nBE5EFD7981B1A3A7A0FA3DAD4BF36516D4BEC4DA\nD8BC5A8D6A79F00A6B79A051D916AA871D7F88CA\nB550178D9602E760B7F656A37A1EA1CC05D4968B\nFA92BD9CBD3C775FCD6EB0E566B38335535C772C\n7C5B483D065BF2E01E1DF71E61F53CE46C03D782\n7D0BD3B7FB1B6CCB186BCE0D33B66EBD7061EB56\n6676754D4E7675B14CA170FD7AA1A0592727B52D\n8C9D3E8D1DC61E3BA6DD626CDF3E1D55C6D6AFC7\nBE9831B031B6249F1F1FC7A53D9FBF74097BC7DA\nB5E3E31B37B2A55D5DE3E36082E207D3F26A6E5F\n7785162791F485EF70EEA6BC5D702E70EAAF5A9C\nD7852983F8477AFE2AAD4824485C7000FBAE552A\nF5F6F6CFF2BCE3F2332072DC599E8A8CE74BF50B\n6DFE8DCC64CC4F85E13A759E87206765BF55DADD\nE086CB1B22C7CFF4734780516B7D63A565F5BEEE\nC9F857DA3C45AB7C40AC534F5B1677B8F0A4544A\nCAC86BA8BB6424718366556724DCF727AC7269DB\n9A0B57AF5E11DEEFF4DC1F944D7CB75CEA4D4BF9\n4FAC7E47F3C63F532101BBEF9E7FEBFC9713BB4A\n80C5093D798D96D4606319167881FA9E96A8C817\nE270A9F48113FE454FDCA045896F5B5C48F51DE5\n93C83F6B95B78BF03AADBC05915120643C818374\nB917DCA4A5B7B42D5FC16E15DFA2A76628839400\n6746524E53A62665FCC3DC1A4E5353B1B84EEBC0\n51B3ACBE9B9419EEB7635CA7AC72052453963508\n12C1BC3A59292D4BA4904122EC3EA29AF0827BD4\n069B284A258B53481076384B591548DF8B6F6A43\n8413C4B78981A02326AA0A518107C20B536A438E\n02901E32404F2912149CDC1F92CEB3C4AAE2FB64\nAA44A594F347A9CA4F860D3482F48240A9C80BEE\n5247E272217C8E8C925233C492BA326A32A54EE4\n0BCD61EF97440B1A77995507C664AAC2818C5C22\n83D0E7FBEF50CE75C22A17C09830AC8E8008EF1E\n99AE13DCD7668AF00EA2E387C10CE5A41825DF1B\nADD336734CC4B364F8DE0C3543D3771DE8483CAF\nE679506A26521BE78B789A0C11CE472F441EE3DA\n3465234908629D4A06C9FBD494334801B232709B\nB2BE47910F01C6949AA5B6314A22A450ABD1C954\n3AC74604658BC67C71B2A21444EC324F5AA3B364\n9E295590B9B1FEC191F2765871B6B473AA54AEC0\n8A2A4768D9899433F860564B7D9052E5BC729BCC\nDAE13E6839B3DDB62B2E1FA8D3818E1AAA432176\n2AB2079087D5480252C703A2041F643751A2DC1F\nD6ECDE4A83323E0A2745E452758F3223659D1778\n3EFA37B52591EB8EDEA7BCF47C118E090FED78D0\nA4C44F79DEE64E00FF7D81B60852CAD704BA5404\n88BFC8A0BE3D585673454366E74A20BC49CC6FF0\n9C00A1A4A82EDB43D48737D7F60E0CCDF91201A5\n5A1D622C58825E4182337050071011C751C94821\nC94910016AA8F8FA5C13E5E228503034554D6A9F\nE24E8522FDD210A10E3DEAA5011715083ACFE9C6\n10217AA136E25CA3FCA748915D215A760E084D37\n7F2E802781D35AFD090751297CA1DBDC918A6A3E\nDAE51A757C464CA602C522FF243CB2F0CA393EFF\nD27A9CC3C08349FCF4145971B9D17A3EE2F8F1C2\nEB10A7FFF6C73FF806E3FEFD53C0287F38D1A91F\n9B6BD83E66BCB6735B19042B766EDBBB07986773\n65EDDBFC3C5368368B2C7760EE8800E21DFB933E\nAD280D8DBCF9B65DDC32A2FF317875C8191AFC1F\n000E00EB37EAB4CECBEBECBC0CCF06793DBD2EB3\n".to_string()
},
trm::Group {
flags: trm::GroupFlags::empty(),
ttype: trm::GroupType::Logoff,
pdata: 1619,
ttype: trm::GroupType::Logoff(1619),
lines: 1,
text: "".to_string()
}

View File

@ -1,14 +0,0 @@
use maraiah::{durandal::{bin, fixed::*},
marathon::map};
#[test]
fn read_epnt_must_process_this()
{
assert_eq!(bin::rd_array(INPUT, map::read_epnt).unwrap(),
OUTPUT.to_vec());
}
const INPUT: &'static [u8] = include_bytes!("data/epnt.in");
const OUTPUT: [map::Point; 54] = include!("data/epnt.out");
// EOF

View File

@ -1,14 +0,0 @@
use maraiah::marathon::machdr::*;
// TODO: missing test data for applesingle
#[test]
fn macbin_must_process_this()
{
assert_eq!(check_macbin(INPUT_MACBIN), Some(128));
assert_eq!(try_mac_header(INPUT_MACBIN), 128);
}
const INPUT_MACBIN: &'static [u8] = include_bytes!("data/macbin.in");
// EOF

52
tests/map.rs Normal file
View File

@ -0,0 +1,52 @@
use maraiah::{durandal::{bin, fixed::*}, marathon::{map, trm}};
#[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 read_minf_must_process()
{
const INPUT: &[u8] = include_bytes!("data/minf.in");
let out = map::Minf{env_code: 0,
physi_id: 1,
music_id: 1,
missi_flags: map::MsnFlags::Repair,
envir_flags: map::EnvFlags::empty(),
entry_flags: map::EntFlags::Solo | map::EntFlags::CoOp,
level_name: "Waterloo Waterpark".to_string()};
assert_eq!(map::read_minf(INPUT).unwrap(), out);
}
#[test]
fn read_epnt_must_process()
{
const INPUT: &[u8] = include_bytes!("data/epnt.in");
const OUTPUT: [map::Point; 54] = include!("data/epnt.out");
assert_eq!(bin::rd_array(INPUT, map::read_epnt).unwrap(), OUTPUT.to_vec());
}
// EOF

View File

@ -1,18 +0,0 @@
use maraiah::marathon::map;
#[test]
fn read_minf_must_process_map0()
{
assert_eq!(map::read_minf(INPUT).unwrap(),
map::Minf{env_code: 0,
physi_id: 1,
music_id: 1,
missi_flags: map::MsnFlags::Repair,
envir_flags: map::EnvFlags::empty(),
entry_flags: map::EntFlags::Solo | map::EntFlags::CoOp,
level_name: "Waterloo Waterpark".to_string()});
}
const INPUT: &'static [u8] = include_bytes!("data/minf.in");
// EOF

23
tests/misc.rs Normal file
View File

@ -0,0 +1,23 @@
use maraiah::{durandal::image::Color8, marathon::{machdr, pict}};
#[test]
fn get_clut_must_process_this()
{
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 macbin_must_process_this()
{
const INPUT: &[u8] = include_bytes!("data/macbin.in");
assert_eq!(machdr::check_macbin(INPUT), Some(128));
assert_eq!(machdr::try_mac_header(INPUT), 128);
}
// TODO: missing test data for applesingle
// EOF

View File

@ -1,27 +0,0 @@
use maraiah::{durandal::bin, marathon::trm};
#[test]
fn read_term_must_process_map0()
{
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);
}
}
}
const INPUT: &'static [u8] = include_bytes!("data/term.in");
// EOF