From 116aa4778fd55fb411ca04bf08079655cd0d06c2 Mon Sep 17 00:00:00 2001 From: r4v3n6101 Date: Sat, 2 Dec 2023 22:02:58 +0300 Subject: [PATCH] Raw archive MipTex doesn't require Seek --- src/lib.rs | 16 +++++++- src/parser/texture.rs | 25 +++++++----- src/parser/wad.rs | 91 ++++++++++++++++--------------------------- src/repr/wad.rs | 71 ++++++++++++++++++++++++++++----- tests/wad.rs | 34 ++++++++++------ 5 files changed, 146 insertions(+), 91 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 835a730..3f1a256 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,8 +11,20 @@ pub use repr::*; mod parser; mod repr; -pub fn wad(reader: R) -> io::Result { - parser::wad::archive(reader) +pub fn raw_wad(reader: R) -> io::Result { + parser::wad::raw_archive(reader) +} + +pub fn pic(reader: R) -> io::Result { + parser::texture::qpic(reader) +} + +pub fn miptex(reader: R) -> io::Result { + parser::texture::miptex(reader) +} + +pub fn font(reader: R) -> io::Result { + parser::texture::font(reader) } pub fn bsp(reader: R) -> io::Result { diff --git a/src/parser/texture.rs b/src/parser/texture.rs index bc85b98..e02c6de 100644 --- a/src/parser/texture.rs +++ b/src/parser/texture.rs @@ -1,6 +1,6 @@ use std::{ array, - io::{self, Read, Seek, SeekFrom}, + io::{self, Cursor, Read}, mem, slice, }; @@ -38,25 +38,30 @@ pub fn qpic(mut reader: R) -> io::Result { }) } -pub fn miptex(mut reader: R) -> io::Result { - let begin = reader.stream_position()?; - +pub fn miptex(mut reader: R) -> io::Result { let name = cstr16(&mut reader)?; let width = reader.read_u32::()?; let height = reader.read_u32::()?; let offsets: [_; 4] = array::try_from_fn(|_| reader.read_u32::())?; let data = if offsets.iter().all(|&x| x != 0) { let pixels = (width * height) as usize; + + // Skip something between header and first mip indices + for _ in 0..(offsets[0].saturating_sub(40)) { + reader.read_u8()?; + } + let data_len = ((pixels * 85) >> 6) as u32 + 2 + 256 * 3; + let mut cursor = Cursor::new(vec![0; data_len as usize]); + reader.read_exact(cursor.get_mut())?; + let indices = array::try_from_fn(|i| { chunk_with_offset( - &mut reader, - begin + offsets[i] as u64, + &mut cursor, + offsets[i].saturating_sub(40) as u64, pixels / (1 << (2 * i)), ) })?; - - reader.seek(SeekFrom::Start(begin + 40 + ((pixels * 85) >> 6) as u64))?; - let palette = palette(&mut reader)?; + let palette = palette(&mut cursor)?; Some(ColourData { indices, palette }) } else { @@ -78,7 +83,7 @@ fn char_info(mut reader: R) -> io::Result { }) } -pub fn font(mut reader: R) -> io::Result { +pub fn font(mut reader: R) -> io::Result { const GLYPHS_NUM: usize = 256; let _ = reader.read_u32::()?; diff --git a/src/parser/wad.rs b/src/parser/wad.rs index 84aae47..ff3c16f 100644 --- a/src/parser/wad.rs +++ b/src/parser/wad.rs @@ -1,82 +1,57 @@ use std::{ collections::HashMap, io::{self, Read, Seek, SeekFrom}, + sync::{Arc, Mutex}, }; use byteorder::{LittleEndian, ReadBytesExt}; -use crate::{ - wad::{Archive, Content}, - CStr16, -}; - -use super::{ - chunk, cstr16, - texture::{font, miptex, qpic}, -}; +use crate::wad::{ContentType, RawArchive, RawEntry, ReadSeek}; -#[allow(dead_code)] -struct Entry { - offset: u32, - full_size: u32, - size: u32, - ty: u8, - compression: u8, - name: CStr16, -} +use super::cstr16; -pub fn archive(mut reader: R) -> io::Result { +pub fn raw_archive(reader: R) -> io::Result { const MAGIC: &[u8; 4] = b"WAD3"; + let reader: Arc> = Arc::new(Mutex::new(reader)); + let mut reader_ref = reader.lock().unwrap(); + let mut header = [0u8; 4]; - reader.read_exact(&mut header)?; + reader_ref.read_exact(&mut header)?; if &header != MAGIC { return Err(io::Error::new(io::ErrorKind::Unsupported, "invalid magic")); } - let size = reader.read_u32::()?; - let offset = reader.read_u32::()?; + let size = reader_ref.read_u32::()?; + let offset = reader_ref.read_u32::()?; - reader.seek(SeekFrom::Start(offset as u64))?; - let mut entries = Vec::with_capacity(size as usize); + reader_ref.seek(SeekFrom::Start(offset as u64))?; + let mut entries = HashMap::with_capacity(size as usize); for _ in 0..size { - let offset = reader.read_u32::()?; - let size = reader.read_u32::()?; - let full_size = reader.read_u32::()?; // full_size, not used - let ty = reader.read_u8()?; - let compression = reader.read_u8()?; - let _ = reader.read_u16::(); // dummy - let name = cstr16(&mut reader)?; + let offset = reader_ref.read_u32::()?; + let size = reader_ref.read_u32::()?; + let full_size = reader_ref.read_u32::()?; // full_size, not used + let ty = match reader_ref.read_u8()? { + 0x42 => ContentType::Picture, + 0x43 => ContentType::MipTexture, + 0x46 => ContentType::Font, + unknown => ContentType::Other(unknown), + }; + let compression = reader_ref.read_u8()?; + let _ = reader_ref.read_u16::(); // dummy + let name = cstr16(&mut *reader_ref)?; - entries.push(Entry { - offset, - full_size, - size, - ty, - compression, + entries.insert( name, - }) - } - - let mut archive = HashMap::with_capacity(entries.len()); - for entry in entries { - reader.seek(SeekFrom::Start(entry.offset as u64))?; - - if entry.compression != 0 { - unimplemented!("compression not supported by goldsrc") - } - - let content = match entry.ty { - 0x42 => Content::Picture(qpic(&mut reader)?), - 0x43 => Content::MipTexture(miptex(&mut reader)?), - 0x46 => Content::Font(font(&mut reader)?), - ty => Content::Other { + RawEntry { + source: Arc::clone(&reader), + offset, + full_size, + size, ty, - data: chunk(&mut reader, entry.size as usize)?, + compression, }, - }; - - archive.insert(entry.name, content); + ); } - Ok(archive) + Ok(RawArchive { entries }) } diff --git a/src/repr/wad.rs b/src/repr/wad.rs index 5979b05..7531d60 100644 --- a/src/repr/wad.rs +++ b/src/repr/wad.rs @@ -1,15 +1,68 @@ -use std::collections::HashMap; +use std::{ + collections::HashMap, + io::{self, Read, Seek, SeekFrom}, + sync::{Arc, Mutex}, +}; use crate::CStr16; -use super::texture::{Font, MipTexture, Picture}; +#[non_exhaustive] +#[repr(u8)] +#[derive(Debug, Clone, Copy)] +pub enum ContentType { + Picture, + MipTexture, + Font, + Other(u8), +} -pub type Archive = HashMap; +/// Helpful trait for Read and Seek dyn Trait +pub(crate) trait ReadSeek: Read + Seek {} +impl ReadSeek for T {} -#[non_exhaustive] -pub enum Content { - Picture(Picture), - MipTexture(MipTexture), - Font(Font), - Other { ty: u8, data: Vec }, +struct SharedChunkReader { + source: Arc>, + begin: usize, + end: usize, +} + +impl Read for SharedChunkReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + if self.begin == self.end { + return Ok(0); + } + + let mut source = self.source.lock().unwrap(); + source.seek(SeekFrom::Start(self.begin as u64))?; + let remaining = self.end - self.begin; + let readlen = buf.len().min(remaining); + source.read_exact(&mut buf[..readlen])?; + self.begin += readlen; + + Ok(readlen) + } +} + +// TODO : Debug? +pub struct RawEntry { + pub(crate) source: Arc>, + pub offset: u32, + pub full_size: u32, + pub size: u32, + pub ty: ContentType, + pub compression: u8, +} + +impl RawEntry { + pub fn reader(&self) -> impl Read { + SharedChunkReader { + source: Arc::clone(&self.source), + begin: self.offset as usize, + end: (self.offset + self.size) as usize, + } + } +} + +pub struct RawArchive { + pub entries: HashMap, } diff --git a/tests/wad.rs b/tests/wad.rs index 0de9799..274b6e9 100644 --- a/tests/wad.rs +++ b/tests/wad.rs @@ -1,4 +1,4 @@ -use goldsrc_rs::{texture::ColourData, wad::Content}; +use goldsrc_rs::{texture::ColourData, wad::ContentType}; fn save_img(name: &str, width: u32, height: u32, data: &ColourData) { let data = data.indices[0] @@ -27,18 +27,28 @@ fn extract_wad() { .flatten() { let data = std::fs::read(&path).expect("error reading file"); - let archive = goldsrc_rs::wad(std::io::Cursor::new(&data)).unwrap(); + let raw_archive = goldsrc_rs::raw_wad(std::io::Cursor::new(data)).unwrap(); - for (name, content) in &archive { - match content { - Content::Font(font) => save_img(name, font.width, font.height, &font.data), - Content::Picture(pic) => save_img(name, pic.width, pic.height, &pic.data), - Content::MipTexture(miptex) => save_img( - name, - miptex.width, - miptex.height, - miptex.data.as_ref().unwrap(), - ), + for (name, entry) in &raw_archive.entries { + let reader = entry.reader(); + match entry.ty { + ContentType::Font => { + let font = goldsrc_rs::font(reader).unwrap(); + save_img(name, font.width, font.height, &font.data); + } + ContentType::Picture => { + let pic = goldsrc_rs::pic(reader).unwrap(); + save_img(name, pic.width, pic.height, &pic.data); + } + ContentType::MipTexture => { + let miptex = goldsrc_rs::miptex(reader).unwrap(); + save_img( + name, + miptex.width, + miptex.height, + miptex.data.as_ref().unwrap(), + ); + } _ => { eprintln!("Unknown type: {}", name); }