caco 0.5.33

library for dealing with doom [et al] wad files
Documentation
use crate::lump::QuakeLump;
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::{Result, Error};

/// Quake's "Wad2" format.
/// 
/// https://six-of-one.github.io/quake-specifications/qkspec_7.htm#CWAD0
#[derive(Clone, Debug)]
pub struct QuakeWad {
    /// The series of lumps (named blocks of binary data) contained
    /// in the file.
    pub lumps: Vec<QuakeLump>
}

impl QuakeWad {
    /// Create a new, empty Quake wad.
    pub fn blank() -> Self {
        Self { lumps: vec!() }
    }

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

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

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

        let lump_count = f.read_u32::<LittleEndian>()? as u64;
        let directory_location = f.read_u32::<LittleEndian>()? as u64;

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

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

            let size_in_memory = f.read_u32::<LittleEndian>()?;
            let data_type = f.read_u8()? as char;
            let compression = f.read_u8()? as char;
            let _unused = f.read_u16::<LittleEndian>()?;
            
            let mut lumpname = "".to_owned();
            let mut null_seen = false;

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

            f.seek(std::io::SeekFrom::Start(filepos))?;
            let mut filebuf = vec![0; filesize];
            f.read_exact(&mut filebuf)?;
            
            lumps.push(QuakeLump {
                name: lumpname,
                data: filebuf.into_boxed_slice(),
                memsize: size_in_memory,
                datatype: data_type,
                compression: compression,
            })
        }

        Ok(Self { lumps })
    }

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

        f.write(b"WAD2")?;
        f.write_u32::<LittleEndian>(self.lumps.len() as u32)?;

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

        struct DictionaryEntry {
            name: String,
            size: u32,
            loc: u32,
            memsize: u32,
            dtype: char,
            comprs: char
        }

        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,
                memsize: l.memsize,
                dtype: l.datatype,
                comprs: l.compression
            });

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

        let dictpos = f.stream_position()?;

        for d in dict {
            f.write_u32::<LittleEndian>(d.loc)?;
            f.write_u32::<LittleEndian>(d.size)?;
            f.write_u32::<LittleEndian>(d.memsize)?;
            f.write(&[d.dtype as u8])?;
            f.write(&[d.comprs as u8])?;
            f.write_u16::<LittleEndian>(0)?;
            
            let mut lname = d.name.clone();
            lname.truncate(16);
            while lname.len() < 16 { lname.push('\0'); }
            f.write(lname.as_bytes())?;
        }

        f.seek(std::io::SeekFrom::Start(8))?;
        f.write_u32::<LittleEndian>(dictpos as u32)?;

        Ok(())
    }
}

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

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

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

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

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

    fn a_sub_nth_clumps_mut(&mut self, a: impl Iterator<Item = usize>) -> Vec<&mut QuakeLump> {
        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: QuakeLump, n: usize) {
        self.lumps.insert(n, l);
    }

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