Compare commits

...

4 Commits

Author SHA1 Message Date
an 1a802c47db fix 16 bit sounds 2019-02-17 22:25:20 -05:00
an 58cbdd1a57 add Sounds reader 2019-02-17 21:17:35 -05:00
an a8f04cc01c rename Chunked to ArrayRead and remove Chunker 2019-02-17 21:17:02 -05:00
an b135077b8f don't use casts like that 2019-02-17 21:16:01 -05:00
12 changed files with 512 additions and 176 deletions

View File

@ -426,6 +426,9 @@ actual PCM data.
All integers here are big-endian unless specified.
All unspecified bytes must be set to `0` when written, although when reading
should not be checked as they may be garbage.
The type "`fixed`" refers to a 32-bit fixed point number with the format 15.16s
(the lower 16 bits are fractional, the upper 15 are integral, and one bit for
sign.)
@ -1079,10 +1082,32 @@ but due to padding, the real length is 34.
- `SndBeg`, `SndKey` and `SndEnd` are the sounds played at the first, key and
last frame of this sequence.
### Bitmap Header ###
Bitmap Header is 26 bytes.
Each Bitmap Header is followed by either `Height * 4` or `Width * 4` empty
bytes which must be skipped.
| Name | Type | Offset |
| ---- | ---- | ------ |
| `Width` | `u16` | `0` |
| `Height` | `u16` | `2` |
| `Pitch` | `u16` | `4` |
| `Flags` | `u16` | `6` |
| `Depth` | `u16` | `8` |
- `Width` is the number of pixels on the horizontal axis.
- `Height` is the number of pixels on the vertical axis.
- `Pitch` is either the number of pixels per row if row ordered, per column if
column ordered, or `65535` if the data is transparency RLE compressed.
- `Flags` is a Bitmap Flags bit field.
- `Depth` must always be `8`.
## Sounds ##
Sounds files start with a header followed by all of the actual sound
definitions. Simple enough.
definitions. Each sound starts with a Carbon Sound Header.
### Sounds Header ###
@ -1113,24 +1138,69 @@ Sound Definition is 64 bytes.
| Name | Type | Offset |
| ---- | ---- | ------ |
| sound_code | `u16` | `0` |
| `Behaviour` | `u16` | `2` |
| `Code` | `u16` | `0` |
| `Volume` | `u16` | `2` |
| `Flags` | `u16` | `4` |
| `Chance` | `u16` | `6` |
| `PitchLo` | `fixed` | `8` |
| `PitchHi` | `fixed` | `12` |
| permutations | `i16` | `16` |
| perms_played | `u16` | `18` |
| group_offset | `u32` | `20` |
| single_length | `u32` | `24` |
| total_length | `u32` | `28` |
| sound_offsets | `u32[5]` | `32` |
| `NumOfs` | `u16` | `16` |
| `GroupOffset` | `u32` | `20` |
| `Size` | `u32` | `24` |
| `GroupSize` | `u32` | `28` |
| `AddOffset` | `u32[5]` | `32` |
- `Behaviour` is a Sound Behaviour enumeration.
- `Code` is an Object ID referencing something (TODO.)
- `Volume` is a Sound Behaviour enumeration.
- `Flags` is a Sound Definition Flags bit field.
- `Chance` is the chance out of `65535` that the sound will not play.
- `PitchLo` is the lower random pitch bound, if `0` then it will be `1.0`.
- `PitchHi` is the high random pitch bound, if `0` then it will be `PitchLo`.
- `NumOfs` is the number of random sounds to pick from `AddOffset`.
- `GroupOffset` is the starting offset for each additive sound offset.
- `Size` is the sound of an individual sound in the group.
- `GroupSize` is the total size of all sounds in the group.
- `AddOffset` is the offset added to `GroupOffset` to get an individual sound.
While it is an array of `NumOfs` offsets, it has a fixed size in the format.
### Carbon Sound Header ###
Carbon Sound Header is 21 bytes.
The sound format is from Carbon's `SoundHeader` structures. It's used primarily
in System 7 programs as `snd` resources but in OS X it was deprecated in favor
of QuickTime. HFS still has Resource Forks but they aren't used anymore. I
don't imagine this format was ever used for anything else, except for Marathon,
which embeds it in the Sound files directly, instead of using `snd` resources
(which have a larger structure consisting of a resource header and sound
commands rather than just the header and sample data.)
| Name | Type | Offset |
| ---- | ---- | ------ |
| `Size` | `u32` | `4` |
| `SampleRate` | `u16` | `8` |
| `LoopBeg` | `u32` | `12` |
| `LoopEnd` | `u32` | `16` |
| `Magic` | `u8` | `20` |
- If `Magic` is `$00` nothing else needs to be done and raw signed 8-bit mono
PCM sample data starts at byte 22. If it is `$FF` it is followed by a Carbon
Extended Sound Header, or if it is `$FE` it is followed by a Carbon Compressed
Sound Header. The compressed sound header is not documented because it is not
actually used by Marathon.
### Carbon Extended Sound Header ###
Carbon Extended Sound Header is 42 bytes.
The extended sound header contains more useless information and even several
fields that do absolutely nothing. Wow. At least it can store 16 bit samples.
It also has an 80-bit float in it, which horrifies me greatly. There's only one
actually useful field.
| Name | Type | Offset |
| ---- | ---- | ------ |
| `SampleBits` | `u16` | `26` |
# ENUMERATIONS ################################################################

