mod test;
mod validate;
use crate::wad::{
DecodeError,
Lump,
Lumps,
RawHeader,
RawLumpSlice,
Tag,
};
use crate::wad::ffi::{doom, halflife, quake};
use core::slice;
use oct::{FromOcts, IntoOcts};
#[cfg(feature = "alloc")]
use alloc::borrow::ToOwned;
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
#[cfg(feature = "alloc")]
use core::mem::transmute;
#[repr(transparent)]
#[derive(Debug)]
pub struct Wad {
data: [u8],
}
impl Wad {
pub fn from_bytes(bytes: &[u8]) -> Result<&Self, DecodeError> {
let this = unsafe { Self::from_bytes_unchecked(bytes) };
this.validate().map(|()| this)
}
#[cfg(feature = "alloc")]
pub fn from_boxed_bytes(bytes: Box<[u8]>) -> Result<Box<Self>, DecodeError> {
let this = unsafe { Self::from_boxed_bytes_unchecked(bytes) };
this.validate().map(|()| this)
}
#[inline]
#[must_use]
pub const unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self {
let p = &raw const *bytes as *const Self;
unsafe { &*p }
}
#[cfg(feature = "alloc")]
#[inline]
#[must_use]
pub const unsafe fn from_boxed_bytes_unchecked(bytes: Box<[u8]>) -> Box<Self> {
unsafe { transmute::<Box<[u8]>, Box<Self>>(bytes) }
}
#[inline]
#[must_use]
pub const unsafe fn from_raw_parts<'a>(ptr: *const u8, len: usize) -> &'a Self {
let bytes = unsafe { slice::from_raw_parts(ptr, len) };
unsafe { Self::from_bytes_unchecked(bytes) }
}
#[inline(always)]
#[must_use]
pub(super) fn tag(&self) -> Tag {
let bytes = self.as_bytes();
let magic = <[i8; 4]>::read_from_octs(bytes);
unsafe { Tag::from_magic_unchecked(magic) }
}
#[inline]
#[must_use]
pub(super) fn header(&self) -> RawHeader<'_> {
let bytes = self.as_bytes();
match self.tag() {
Tag::Wad => doom::wadinfo_t::ref_from_octs(bytes).into(),
Tag::Wad2 => quake::wadinfo_t::ref_from_octs(bytes).into(),
Tag::Wad3 => halflife::wadinfo_t::ref_from_octs(bytes).into(),
}
}
#[inline]
#[must_use]
pub(super) fn directory(&self) -> RawLumpSlice<'_> {
let header = self.header();
let bytes = {
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_possible_wrap)]
let start = header.infotableofs().cast_unsigned() as usize;
unsafe { self.as_bytes().get_unchecked(start..) }
};
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_possible_wrap)]
let len = header.numlumps().cast_unsigned() as usize;
match self.tag() {
Tag::Wad => <[doom::filelump_t]>::ref_from_octs_with_len(bytes, len).into(),
Tag::Wad2 => <[quake::lumpinfo_t]>::ref_from_octs_with_len(bytes, len).into(),
Tag::Wad3 => <[halflife::lumpinfo_t]>::ref_from_octs_with_len(bytes, len).into(),
}
}
#[inline]
#[must_use]
pub fn is_iwad(&self) -> bool {
matches!(
self.header().identification().as_octs(),
b"IWAD" | b"DAWI",
)
}
#[inline]
#[must_use]
pub fn is_pwad(&self) -> bool {
matches!(
self.header().identification().as_octs(),
b"PWAD" | b"DAWP",
)
}
#[inline]
#[must_use]
pub fn is_wad2(&self) -> bool {
matches!(
self.header().identification().as_octs(),
b"WAD2" | b"DAW2",
)
}
#[inline]
#[must_use]
pub fn is_wad3(&self) -> bool {
self.header().identification().as_octs() == b"WAD3"
}
#[must_use]
pub fn contains_lump(&self, name: &str) -> bool {
self.lumps().any(|l| l.raw_name().as_octs() == name.as_bytes())
}
#[must_use]
pub fn lump(&self, name: &str) -> Option<Lump<'_>> {
self.lumps().rev().find(|l| l.raw_name().as_octs() == name.as_bytes())
}
#[inline]
pub fn lumps(&self) -> Lumps<'_> {
Lumps::new(self)
}
#[inline]
#[must_use]
pub fn lump_count(&self) -> usize {
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_possible_wrap)]
{ self.header().numlumps().cast_unsigned() as usize }
}
#[expect(clippy::len_without_is_empty)]
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.data.len()
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
&self.data
}
#[inline]
pub fn as_ptr(&self) -> *const u8 {
self.data.as_ptr()
}
}
#[cfg(feature = "alloc")]
impl Clone for Box<Wad> {
#[inline]
fn clone(&self) -> Self {
self.to_owned()
}
}
#[cfg(feature = "alloc")]
impl ToOwned for Wad {
type Owned = Box<Self>;
fn to_owned(&self) -> Self::Owned {
let bytes = self.data.to_owned().into_boxed_slice();
unsafe { Self::from_boxed_bytes_unchecked(bytes) }
}
}