Compare commits
4 Commits
5e2ad524b0
...
1a802c47db
Author | SHA1 | Date |
---|---|---|
an | 1a802c47db | |
an | 58cbdd1a57 | |
an | a8f04cc01c | |
an | b135077b8f |
|
@ -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 ################################################################
|
||||
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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)}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
37
src/main.rs
37
src/main.rs
|
@ -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")),
|
||||
}?;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue