Compare commits
7 Commits
daebfd2da6
...
ad792b99d3
Author | SHA1 | Date |
---|---|---|
an | ad792b99d3 | |
an | b78f6e8401 | |
an | 9b9a484351 | |
an | 2dd4762c2e | |
an | 5eb8962d2d | |
an | 430457e570 | |
an | 2095e9d58a |
|
@ -1,7 +1,7 @@
|
||||||
//! File utilities.
|
//! File utilities.
|
||||||
|
|
||||||
use crate::err::*;
|
use crate::{err::*, machdr};
|
||||||
use std::fs;
|
use std::{fs, path::Path, io::{SeekFrom, prelude::*}};
|
||||||
|
|
||||||
/// Confirms that the path `p` is a folder.
|
/// Confirms that the path `p` is a folder.
|
||||||
pub fn validate_folder_path(p: &str) -> ResultS<()>
|
pub fn validate_folder_path(p: &str) -> ResultS<()>
|
||||||
|
@ -14,4 +14,54 @@ pub fn validate_folder_path(p: &str) -> ResultS<()>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Opens the file at `path` and skips past any macintosh headers.
|
||||||
|
pub fn open_mac_file<P: AsRef<Path>>(path: P) -> ResultS<fs::File>
|
||||||
|
{
|
||||||
|
let mut fp = fs::File::open(path)?;
|
||||||
|
|
||||||
|
machdr::skip_mac_header(&mut fp);
|
||||||
|
|
||||||
|
Ok(fp)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Drop for SeekBackToStart<T>
|
||||||
|
where T: Seek
|
||||||
|
{
|
||||||
|
fn drop(&mut self) {if self.fl {let _ = self.seek(SeekFrom::Start(0));}}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> std::ops::Deref for SeekBackToStart<T>
|
||||||
|
where T: Seek
|
||||||
|
{
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {&self.sc}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> std::ops::DerefMut for SeekBackToStart<T>
|
||||||
|
where T: Seek
|
||||||
|
{
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {&mut self.sc}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> SeekBackToStart<T>
|
||||||
|
where T: Seek
|
||||||
|
{
|
||||||
|
/// Creates a new `SeekBackToStart` object.
|
||||||
|
pub fn new(sc: T) -> Self {Self{sc, fl: true}}
|
||||||
|
|
||||||
|
/// Sets the seek-back flag.
|
||||||
|
pub fn set_seek(&mut self, fl: bool) {self.fl = fl;}
|
||||||
|
|
||||||
|
/// Returns the seek-back flag.
|
||||||
|
pub fn get_seek(&self) -> bool {self.fl}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Seeks back to the starting position of the inner object when losing scope,
|
||||||
|
/// unless a flag is set.
|
||||||
|
pub struct SeekBackToStart<T: Seek> {
|
||||||
|
sc: T,
|
||||||
|
fl: bool,
|
||||||
|
}
|
||||||
|
|
||||||
// EOF
|
// EOF
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
//! handling, FFI tools, fixed point numbers, and image and sound structures.
|
//! handling, FFI tools, fixed point numbers, and image and sound structures.
|
||||||
//!
|
//!
|
||||||
//! Maraiah also comes with APIs for working with various data formats,
|
//! Maraiah also comes with APIs for working with various data formats,
|
||||||
//! primarily Marathon's. It also handles Macintosh PICT files, Portable PixMap,
|
//! primarily Marathon's. It also handles Macintosh PICT files, PPM, TARGA, RIFF
|
||||||
//! TARGA, RIFF WAVE, and more.
|
//! WAVE, and more.
|
||||||
//!
|
//!
|
||||||
//! # Features
|
//! # Features
|
||||||
//!
|
//!
|
||||||
|
|
|
@ -1,50 +1,77 @@
|
||||||
//! Macintosh archived format header utilities.
|
//! Macintosh archived format header utilities.
|
||||||
|
|
||||||
use crate::bin::*;
|
use crate::file::SeekBackToStart;
|
||||||
|
use std::io::{SeekFrom, prelude::*};
|
||||||
|
|
||||||
/// Checks for an `AppleSingle` header. Returns offset to the resource fork.
|
/// Skips over an Apple Single header. Returns true if one was found.
|
||||||
pub fn check_apple_single(b: &[u8]) -> Option<usize>
|
pub fn skip_apple_single<R>(fp: &mut R) -> bool
|
||||||
|
where R: Read + Seek
|
||||||
{
|
{
|
||||||
|
let mut fp = SeekBackToStart::new(fp);
|
||||||
|
|
||||||
|
let mut magic = [0; 8];
|
||||||
|
let magic = if fp.read(&mut magic).is_ok() {magic} else {return false;};
|
||||||
|
|
||||||
// check magic numbers
|
// check magic numbers
|
||||||
if b.len() < 26 || *b.get(0..8)? != [0, 5, 22, 0, 0, 2, 0, 0] {
|
if magic != [0, 5, 22, 0, 0, 2, 0, 0] {
|
||||||
return None;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let num = usize::from(u16b(&b[24..]));
|
let mut num = [0; 2];
|
||||||
|
let num = if fp.read(&mut num).is_ok() {num} else {return false;};
|
||||||
|
let num = u64::from(u16::from_be_bytes(num));
|
||||||
|
|
||||||
if b.len() < 26 + 12 * num {
|
if fp.seek(SeekFrom::Start(26 + 12 * num)).is_err() |
|
||||||
return None;
|
fp.seek(SeekFrom::Start(26)).is_err() {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the resource fork (entity 1)
|
// get the resource fork (entity 1)
|
||||||
for i in 0..num {
|
for _ in 0..num {
|
||||||
let p = 26 + 12 * i;
|
let mut ent = [0; 4];
|
||||||
let ent = u32b(&b[p..]);
|
let mut ofs = [0; 4];
|
||||||
let ofs = usize_from_u32(u32b(&b[p + 4..]));
|
let mut len = [0; 4];
|
||||||
let len = usize_from_u32(u32b(&b[p + 8..]));
|
let ent = if fp.read(&mut ent).is_ok() {ent} else {return false;};
|
||||||
|
let ofs = if fp.read(&mut ofs).is_ok() {ofs} else {return false;};
|
||||||
|
let len = if fp.read(&mut len).is_ok() {len} else {return false;};
|
||||||
|
let ent = u32::from_be_bytes(ent);
|
||||||
|
let ofs = u64::from(u32::from_be_bytes(ofs));
|
||||||
|
let len = u64::from(u32::from_be_bytes(len));
|
||||||
|
|
||||||
if ent == 1 {
|
if ent == 1 {
|
||||||
return if ofs + len > b.len() {None} else {Some(ofs)};
|
if fp.seek(SeekFrom::Start(ofs + len)).is_ok() &
|
||||||
|
fp.seek(SeekFrom::Start(ofs)).is_ok() {
|
||||||
|
fp.set_seek(false);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// no resource fork
|
// no resource fork
|
||||||
None
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks for a `MacBinary II` header. Returns offset to the resource fork.
|
/// Skips over a Mac Binary II header. Returns true if one was found.
|
||||||
pub fn check_macbin(b: &[u8]) -> Option<usize>
|
pub fn skip_macbin<R>(fp: &mut R) -> bool
|
||||||
|
where R: Read + Seek
|
||||||
{
|
{
|
||||||
// check legacy version, length, zero fill, and macbin2 version
|
let mut fp = SeekBackToStart::new(fp);
|
||||||
// I swear this isn't *completely* magic
|
|
||||||
if b.len() < 128 || b[0] != 0 || b[1] > 63 || b[74] != 0 || b[123] > 129 {
|
let mut head = [0; 128];
|
||||||
return None;
|
let head = if fp.read(&mut head).is_ok() {head} else {return false;};
|
||||||
|
|
||||||
|
// check legacy version, length, zero fill, and macbin2 version. I swear,
|
||||||
|
// this isn't *completely* magic
|
||||||
|
if head[0] != 0 || head[1] > 63 || head[74] != 0 || head[123] > 129 {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut crc = 0;
|
let mut crc = 0;
|
||||||
|
|
||||||
// check header crc
|
// check header crc
|
||||||
for &byte in b.iter().take(124) {
|
for &byte in head.iter().take(124) {
|
||||||
for j in 8..16 {
|
for j in 8..16 {
|
||||||
let d = u16::from(byte) << j;
|
let d = u16::from(byte) << j;
|
||||||
|
|
||||||
|
@ -57,22 +84,33 @@ pub fn check_macbin(b: &[u8]) -> Option<usize>
|
||||||
}
|
}
|
||||||
|
|
||||||
// if ok, resource fork follows
|
// if ok, resource fork follows
|
||||||
if crc == u16b(&b[124..]) {
|
if crc == u16::from_be_bytes([head[124], head[125]]) {
|
||||||
Some(128)
|
fp.set_seek(false);
|
||||||
|
true
|
||||||
} else {
|
} else {
|
||||||
None
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads a `MacBin` or `AppleSingle` header if there is one and returns the
|
/// Reads a `MacBin` or `AppleSingle` header if there is one and skips past it
|
||||||
/// offset from the start of the header to the resource fork (if one is found.)
|
/// from the start of the header to the resource fork (if one is found.) Returns
|
||||||
pub fn try_mac_header(b: &[u8]) -> usize
|
/// true if either one was found.
|
||||||
|
pub fn skip_mac_header<R>(fp: &mut R) -> bool
|
||||||
|
where R: Read + Seek
|
||||||
{
|
{
|
||||||
if let Some(ofs) = check_macbin(b) {
|
if skip_macbin(fp) {
|
||||||
ofs
|
return true;
|
||||||
} else {
|
|
||||||
check_apple_single(b).unwrap_or(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _ = fp.seek(SeekFrom::Start(0));
|
||||||
|
|
||||||
|
if skip_apple_single(fp) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = fp.seek(SeekFrom::Start(0));
|
||||||
|
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
// EOF
|
// EOF
|
||||||
|
|
|
@ -67,13 +67,13 @@ pub fn read(head: &head::Header, b: &[u8]) -> ResultS<EntryData>
|
||||||
pub fn read_all(head: &head::Header,
|
pub fn read_all(head: &head::Header,
|
||||||
map: &entr::EntryMap<'_>) -> ResultS<EntryDataMap>
|
map: &entr::EntryMap<'_>) -> ResultS<EntryDataMap>
|
||||||
{
|
{
|
||||||
let mut dmap = EntryDataMap::new();
|
let mut data_map = EntryDataMap::new();
|
||||||
|
|
||||||
for (&index, entry) in map {
|
for (&index, entry) in map {
|
||||||
dmap.insert(index, read(head, &entry.data)?);
|
data_map.insert(index, read(head, entry.data)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(dmap)
|
Ok(data_map)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The loaded data of a Map file entry.
|
/// The loaded data of a Map file entry.
|
||||||
|
|
|
@ -49,8 +49,8 @@ impl Header
|
||||||
ver_wad: Ver,
|
ver_wad: Ver,
|
||||||
name: Option<String>) -> Self
|
name: Option<String>) -> Self
|
||||||
{
|
{
|
||||||
let name = name.unwrap_or(String::new());
|
let name = name.unwrap_or_default();
|
||||||
Header{name, size_appl, ver_data, ver_wad}
|
Self{name, size_appl, ver_data, ver_wad}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the data is in Marathon 1 format.
|
/// Returns `true` if the data is in Marathon 1 format.
|
||||||
|
|
|
@ -7,6 +7,7 @@ pub mod fram;
|
||||||
pub mod sequ;
|
pub mod sequ;
|
||||||
|
|
||||||
use crate::{bin::usize_from_u32, err::*};
|
use crate::{bin::usize_from_u32, err::*};
|
||||||
|
use std::io::prelude::*;
|
||||||
|
|
||||||
/// Reads a collection at an offset provided by the Shapes header.
|
/// Reads a collection at an offset provided by the Shapes header.
|
||||||
pub fn read_coll_at_offset(b: &[u8],
|
pub fn read_coll_at_offset(b: &[u8],
|
||||||
|
@ -23,14 +24,17 @@ pub fn read_coll_at_offset(b: &[u8],
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read all of the collections in a Shapes file.
|
/// Read all of the collections in a Shapes file.
|
||||||
pub fn read(b: &[u8]) -> ResultS<Vec<CollectionDef>>
|
pub fn read(fp: &mut impl Read) -> ResultS<Collections>
|
||||||
{
|
{
|
||||||
let mut cl = Vec::with_capacity(32);
|
let mut b = Vec::new();
|
||||||
let mut p = 0;
|
fp.read_to_end(&mut b)?;
|
||||||
|
|
||||||
for _ in 0..32 {
|
// hush little rustc, don't say a word...
|
||||||
|
let mut collections: Collections = unsafe {std::mem::uninitialized()};
|
||||||
|
|
||||||
|
for (i, co) in collections.iter_mut().enumerate() {
|
||||||
read_data! {
|
read_data! {
|
||||||
endian: BIG, buf: b, size: 32, start: p, data {
|
endian: BIG, buf: &b, size: 32, start: i * 32, data {
|
||||||
let lo_ofs = u32[4];
|
let lo_ofs = u32[4];
|
||||||
let lo_len = u32[8] usize;
|
let lo_len = u32[8] usize;
|
||||||
let hi_ofs = u32[12];
|
let hi_ofs = u32[12];
|
||||||
|
@ -38,18 +42,19 @@ pub fn read(b: &[u8]) -> ResultS<Vec<CollectionDef>>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let lo = read_coll_at_offset(b, lo_ofs, lo_len)?;
|
let lo = read_coll_at_offset(&b, lo_ofs, lo_len)?;
|
||||||
let hi = read_coll_at_offset(b, hi_ofs, hi_len)?;
|
let hi = read_coll_at_offset(&b, hi_ofs, hi_len)?;
|
||||||
|
|
||||||
cl.push((lo, hi));
|
*co = (lo, hi);
|
||||||
|
|
||||||
p += 32;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(cl)
|
Ok(collections)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A collection, which may have low- and high-definition variations, or none.
|
/// A collection, which may have low- and high-definition variations, or none.
|
||||||
pub type CollectionDef = (Option<coll::Collection>, Option<coll::Collection>);
|
pub type CollectionDef = (Option<coll::Collection>, Option<coll::Collection>);
|
||||||
|
|
||||||
|
/// The set of all collections in a Shapes file.
|
||||||
|
pub type Collections = [CollectionDef; 32];
|
||||||
|
|
||||||
// EOF
|
// EOF
|
||||||
|
|
|
@ -4,13 +4,16 @@ pub mod defs;
|
||||||
pub mod snds;
|
pub mod snds;
|
||||||
|
|
||||||
use crate::{bin::Ident, err::*};
|
use crate::{bin::Ident, err::*};
|
||||||
use std::collections::BTreeMap;
|
use std::{collections::BTreeMap, io::prelude::*};
|
||||||
|
|
||||||
/// Reads all sounds from a Sound file.
|
/// Reads all sounds from a Sound file.
|
||||||
pub fn read(b: &[u8]) -> ResultS<Vec<SoundTable>>
|
pub fn read(fp: &mut impl Read) -> ResultS<Vec<SoundTable>>
|
||||||
{
|
{
|
||||||
|
let mut b = Vec::new();
|
||||||
|
fp.read_to_end(&mut b)?;
|
||||||
|
|
||||||
read_data! {
|
read_data! {
|
||||||
endian: BIG, buf: b, size: 260, start: 0, data {
|
endian: BIG, buf: &b, size: 260, start: 0, data {
|
||||||
let version = u32[0];
|
let version = u32[0];
|
||||||
let magic = Ident[4];
|
let magic = Ident[4];
|
||||||
let src_num = u16[8] usize;
|
let src_num = u16[8] usize;
|
||||||
|
@ -23,9 +26,10 @@ pub fn read(b: &[u8]) -> ResultS<Vec<SoundTable>>
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut sc = Vec::with_capacity(src_num);
|
let mut sc = Vec::with_capacity(src_num);
|
||||||
let mut p = 260;
|
|
||||||
|
|
||||||
for _ in 0..src_num {
|
for i in 0..src_num {
|
||||||
|
let p = 260 + i * 64;
|
||||||
|
|
||||||
let mut st = SoundTable::new();
|
let mut st = SoundTable::new();
|
||||||
|
|
||||||
for _ in 0..snd_num {
|
for _ in 0..snd_num {
|
||||||
|
@ -36,8 +40,6 @@ pub fn read(b: &[u8]) -> ResultS<Vec<SoundTable>>
|
||||||
|
|
||||||
st.insert(idx, def);
|
st.insert(idx, def);
|
||||||
}
|
}
|
||||||
|
|
||||||
p += 64;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sc.push(st);
|
sc.push(st);
|
||||||
|
|
|
@ -7,8 +7,10 @@ fn machdr_must_process()
|
||||||
{
|
{
|
||||||
const INPUT: &[u8] = include_bytes!("data/misc/macbin.in");
|
const INPUT: &[u8] = include_bytes!("data/misc/macbin.in");
|
||||||
|
|
||||||
assert_eq!(machdr::check_macbin(INPUT), Some(128));
|
let mut inp = std::io::Cursor::new(INPUT);
|
||||||
assert_eq!(machdr::try_mac_header(INPUT), 128);
|
|
||||||
|
assert_eq!(machdr::skip_macbin(&mut inp), true);
|
||||||
|
assert_eq!(machdr::skip_mac_header(&mut inp), true);
|
||||||
|
|
||||||
// FIXME: missing test data for applesingle
|
// FIXME: missing test data for applesingle
|
||||||
}
|
}
|
||||||
|
@ -16,10 +18,11 @@ fn machdr_must_process()
|
||||||
#[test]
|
#[test]
|
||||||
fn machdr_must_not_process()
|
fn machdr_must_not_process()
|
||||||
{
|
{
|
||||||
for inp in &RANDOM {
|
for rinp in &RANDOM {
|
||||||
assert_eq!(machdr::check_macbin(inp), None);
|
let mut inp = std::io::Cursor::new(rinp);
|
||||||
assert_eq!(machdr::check_apple_single(inp), None);
|
assert_eq!(machdr::skip_macbin(&mut inp), false);
|
||||||
assert_eq!(machdr::try_mac_header(inp), 0);
|
assert_eq!(machdr::skip_apple_single(&mut inp), false);
|
||||||
|
assert_eq!(machdr::skip_mac_header(&mut inp), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,9 @@ fn shp_must_not_process()
|
||||||
assert!(shp::coll::read(inp).is_err());
|
assert!(shp::coll::read(inp).is_err());
|
||||||
assert!(shp::fram::read(inp).is_err());
|
assert!(shp::fram::read(inp).is_err());
|
||||||
assert!(shp::sequ::read(inp).is_err());
|
assert!(shp::sequ::read(inp).is_err());
|
||||||
assert!(shp::read(inp).is_err());
|
|
||||||
|
let mut inp = std::io::BufReader::new(&inp[..]);
|
||||||
|
assert!(shp::read(&mut inp).is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,9 @@ fn snd_must_not_process()
|
||||||
for inp in &RANDOM {
|
for inp in &RANDOM {
|
||||||
assert!(snd::defs::read(inp).is_err());
|
assert!(snd::defs::read(inp).is_err());
|
||||||
assert!(snd::snds::read(inp).is_err());
|
assert!(snd::snds::read(inp).is_err());
|
||||||
assert!(snd::read(inp).is_err());
|
|
||||||
|
let mut inp = std::io::BufReader::new(&inp[..]);
|
||||||
|
assert!(snd::read(&mut inp).is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue