1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
use std::slice::SliceIndex;
use byteorder::{ByteOrder, LittleEndian};

use crate::entry::Entry;
use crate::entry_id::EntryId;
use crate::error::Error;
use crate::iterator::*;
use crate::wad::*;

pub struct WadSlice<'a> {
    data: &'a [u8],
    directory: &'a [RawEntry],
}

impl<'a> WadSlice<'a> {
    pub(crate) fn new<'n>(data: &'n [u8], directory: &'n [RawEntry]) -> WadSlice<'n> {
        WadSlice { data, directory }
    }

    pub fn len(&self) -> usize {
        self.directory.len()
    }

    pub fn entry_id_from_raw_entry(raw_entry: &RawEntry) -> EntryId {
        // This is safe because the static size of RawEntry is bigger than
        // the size of the requested slice:
        let id = unsafe { &*(raw_entry[8..16].as_ptr() as *const _) };
        EntryId::from_bytes(id)
    }

    pub unsafe fn entry_id_unchecked(&self, index: usize) -> EntryId {
        let directory_entry = self.directory.get_unchecked(index);
        Self::entry_id_from_raw_entry(directory_entry)
    }

    pub fn entry_id(&self, index: usize) -> Option<EntryId> {
        let directory_entry = self.directory.get(index)?;
        Some(Self::entry_id_from_raw_entry(directory_entry))
    }

    pub fn id_iter(&self) -> SliceIdIterator {
        SliceIdIterator::new(self)
    }

    pub fn index_of(&self, id: impl Into<EntryId>) -> Option<usize> {
        let id = id.into();
        self.id_iter().position(|x| x == id)
    }

    pub fn entry_from_raw_entry(&self, raw_entry: &RawEntry) -> Result<Entry<'a>, Error> {
        let start = LittleEndian::read_i32(&raw_entry[0..4]);
        let length = LittleEndian::read_i32(&raw_entry[4..8]);
        let id = Self::entry_id_from_raw_entry(raw_entry);

        verify!(length >= 0, Error::InvalidEntry);
        let length = length as usize;

        verify!(start >= 0, Error::InvalidEntry);
        let mut start = start as usize;

        // If length == 0, start doesn't matter. Some directory entries in
        // official doom wads have start == 0, which is really too early.
        if length == 0 {
            start = HEADER_BYTE_SIZE;
        }

        verify!(start >= HEADER_BYTE_SIZE, Error::InvalidEntry);

        let end = start.checked_add(length).ok_or(Error::InvalidEntry)?;
        verify!(end <= self.data.len(), Error::InvalidEntry);

        let lump = &self.data[start..end];

        Ok(Entry { id, lump })
    }

    pub unsafe fn entry_unchecked(&self, index: usize) -> Result<Entry<'a>, Error> {
        let raw_entry = self.directory.get_unchecked(index);
        self.entry_from_raw_entry(raw_entry)
    }

    pub fn entry(&self, index: usize) -> Result<Entry<'a>, Error> {
        let raw_entry = self.directory.get(index).ok_or(Error::OutOfBounds)?;
        self.entry_from_raw_entry(raw_entry)
    }

    pub fn entry_iter(&self) -> SliceEntryIterator {
        SliceEntryIterator::new(self)
    }

    pub fn by_id(&self, id: impl Into<EntryId>) -> Option<&'a [u8]> {
        let id = id.into();
        let index = self.index_of(id)?;
        let entry = self.entry(index).ok()?;
        Some(entry.lump)
    }

    pub fn slice(&self, slice_index: impl SliceIndex<[RawEntry], Output = [RawEntry]>) -> WadSlice<'a> {
        WadSlice::new(
            self.data,
            &self.directory[slice_index],
        )
    }
}

impl<'a> std::ops::Index<usize> for WadSlice<'a> {
    type Output = [u8];

    fn index(&self, index: usize) -> &Self::Output {
        self.entry(index).unwrap().lump
    }
}

impl<'a> From<&'a Wad> for WadSlice<'a> {
    fn from(wad: &'a Wad) -> WadSlice<'a> {
        wad.as_slice()
    }
}