Skip to content

Commit

Permalink
implement experimental support for gnmf archives
Browse files Browse the repository at this point in the history
  • Loading branch information
Ryan-rsm-McKenzie committed Feb 11, 2024
1 parent 47de226 commit 5152185
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 15 deletions.
20 changes: 17 additions & 3 deletions src/fo4/archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
derive,
fo4::{
self, Chunk, CompressionFormat, DX10Header, Error, File, FileHash, FileHeader, Format,
Hash, Result, Version,
GNMFHeader, Hash, Result, Version,
},
io::{Endian, Sink, Source},
protocols::WString,
Expand All @@ -18,16 +18,19 @@ mod constants {

pub(crate) const GNRL: u32 = cc::make_four(b"GNRL");
pub(crate) const DX10: u32 = cc::make_four(b"DX10");
pub(crate) const GNMF: u32 = cc::make_four(b"GNMF");

pub(crate) const HEADER_SIZE_V1: usize = 0x18;
pub(crate) const HEADER_SIZE_V2: usize = 0x20;
pub(crate) const HEADER_SIZE_V3: usize = 0x24;

pub(crate) const FILE_HEADER_SIZE_GNRL: u16 = 0x10;
pub(crate) const FILE_HEADER_SIZE_DX10: u16 = 0x18;
pub(crate) const FILE_HEADER_SIZE_GNMF: u16 = 0x30;

pub(crate) const CHUNK_SIZE_GNRL: usize = 0x14;
pub(crate) const CHUNK_SIZE_DX10: usize = 0x18;
pub(crate) const CHUNK_SIZE_GNMF: usize = 0x18;

pub(crate) const CHUNK_SENTINEL: u32 = 0xBAAD_F00D;
}
Expand All @@ -50,6 +53,7 @@ impl Offsets {
let (file_header_size, chunk_size) = match options.format {
Format::GNRL => (constants::FILE_HEADER_SIZE_GNRL, constants::CHUNK_SIZE_GNRL),
Format::DX10 => (constants::FILE_HEADER_SIZE_DX10, constants::CHUNK_SIZE_DX10),
Format::GNMF => (constants::FILE_HEADER_SIZE_GNMF, constants::CHUNK_SIZE_GNMF),
};
let chunks_count: usize = archive.values().map(File::len).sum();
chunks_offset
Expand Down Expand Up @@ -272,7 +276,7 @@ impl<'bytes> Archive<'bytes> {

match (header.format, &chunk.mips) {
(Format::GNRL, None) => (),
(Format::DX10, Some(mips)) => {
(Format::DX10 | Format::GNMF, Some(mips)) => {
sink.write(&(*mips.start(), *mips.end()), Endian::Little)?;
}
_ => {
Expand Down Expand Up @@ -300,6 +304,7 @@ impl<'bytes> Archive<'bytes> {
let chunk_size = match header.format {
Format::GNRL => constants::FILE_HEADER_SIZE_GNRL,
Format::DX10 => constants::FILE_HEADER_SIZE_DX10,
Format::GNMF => constants::FILE_HEADER_SIZE_GNMF,
};
sink.write(&(0u8, chunk_count, chunk_size), Endian::Little)?;

Expand All @@ -318,6 +323,9 @@ impl<'bytes> Archive<'bytes> {
Endian::Little,
)?;
}
(Format::GNMF, FileHeader::GNMF(x)) => {
sink.write(&x.metadata, Endian::Little)?;
}
(_, _) => {
return Err(Error::FormatMismatch);
}
Expand Down Expand Up @@ -345,6 +353,7 @@ impl<'bytes> Archive<'bytes> {
let format = match header.format {
Format::GNRL => constants::GNRL,
Format::DX10 => constants::DX10,
Format::GNMF => constants::GNMF,
};

sink.write(
Expand Down Expand Up @@ -404,7 +413,7 @@ impl<'bytes> Archive<'bytes> {
source.read(Endian::Little)?;
let mips = match header.format {
Format::GNRL => None,
Format::DX10 => {
Format::DX10 | Format::GNMF => {
let (mip_first, mip_last) = source.read(Endian::Little)?;
Some(mip_first..=mip_last)
}
Expand Down Expand Up @@ -475,6 +484,10 @@ impl<'bytes> Archive<'bytes> {
}
.into()
}
Format::GNMF => {
let metadata = source.read(Endian::Little)?;
GNMFHeader { metadata }.into()
}
};

let mut chunks = Vec::with_capacity(chunk_count.into());
Expand Down Expand Up @@ -521,6 +534,7 @@ impl<'bytes> Archive<'bytes> {
let format = match contents_format {
constants::GNRL => Format::GNRL,
constants::DX10 => Format::DX10,
constants::GNMF => Format::GNMF,
_ => return Err(Error::InvalidFormat(contents_format)),
};

Expand Down
113 changes: 104 additions & 9 deletions src/fo4/file.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use crate::{
cc,
containers::CompressableBytes,
derive,
fo4::{
ArchiveOptions, Chunk, ChunkCompressionOptions, CompressionFormat, CompressionLevel, Error,
Format, Result,
},
io::Source,
io::{Endian, Sink, Source},
CompressionResult, Sealed,
};
use core::{
Expand Down Expand Up @@ -335,13 +336,18 @@ pub struct DX10 {
pub tile_mode: u8,
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct GNMF {
pub metadata: [u32; 8],
}

#[allow(clippy::upper_case_acronyms)]
#[non_exhaustive]
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub enum Header {
#[default]
GNRL,
DX10(DX10),
GNMF(GNMF),
}

impl From<DX10> for Header {
Expand All @@ -350,6 +356,12 @@ impl From<DX10> for Header {
}
}

impl From<GNMF> for Header {
fn from(value: GNMF) -> Self {
Self::GNMF(value)
}
}

type Container<'bytes> = Vec<Chunk<'bytes>>;

/// Represents a file within the FO4 virtual filesystem.
Expand Down Expand Up @@ -520,9 +532,11 @@ impl<'bytes> File<'bytes> {
where
Out: ?Sized + Write,
{
match self.header {
Header::GNRL => self.write_gnrl(stream, *options)?,
Header::DX10(x) => self.write_dx10(stream, *options, x)?,
let mut sink = Sink::new(stream);
match &self.header {
Header::GNRL => self.write_gnrl(&mut sink, *options)?,
Header::DX10(x) => self.write_dx10(&mut sink, *options, *x)?,
Header::GNMF(x) => self.write_gnmf(&mut sink, *options, x)?,
}

Ok(())
Expand All @@ -544,6 +558,7 @@ impl<'bytes> File<'bytes> {
let mut this = match options.format {
Format::GNRL => Self::read_gnrl(stream),
Format::DX10 => Self::read_dx10(stream, options),
Format::GNMF => Err(Error::NotImplemented),
}?;

if options.compression_result == CompressionResult::Compressed {
Expand Down Expand Up @@ -654,7 +669,12 @@ impl<'bytes> File<'bytes> {
Ok([chunk].into_iter().collect())
}

fn write_dx10<Out>(&self, stream: &mut Out, options: WriteOptions, dx10: DX10) -> Result<()>
fn write_dx10<Out>(
&self,
stream: &mut Sink<Out>,
options: WriteOptions,
dx10: DX10,
) -> Result<()>
where
Out: ?Sized + Write,
{
Expand All @@ -678,11 +698,86 @@ impl<'bytes> File<'bytes> {
};

let header = meta.encode_dds_header(DDS_FLAGS::DDS_FLAGS_NONE)?;
stream.write_all(&header)?;
stream.write_bytes(&header)?;
self.write_gnrl(stream, options)
}

fn write_gnrl<Out>(&self, stream: &mut Out, options: WriteOptions) -> Result<()>
fn write_gnmf<Out>(
&self,
stream: &mut Sink<Out>,
options: WriteOptions,
gnmf: &GNMF,
) -> Result<()>
where
Out: ?Sized + Write,
{
const ALIGNMENT: u8 = 0x8;

let body = {
const ALIGN: usize = ALIGNMENT as usize;
let len = {
let len: usize = self
.iter()
.map(|x| x.decompressed_len().unwrap_or_else(|| x.len()))
.sum();
let extra = len % ALIGN;
if extra == 0 {
len
} else {
len - extra + ALIGN
}
};

let mut buffer = Vec::with_capacity(len);
let mut stream = Sink::new(&mut buffer);
self.write_gnrl(&mut stream, options)?;

let extra = buffer.len() % ALIGN;
if extra != 0 {
buffer.resize_with(buffer.len() - extra + ALIGN, Default::default);
}

buffer
};

let header = {
const HEADER_SIZE: u32 = 0x100;
let magic = cc::make_four(b"GNF ");
let contents_size: u32 = HEADER_SIZE - 0x8;
let version: u8 = 2;
let texture_count: u8 = 1;
let alignment: u8 = ALIGNMENT;
let stream_size: u32 = HEADER_SIZE
.checked_add(body.len().try_into()?)
.ok_or(Error::IntegralOverflow)?;

let mut buffer = Vec::with_capacity(HEADER_SIZE as usize);
let mut stream = Sink::new(&mut buffer);

stream.write(
&(
magic,
contents_size,
version,
texture_count,
alignment,
0u8,
stream_size,
),
Endian::Little,
)?;
stream.write(&gnmf.metadata, Endian::Little)?;

buffer.resize_with(HEADER_SIZE as usize, Default::default);
buffer
};

stream.write_bytes(&header)?;
stream.write_bytes(&body)?;
Ok(())
}

fn write_gnrl<Out>(&self, stream: &mut Sink<Out>, options: WriteOptions) -> Result<()>
where
Out: ?Sized + Write,
{
Expand All @@ -699,7 +794,7 @@ impl<'bytes> File<'bytes> {
} else {
chunk.as_bytes()
};
stream.write_all(bytes)?;
stream.write_bytes(bytes)?;
}

Ok(())
Expand Down
15 changes: 12 additions & 3 deletions src/fo4/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ pub use self::{
CapacityError as FileCapacityError, File, Header as FileHeader,
ReadOptions as FileReadOptions, ReadOptionsBuilder as FileReadOptionsBuilder,
WriteOptions as FileWriteOptions, WriteOptionsBuilder as FileWriteOptionsBuilder,
DX10 as DX10Header,
DX10 as DX10Header, GNMF as GNMFHeader,
},
hashing::{hash_file, hash_file_in_place, FileHash, Hash},
};
Expand Down Expand Up @@ -98,6 +98,9 @@ pub enum Error {
#[error(transparent)]
Infallible(#[from] Infallible),

#[error("an operation on two integers would have overflowed and corrupted data")]
IntegralOverflow,

#[error("an operation on an integer would have truncated and corrupted data")]
IntegralTruncation,

Expand All @@ -121,6 +124,9 @@ pub enum Error {

#[error(transparent)]
LZ4(#[from] lzzzz::Error),

#[error("support for this feature is not yet implemented")]
NotImplemented,
}

impl From<TryFromIntError> for Error {
Expand Down Expand Up @@ -172,12 +178,15 @@ impl CompressionLevel {
/// Represents the file format for an archive.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum Format {
/// A general archive can contain any kind of file.
/// A GNRL archive can contain any kind of file.
#[default]
GNRL,

/// A directx archive can only contain .dds files.
/// A DX10 archive can only contain .dds files.
DX10,

/// A GNMF archive can only contain .gnf files.
GNMF,
}

/// Indicates the version of an archive.
Expand Down

0 comments on commit 5152185

Please sign in to comment.