caco 0.5.33

library for dealing with doom [et al] wad files
Documentation
use crate::lump::QuakePackLump;
use crate::agnostic::{FlatClod, FlatClodMut};
use crate::error::Error as WadError;

use std::{fs::File, io::{Read, Seek, Write}};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use anyhow::{Error, Result};

/// A quake .PAK file. Contains game data for Quake as well as games
/// in the GoldSrc family of engines (todo can someone verify? -nf)
#[derive(Clone, Debug)]
pub struct QuakePack {
    /// The series of lumps (named blocks of binary data) contained
    /// in the file.
    pub lumps: Vec<QuakePackLump>,
}

impl QuakePack {
    /// Create a new, empty Quake/GoldSrc pack file.
    pub fn blank() -> Self {
        Self { lumps: vec!() }
    }

    /// Load a Quake pack from a file.
    pub fn load(f: &mut File) -> Result<Self> {
        f.seek(std::io::SeekFrom::Start(0))?;

        let mut lumps: Vec<QuakePackLump> = vec!();
        
        let mut wad_sig = [0u8; 4];
        f.read_exact(&mut wad_sig)?;

        if wad_sig != *b"PACK" {
            return Err(Error::new(WadError::IncorrectMagicInHeader));
        }

        // https://six-of-one.github.io/quake-specifications/qkspec_3.htm#CPAK0
        let directory_location = f.read_u32::<LittleEndian>()? as u64;
        let directory_size = f.read_u32::<LittleEndian>()? as u64;

        let lump_count = directory_size / 64;

        for n in 0..lump_count {
            f.seek(std::io::SeekFrom::Start(directory_location + 64 * n))?;

            let mut lumpname = "".to_owned();
            let mut null_seen = false;

            for _ in 0..56 {
                let nextchar = f.read_u8()? as char;
                if nextchar == '\0' { null_seen = true; }
                if !null_seen { lumpname.push(nextchar); }
            }

            let filepos = f.read_u32::<LittleEndian>()? as u64;
            let filesize = f.read_u32::<LittleEndian>()? as usize;

            f.seek(std::io::SeekFrom::Start(filepos))?;

            // this WILL fail to load pack entries that are greater in size than 2^32-1 bytes
            // TODO TODO TODO load with multiple buffers at a time(?) to get all the data.
            let mut filebuf = vec![0; filesize as usize];
            f.read_exact(&mut filebuf)?;

            lumps.push(QuakePackLump {
                name: lumpname,
                data: filebuf.into_boxed_slice(),
            });
        }


        Ok(Self { lumps })
    }

    /// Write the Quake/Goldsrc pack to a file on disk.
    pub fn save(&self, f: &mut File) -> Result<()> {
        f.seek(std::io::SeekFrom::Start(0))?;

        f.write(b"PACK")?;

        f.seek(std::io::SeekFrom::Start(12))?;

        struct DictionaryEntry {
            name: String,
            size: u32,
            loc: u32,
        }

        let mut dict = vec!();

        for l in &self.lumps {
            dict.push(DictionaryEntry {
                name: l.name.clone(),
                size: l.data.len() as u32,
                loc: f.stream_position()? as u32,
            });

            f.write(&l.data)?;
        }

        let dictstart = f.stream_position()? as u32;

        for d in dict {
            let mut lname = d.name.clone();
            lname.truncate(56);
            while lname.len() < 56 { lname.push('\0'); }
            f.write(lname.as_bytes())?;

            f.write_u32::<LittleEndian>(d.loc)?;
            f.write_u32::<LittleEndian>(d.size)?;
        }

        let dictend = f.stream_position()? as u32;
        let dictsize = dictend - dictstart;

        f.seek(std::io::SeekFrom::Start(4))?;

        f.write_u32::<LittleEndian>(dictstart)?;
        f.write_u32::<LittleEndian>(dictsize)?;

        Ok(())
    }
}

impl FlatClod<QuakePackLump> for QuakePack {
    fn clump_amt(&self) -> usize {
        self.lumps.len()
    }

    fn nth_clump(&self, n: usize) -> &QuakePackLump {
        &self.lumps[n]
    }

    fn a_sub_nth_clumps(&self, a: impl Iterator<Item = usize>) -> Vec<&QuakePackLump> {
        let a: Vec<usize> = a.collect();

        (&self.lumps[..])
            .iter()
            .enumerate()
            .filter(|(k, _)| a.contains(k))
            .map(|(_, l)| l)
            .collect()
    }
}

impl FlatClodMut<QuakePackLump> for QuakePack {
    fn nth_clump_mut(&mut self, n: usize) -> &mut QuakePackLump {
        &mut self.lumps[n]
    }

    fn a_sub_nth_clumps_mut(&mut self, a: impl Iterator<Item = usize>) -> Vec<&mut QuakePackLump> {
        let a: Vec<usize> = a.collect();
        (&mut self.lumps[..])
            .iter_mut()
            .enumerate()
            .filter(|(k, _)| a.contains(k))
            .map(|(_, l)| l)
            .collect()
    }

    fn insert_clump(&mut self, l: QuakePackLump, n: usize) {
        self.lumps.insert(n, l);
    }

    fn remove_clump(&mut self, n: usize) -> QuakePackLump {
        self.lumps.remove(n)
    }
}