View File

@ -4,14 +4,14 @@ use crate::durandal::err::*;
use serde::Serialize;
use std::{fmt, num::NonZeroU16, slice::SliceIndex};
/// Returns a byte array from `b` at `i`.
/// Returns a `[u8]` from `b` at `i`.
pub fn c_data<I>(b: &[u8], i: I) -> ResultS<&<I as SliceIndex<[u8]>>::Output>
where I: SliceIndex<[u8]>
{
ok!(b.get(i), "not enough data")
}
/// Returns a byte from `b` at `i`.
/// Returns a `u8` from `b` at `i`.
pub fn c_byte(b: &[u8], i: usize) -> ResultS<u8>
{
match b.get(i) {
@ -70,6 +70,16 @@ pub fn o_u32b(b: &[u8], i: usize) -> Option<u32>
}
}
/// Returns a big-endian `i32` from `b` at `i`.
pub fn o_i32b(b: &[u8], i: usize) -> Option<i32>
{
if i + 3 < b.len() {
Some(i32::from_be_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]))
} else {
None
}
}
/// Returns a big-endian `u16` from `b` at `i`.
pub fn o_u16b(b: &[u8], i: usize) -> Option<u16>
{
@ -80,24 +90,37 @@ pub fn o_u16b(b: &[u8], i: usize) -> Option<u16>
}
}
/// Returns a big-endian `i32` from `b` at `i`.
pub fn o_i32b(b: &[u8], i: usize) -> Option<i32>
{
match o_u32b(b, i) {
Some(n) => Some(n as i32),
None => None,
}
}
/// Returns a big-endian `i16` from `b` at `i`.
pub fn o_i16b(b: &[u8], i: usize) -> Option<i16>
{
match o_u16b(b, i) {
Some(n) => Some(n as i16),
None => None,
if i + 1 < b.len() {
Some(i16::from_be_bytes([b[i], b[i + 1]]))
} else {
None
}
}
/// Reads an array of any type with the `ArrayRead` trait.
pub fn c_array<T: ArrayRead>(b: &[u8]) -> ResultS<Vec<T>>
{
let mut v = Vec::with_capacity(b.len() / T::SIZE);
let mut p = 0;
while p < b.len() {
v.push(T::read(&b[p..])?);
p += T::SIZE;
}
Ok(v)
}
/// Trait for any structure which can be deserialized from an array of bytes.
pub trait ArrayRead: Sized
{
const SIZE: usize;
fn read(b: &[u8]) -> ResultS<Self>;
}
impl ObjID
{
/// Creates an `ObjID` from a `u16`.
@ -124,7 +147,7 @@ impl ObjID
{
match self.0 {
None => None,
Some(n) => Some(n.get()),
Some(n) => Some(n.get() - 1),
}
}
}

View File

@ -1,32 +0,0 @@
//! Traits for data which can be read as a sized array.
use crate::durandal::err::*;
pub trait Chunked<T>
{
const SIZE_CHUNK: usize;
fn read(b: &[u8]) -> ResultS<T>;
}
pub trait Chunker<T>
{
fn chunk(b: &[u8]) -> ResultS<T>;
}
impl<T: Chunked<T>> Chunker<Vec<T>> for T
{
fn chunk(b: &[u8]) -> ResultS<Vec<T>>
{
let mut v = Vec::with_capacity(b.len() / T::SIZE_CHUNK);
let mut p = 0;
while p < b.len() {
v.push(T::read(&b[p..])?);
p += T::SIZE_CHUNK;
}
Ok(v)
}
}
// EOF

View File

@ -99,9 +99,9 @@ pub trait Color
impl Image16
{
/// Creates a new Image16.
pub fn new(w: usize, h: usize) -> Image16
pub fn new(w: usize, h: usize) -> Self
{
Image16{w, h, cr: Vec::with_capacity(w * h)}
Self{w, h, cr: Vec::with_capacity(w * h)}
}
}
@ -121,9 +121,9 @@ impl Image for Image16
impl Image8
{
/// Creates a new Image8.
pub fn new(w: usize, h: usize) -> Image8
pub fn new(w: usize, h: usize) -> Self
{
Image8{w, h, cr: Vec::with_capacity(w * h)}
Self{w, h, cr: Vec::with_capacity(w * h)}
}
}

View File

@ -6,11 +6,11 @@ pub mod cenum;
pub mod err;
pub mod bin;
pub mod chunk;
pub mod crc;
pub mod file;
pub mod fixed;
pub mod image;
pub mod sound;
pub mod text;
// EOF

118
src/durandal/sound.rs Normal file
View File

@ -0,0 +1,118 @@
//! Sound representation.
use crate::durandal::err::*;
use std::io;
pub fn write_wav(out: &mut impl io::Write, snd: &impl Sound) -> ResultS<()>
{
let rate = u32::from(snd.rate());
let bps = rate * 2;
let ssize = bps * snd.len() as u32;
let fsize = 36 + ssize;
out.write_all(b"RIFF")?;
out.write_all(&fsize.to_le_bytes())?;
out.write_all(b"WAVE")?;
out.write_all(b"fmt ")?;
out.write_all(&16u32.to_le_bytes())?;
out.write_all(&1u16.to_le_bytes())?; // PCM
out.write_all(&1u16.to_le_bytes())?; // mono
out.write_all(&rate.to_le_bytes())?; // rate
out.write_all(&bps.to_le_bytes())?; // bytes per second
out.write_all(&2u16.to_le_bytes())?; // block alignment
out.write_all(&16u16.to_le_bytes())?; // bits per sample
out.write_all(b"data")?;
out.write_all(&ssize.to_le_bytes())?;
for p in 0..snd.len() {
let sample = snd.index(p);
out.write_all(&sample.to_le_bytes())?;
}
if ssize & 1 == 1 {
out.write_all(&[0])?;
}
Ok(())
}
pub trait Sound
{
fn rate(&self) -> u16;
fn len(&self) -> usize;
fn index(&self, p: usize) -> i16;
fn lp_beg(&self) -> usize;
fn lp_end(&self) -> usize;
fn is_empty(&self) -> bool
{
self.len() == 0
}
fn get(&self, p: usize) -> Option<i16>
{
if p < self.len() {
Some(self.index(p))
} else {
None
}
}
}
impl Sound16
{
/// Creates a new Sound16.
pub fn new(rate: u16, lp_beg: usize, lp_end: usize, len: usize) -> Self
{
Self{rate, lp_beg, lp_end, data: Vec::with_capacity(len)}
}
/// Creates a new Sound16 from an unsigned 8-bit stream.
pub fn new_from_8(rate: u16, lp_beg: usize, lp_end: usize, b: &[u8]) -> Self
{
let mut snd = Sound16::new(rate, lp_beg, lp_end, b.len());
for &sample in b {
snd.data.push(Sound16::sample_from_8(sample));
}
snd
}
/// Creates a new Sound16 from a signed 16-bit stream.
pub fn new_from_16(rate: u16, lp_beg: usize, lp_end: usize, b: &[u8]) -> Self
{
let mut snd = Sound16::new(rate, lp_beg, lp_end, b.len() / 2);
for i in (0..b.len()).step_by(2) {
snd.data.push(i16::from_le_bytes([b[i], b[i + 1]]));
}
snd
}
/// Creates a signed 16-bit sample from an unsigned 8-bit sample.
pub fn sample_from_8(sample: u8) -> i16
{
(i16::from(sample) - 0x80) << 8
}
}
impl Sound for Sound16
{
fn rate(&self) -> u16 {self.rate}
fn len(&self) -> usize {self.data.len()}
fn index(&self, p: usize) -> i16 {self.data[p]}
fn lp_beg(&self) -> usize {self.lp_beg}
fn lp_end(&self) -> usize {self.lp_end}
}
pub struct Sound16
{
rate: u16,
lp_beg: usize,
lp_end: usize,
pub data: Vec<i16>,
}
// EOF

View File

@ -1,5 +1,5 @@
use maraiah::{durandal::{bin::*, chunk::*, err::*, file::*, image::*, text::*},
marathon::{machdr, map, pict, shp, term, wad}};
use maraiah::{durandal::{bin::*, err::*, file::*, image::*, sound::*, text::*},
marathon::{machdr, map, pict, shp, snd, term, wad}};
use std::{collections::HashSet,
fs,
io::{self, Write}};
@ -31,6 +31,12 @@ fn make_yaml<T>(opt: &Options, data: &T) -> ResultS<()>
Ok(())
}
fn make_wav(fname: &str, snd: &impl Sound) -> ResultS<()>
{
let mut out = io::BufWriter::new(fs::File::create(fname)?);
write_wav(&mut out, snd)
}
fn dump_chunk(opt: &Options, cid: Ident, cnk: &[u8], eid: u16) -> ResultS<()>
{
if opt.wad_all {
@ -44,12 +50,12 @@ fn dump_chunk(opt: &Options, cid: Ident, cnk: &[u8], eid: u16) -> ResultS<()>
let im = pict::load_pict(cnk)?;
make_tga(&format!("{}/pict_{}.tga", opt.out_dir, eid), &im)?;
}
b"Minf" => make_yaml(opt, &map::Minf::chunk(cnk)?)?,
b"EPNT" => make_yaml(opt, &map::Endpoint::chunk(cnk)?)?,
b"PNTS" => make_yaml(opt, &map::Point::chunk(cnk)?)?,
b"LINS" => make_yaml(opt, &map::Line::chunk(cnk)?)?,
b"SIDS" => make_yaml(opt, &map::Side::chunk(cnk)?)?,
b"term" => make_yaml(opt, &term::Terminal::chunk(cnk)?)?,
b"Minf" => make_yaml(opt, &map::read_minf(cnk)?)?,
b"EPNT" => make_yaml(opt, &c_array::<map::Endpoint>(cnk)?)?,
b"PNTS" => make_yaml(opt, &c_array::<map::Point>(cnk)?)?,
b"LINS" => make_yaml(opt, &c_array::<map::Line>(cnk)?)?,
b"SIDS" => make_yaml(opt, &c_array::<map::Side>(cnk)?)?,
b"term" => make_yaml(opt, &term::read_term(cnk)?)?,
_ => (),
}
}
@ -129,6 +135,20 @@ fn process_shp(opt: &Options, b: &[u8]) -> ResultS<()>
Ok(())
}
fn process_snd(_opt: &Options, b: &[u8]) -> ResultS<()>
{
for (c, st) in snd::read_sounds(b)?.iter().enumerate() {
for (k, sd) in st {
for (i, snd) in sd.sounds.iter().enumerate() {
let fname = format!("out/snd{}_{}_{}.wav", c, k, i);
make_wav(&fname, snd)?;
}
}
}
Ok(())
}
fn main() -> ResultS<()>
{
use argparse::*;
@ -211,6 +231,7 @@ fn main() -> ResultS<()>
match typ {
"wad:" => process_wad(&opt, b),
"shp:" => process_shp(&opt, b),
"snd:" => process_snd(&opt, b),
_ => Err(err_msg("invalid file type specified on commandline")),
}?;
}

