Skip to content

Commit

Permalink
Raw archive
Browse files Browse the repository at this point in the history
MipTex doesn't require Seek
  • Loading branch information
r4v3n6101 committed Dec 2, 2023
1 parent bf3a08b commit 116aa47
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 91 deletions.
16 changes: 14 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,20 @@ pub use repr::*;
mod parser;
mod repr;

pub fn wad<R: Read + Seek>(reader: R) -> io::Result<wad::Archive> {
parser::wad::archive(reader)
pub fn raw_wad<R: Read + Seek + 'static>(reader: R) -> io::Result<wad::RawArchive> {
parser::wad::raw_archive(reader)
}

pub fn pic<R: Read>(reader: R) -> io::Result<texture::Picture> {
parser::texture::qpic(reader)
}

pub fn miptex<R: Read>(reader: R) -> io::Result<texture::MipTexture> {
parser::texture::miptex(reader)
}

pub fn font<R: Read>(reader: R) -> io::Result<texture::Font> {
parser::texture::font(reader)
}

pub fn bsp<R: Read + Seek>(reader: R) -> io::Result<bsp::Level> {
Expand Down
25 changes: 15 additions & 10 deletions src/parser/texture.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{
array,
io::{self, Read, Seek, SeekFrom},
io::{self, Cursor, Read},
mem, slice,
};

Expand Down Expand Up @@ -38,25 +38,30 @@ pub fn qpic<R: Read>(mut reader: R) -> io::Result<Picture> {
})
}

pub fn miptex<R: Read + Seek>(mut reader: R) -> io::Result<MipTexture> {
let begin = reader.stream_position()?;

pub fn miptex<R: Read>(mut reader: R) -> io::Result<MipTexture> {
let name = cstr16(&mut reader)?;
let width = reader.read_u32::<LittleEndian>()?;
let height = reader.read_u32::<LittleEndian>()?;
let offsets: [_; 4] = array::try_from_fn(|_| reader.read_u32::<LittleEndian>())?;
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 {
Expand All @@ -78,7 +83,7 @@ fn char_info<R: Read>(mut reader: R) -> io::Result<CharInfo> {
})
}

pub fn font<R: Read + Seek>(mut reader: R) -> io::Result<Font> {
pub fn font<R: Read>(mut reader: R) -> io::Result<Font> {
const GLYPHS_NUM: usize = 256;

let _ = reader.read_u32::<LittleEndian>()?;
Expand Down
91 changes: 33 additions & 58 deletions src/parser/wad.rs
Original file line number Diff line number Diff line change
@@ -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<R: Read + Seek>(mut reader: R) -> io::Result<Archive> {
pub fn raw_archive<R: Read + Seek + 'static>(reader: R) -> io::Result<RawArchive> {
const MAGIC: &[u8; 4] = b"WAD3";

let reader: Arc<Mutex<dyn ReadSeek>> = 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::<LittleEndian>()?;
let offset = reader.read_u32::<LittleEndian>()?;
let size = reader_ref.read_u32::<LittleEndian>()?;
let offset = reader_ref.read_u32::<LittleEndian>()?;

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::<LittleEndian>()?;
let size = reader.read_u32::<LittleEndian>()?;
let full_size = reader.read_u32::<LittleEndian>()?; // full_size, not used
let ty = reader.read_u8()?;
let compression = reader.read_u8()?;
let _ = reader.read_u16::<LittleEndian>(); // dummy
let name = cstr16(&mut reader)?;
let offset = reader_ref.read_u32::<LittleEndian>()?;
let size = reader_ref.read_u32::<LittleEndian>()?;
let full_size = reader_ref.read_u32::<LittleEndian>()?; // 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::<LittleEndian>(); // 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 })
}
71 changes: 62 additions & 9 deletions src/repr/wad.rs
Original file line number Diff line number Diff line change
@@ -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<CStr16, Content>;
/// Helpful trait for Read and Seek dyn Trait
pub(crate) trait ReadSeek: Read + Seek {}
impl<T: Read + Seek> ReadSeek for T {}

#[non_exhaustive]
pub enum Content {
Picture(Picture),
MipTexture(MipTexture),
Font(Font),
Other { ty: u8, data: Vec<u8> },
struct SharedChunkReader {
source: Arc<Mutex<dyn ReadSeek>>,
begin: usize,
end: usize,
}

impl Read for SharedChunkReader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
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<Mutex<dyn ReadSeek>>,
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<CStr16, RawEntry>,
}
34 changes: 22 additions & 12 deletions tests/wad.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use goldsrc_rs::{texture::ColourData, wad::Content};
use goldsrc_rs::{texture::ColourData, wad::ContentType};

fn save_img<const N: usize>(name: &str, width: u32, height: u32, data: &ColourData<N>) {
let data = data.indices[0]
Expand Down Expand Up @@ -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);
}
Expand Down

0 comments on commit 116aa47

Please sign in to comment.