View File

@ -1,13 +1,37 @@
use crate::{durandal::{bin::*, chunk::*, err::*, fixed::*,
use crate::{durandal::{bin::*, err::*, fixed::*,
text::mac_roman_conv},
marathon::xfer::TransferMode};
use bitflags::bitflags;
use serde::Serialize;
use std::fmt;
impl Chunked<Point> for Point
fn read_sidetex(b: &[u8]) -> ResultS<SideTex>
{
const SIZE_CHUNK: usize = 4;
let offs = Point::read(c_data(b, 0..4)?)?;
let tex_id = c_u16b(b, 4)?;
let tex_id = ObjID::from_repr(tex_id);
Ok(SideTex{offs, tex_id})
}
pub fn read_minf(b: &[u8]) -> ResultS<Minf>
{
let env_code = c_u16b(b, 0)?;
let physi_id = c_u16b(b, 2)?;
let music_id = c_u16b(b, 4)?;
let msn_flag = c_u16b(b, 6)?;
let env_flag = c_u16b(b, 8)?;
let levelnam = mac_roman_conv(c_data(b, 18..84)?);
let ent_flag = c_u32b(b, 84)?;
let msn_flag = ok!(MsnFlags::from_bits(msn_flag), "bad MsnFlags")?;
let env_flag = ok!(EnvFlags::from_bits(env_flag), "bad EnvFlags")?;
let ent_flag = ok!(EntFlags::from_bits(ent_flag), "bad EntFlags")?;
Ok(Minf{env_code, physi_id, music_id, msn_flag, env_flag, ent_flag,
levelnam})
}
impl ArrayRead for Point
{
const SIZE: usize = 4;
fn read(b: &[u8]) -> ResultS<Self>
{
@ -19,9 +43,9 @@ impl Chunked<Point> for Point
}
}
impl Chunked<Endpoint> for Endpoint
impl ArrayRead for Endpoint
{
const SIZE_CHUNK: usize = 16;
const SIZE: usize = 16;
fn read(b: &[u8]) -> ResultS<Self>
{
@ -37,9 +61,9 @@ impl Chunked<Endpoint> for Endpoint
}
}
impl Chunked<Line> for Line
impl ArrayRead for Line
{
const SIZE_CHUNK: usize = 32;
const SIZE: usize = 32;
fn read(b: &[u8]) -> ResultS<Self>
{
@ -62,28 +86,17 @@ impl Chunked<Line> for Line
}
}
impl SideTex
impl ArrayRead for Side
{
fn read(b: &[u8]) -> ResultS<Self>
{
let offs = Point::read(c_data(b, 0..4)?)?;
let tex_id = c_u16b(b, 4)?;
let tex_id = ObjID::from_repr(tex_id);
Ok(SideTex{offs, tex_id})
}
}
impl Chunked<Side> for Side
{
const SIZE_CHUNK: usize = 64;
const SIZE: usize = 64;
fn read(b: &[u8]) -> ResultS<Self>
{
let stype = c_u16b(b, 0)?;
let flags = c_u16b(b, 2)?;
let tex_pri = SideTex::read(c_data(b, 4..10)?)?;
let tex_sec = SideTex::read(c_data(b, 10..16)?)?;
let tex_tra = SideTex::read(c_data(b, 16..22)?)?;
let tex_pri = read_sidetex(c_data(b, 4..10)?)?;
let tex_sec = read_sidetex(c_data(b, 10..16)?)?;
let tex_tra = read_sidetex(c_data(b, 16..22)?)?;
let ex_tleft = Point::read(c_data(b, 22..26)?)?;
let ex_trigh = Point::read(c_data(b, 26..30)?)?;
let ex_bleft = Point::read(c_data(b, 30..34)?)?;
@ -105,25 +118,6 @@ impl Chunked<Side> for Side
}
}
impl Chunker<Minf> for Minf
{
fn chunk(b: &[u8]) -> ResultS<Self>
{
let env_code = c_u16b(b, 0)?;
let physi_id = c_u16b(b, 2)?;
let music_id = c_u16b(b, 4)?;
let msn_flag = c_u16b(b, 6)?;
let env_flag = c_u16b(b, 8)?;
let levelnam = mac_roman_conv(c_data(b, 18..84)?);
let ent_flag = c_u32b(b, 84)?;
let msn_flag = ok!(MsnFlags::from_bits(msn_flag), "bad MsnFlags")?;
let env_flag = ok!(EnvFlags::from_bits(env_flag), "bad EnvFlags")?;
let ent_flag = ok!(EntFlags::from_bits(ent_flag), "bad EntFlags")?;
Ok(Minf{env_code, physi_id, music_id, msn_flag, env_flag, ent_flag,
levelnam})
}
}
#[derive(Serialize)]
pub struct Point
{
@ -199,9 +193,9 @@ bitflags! {
#[derive(Serialize)]
pub struct EndpFlags: u16
{
const Solid = 0x00_01;
const SameHeight = 0x00_02;
const Transparent = 0x00_04;
const Solid = 1;
const SameHeight = 1 << 1;
const Transparent = 1 << 2;
}
}
@ -209,12 +203,12 @@ bitflags! {
#[derive(Serialize)]
pub struct LineFlags: u16
{
const TransSide = 0x02_00;
const ElevVar = 0x04_00;
const Elevation = 0x08_00;
const Landscape = 0x10_00;
const Transparent = 0x20_00;
const Solid = 0x40_00;
const TransSide = 1 << 9;
const ElevVar = 1 << 10;
const Elevation = 1 << 11;
const Landscape = 1 << 12;
const Transparent = 1 << 13;
const Solid = 1 << 14;
}
}
@ -222,14 +216,14 @@ bitflags! {
#[derive(Serialize)]
pub struct SideFlags: u16
{
const Status = 0x00_01;
const Panel = 0x00_02;
const Repair = 0x00_04;
const ItemUse = 0x00_08;
const Lighted = 0x00_10;
const CanDestroy = 0x00_20;
const HitOnly = 0x00_40;
const ItemOpt = 0x00_80;
const Status = 1;
const Panel = 1 << 1;
const Repair = 1 << 2;
const ItemUse = 1 << 3;
const Lighted = 1 << 4;
const CanDestroy = 1 << 5;
const HitOnly = 1 << 6;
const ItemOpt = 1 << 7;
}
}
@ -237,19 +231,19 @@ bitflags! {
#[derive(Serialize)]
pub struct EnvFlags: u16
{
const Vacuum = 0x00_01;
const Magnetic = 0x00_02;
const Rebellion = 0x00_04;
const LowGrav = 0x00_08;
const M1Glue = 0x00_10;
const LavaFloor = 0x00_20;
const Rebellion2 = 0x00_40;
const Music = 0x00_80;
const TermPause = 0x01_00;
const M1Monster = 0x02_00;
const M1Weps = 0x04_00;
const NetPlay = 0x20_00;
const Solo = 0x40_00;
const Vacuum = 1;
const Magnetic = 1 << 1;
const Rebellion = 1 << 2;
const LowGrav = 1 << 3;
const M1Glue = 1 << 4;
const LavaFloor = 1 << 5;
const Rebellion2 = 1 << 6;
const Music = 1 << 7;
const TermPause = 1 << 8;
const M1Monster = 1 << 9;
const M1Weps = 1 << 10;
const NetPlay = 1 << 13;
const Solo = 1 << 14;
}
}
@ -257,14 +251,14 @@ bitflags! {
#[derive(Serialize)]
pub struct EntFlags: u32
{
const Solo = 0x00_01;
const CoOp = 0x00_02;
const Carnage = 0x00_04;
const KTMWTB = 0x00_08;
const KOTH = 0x00_10;
const Defense = 0x00_20;
const Rugby = 0x00_40;
const CTF = 0x00_80;
const Solo = 1;
const CoOp = 1 << 1;
const Carnage = 1 << 2;
const KTMWTB = 1 << 3;
const KOTH = 1 << 4;
const Defense = 1 << 5;
const Rugby = 1 << 6;
const CTF = 1 << 7;
}
}
@ -272,11 +266,11 @@ bitflags! {
#[derive(Serialize)]
pub struct MsnFlags: u16
{
const Extermination = 0x00_01;
const Exploration = 0x00_02;
const Retrieval = 0x00_04;
const Repair = 0x00_08;
const Rescue = 0x00_10;
const Extermination = 1;
const Exploration = 1 << 1;
const Retrieval = 1 << 2;
const Repair = 1 << 3;
const Rescue = 1 << 4;
}
}

View File

@ -4,6 +4,7 @@ pub mod machdr;
pub mod map;
pub mod pict;
pub mod shp;
pub mod snd;
pub mod term;
pub mod wad;
pub mod xfer;

View File

@ -392,8 +392,8 @@ pub type CollectionDef = (Option<Collection>, Option<Collection>);
bitflags! {
struct BmpFlags: u16
{
const ColumnMajor = 0x80_00;
const Transparent = 0x40_00;
const Transparent = 1 << 14;
const ColumnMajor = 1 << 15;
}
}
@ -401,9 +401,9 @@ bitflags! {
#[derive(Serialize)]
pub struct FrameFlags: u16
{
const Obscure = 0x20_00;
const FlipY = 0x40_00;
const FlipX = 0x80_00;
const Obscure = 1 << 13;
const FlipY = 1 << 14;
const FlipX = 1 << 15;
}
}

144
src/marathon/snd.rs Normal file
View File

@ -0,0 +1,144 @@
//! Marathon Sounds format handling.
use crate::durandal::{bin::*, err::*, fixed::*, sound::*};
use bitflags::bitflags;
use serde::Serialize;
use std::collections::HashMap;
fn sound(b: &[u8]) -> ResultS<Sound16>
{
let len = c_u32b(b, 4)? as usize;
let rate = c_u16b(b, 8)?;
let lp_beg = c_u32b(b, 12)? as usize;
let lp_end = c_u32b(b, 16)? as usize;
let magic = c_byte(b, 20)?;
match magic {
0 => {
let stream = c_data(b, 22..22 + len)?;
Ok(Sound16::new_from_8(rate, lp_beg, lp_end, stream))
}
0xFF => {
let len = c_u32b(b, 22)? as usize;
match c_u16b(b, 48)? {
16 => {
let stream = c_data(b, 63..63 + len * 2)?;
Ok(Sound16::new_from_16(rate, lp_beg, lp_end, stream))
}
_ => {
let stream = c_data(b, 63..63 + len)?;
Ok(Sound16::new_from_8(rate, lp_beg, lp_end, stream))
}
}
}
_ => bail!("invalid magic number"),
}
}
fn sound_def(b: &[u8]) -> ResultS<Option<(Vec<usize>, u16, SoundDef)>>
{
let index = c_u16b(b, 0)?;
let volume = c_u16b(b, 2)?;
let flags = c_u16b(b, 4)?;
let chance = c_u16b(b, 6)?;
let pitch_lo = c_u32b(b, 8)?;
let pitch_hi = c_u32b(b, 12)?;
let n_sounds = c_u16b(b, 16)? as usize;
let grp_ofs = c_u32b(b, 20)? as usize;
let volume = Volume::from_repr(volume)?;
let flags = ok!(SoundFlags::from_bits(flags), "bad SoundFlags")?;
let pitch_lo = Fixed::from_bits(pitch_lo);
let pitch_hi = Fixed::from_bits(pitch_hi);
if index == u16::max_value() {
return Ok(None);
}
if n_sounds > 5 {
bail!("too many sounds");
}
let mut ofs = Vec::with_capacity(n_sounds);
let mut p = 36;
for _ in 0..n_sounds {
ofs.push(grp_ofs + c_u32b(b, p)? as usize);
p += 4;
}
Ok(Some((ofs, index, SoundDef{volume, flags, chance, pitch_lo, pitch_hi,
sounds: Vec::with_capacity(n_sounds)})))
}
pub fn read_sounds(b: &[u8]) -> ResultS<Vec<SoundTable>>
{
let version = c_u32b(b, 0)?;
let magic = c_iden(b, 4)?;
let src_num = c_u16b(b, 8)? as usize; // TODO
let snd_num = c_u16b(b, 10)? as usize;
if version != 1 || magic != *b"snd2" {
bail!("bad sound header");
}
let mut sc = Vec::with_capacity(src_num);
let mut p = 260;
for _ in 0..src_num {
let mut st = HashMap::with_capacity(snd_num);
for _ in 0..snd_num {
if let Some((ofs, idx, mut def)) = sound_def(c_data(b, p..p + 64)?)? {
for &ofs in &ofs {
def.sounds.push(sound(c_data(b, ofs..)?)?);
}
st.insert(idx, def);
}
p += 64;
}
sc.push(st);
}
Ok(sc)
}
pub struct SoundDef
{
pub volume: Volume,
pub flags: SoundFlags,
pub chance: u16,
pub pitch_lo: Fixed,
pub pitch_hi: Fixed,
pub sounds: Vec<Sound16>,
}
type SoundTable = HashMap<u16, SoundDef>;
bitflags! {
#[derive(Serialize)]
pub struct SoundFlags: u16
{
const NoRestart = 1;
const NoChannelSwitch = 1 << 1;
const LessPitchChange = 1 << 2;
const NoPitchChange = 1 << 3;
const NoObstruction = 1 << 4;
const NoMediaObstruct = 1 << 5;
const Ambient = 1 << 6;
}
}
c_enum! {
#[derive(Debug, Serialize)]
pub enum Volume: u16
{
0 => Quiet,
1 => Normal,
2 => Loud,
}
}
// EOF

View File

@ -1,4 +1,4 @@
use crate::durandal::{bin::*, chunk::*, err::*, text::*};
use crate::durandal::{bin::*, err::*, text::*};
use bitflags::bitflags;
use serde::Serialize;
use std::fmt;
@ -42,7 +42,7 @@ fn read_terminal(b: &[u8]) -> ResultS<(usize, Terminal)>
let mut groups = Vec::with_capacity(group_n);
let mut faces = Vec::with_capacity(face_n);
let mut p = 10; // size of header
let mut p = 10;
let text_st = p + SIZE_GROUP * group_n + SIZE_FACE * face_n;
let text = c_data(b, text_st..end)?;
@ -65,21 +65,18 @@ fn read_terminal(b: &[u8]) -> ResultS<(usize, Terminal)>
Ok((end, Terminal{lines, groups, faces}))
}
impl Chunker<Vec<Terminal>> for Terminal
pub fn read_term(b: &[u8]) -> ResultS<Vec<Terminal>>
{
fn chunk(b: &[u8]) -> ResultS<Vec<Terminal>>
{
let mut v = Vec::new();
let mut p = 0;
let mut v = Vec::new();
let mut p = 0;
while p < b.len() {
let (size, trm) = read_terminal(c_data(b, p..)?)?;
v.push(trm);
p += size;
}
Ok(v)
while p < b.len() {
let (size, trm) = read_terminal(c_data(b, p..)?)?;
v.push(trm);
p += size;
}
Ok(v)
}
#[derive(Debug, Serialize)]
@ -112,8 +109,8 @@ bitflags! {
#[derive(Serialize)]
pub struct GroupFlags: u16
{
const DrawOnRight = 0x00_01;
const DrawCenter = 0x00_02;
const DrawOnRight = 1;
const DrawCenter = 1 << 1;
}
}