#![allow(clippy::cast_possible_truncation)]
use byteorder::{LittleEndian, ReadBytesExt};
use std::io::{Read, Seek, SeekFrom};
use crate::error::{Error, Result};
pub mod magic {
pub const LE32: [u8; 16] = [
0x29, 0xDE, 0x6C, 0xC0, 0xBA, 0xA4, 0x53, 0x2B, 0x25, 0xF5, 0xB7, 0xA5, 0xF6, 0x66, 0xE2,
0xEE,
];
pub const LE64: [u8; 16] = [
0xE5, 0x9B, 0x49, 0x5E, 0x6F, 0x63, 0x1F, 0x14, 0x1E, 0x13, 0xEB, 0xA9, 0x90, 0xBE, 0xED,
0xC4,
];
pub const LE64_V2: [u8; 16] = [
0xE5, 0x2F, 0x4A, 0xE1, 0x6F, 0xC2, 0x8A, 0xEE, 0x1E, 0xD2, 0xB4, 0x4C, 0x90, 0xD7, 0x55,
0xAF,
];
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum Compression {
None = 0,
Oodle0 = 1,
Oodle1 = 2,
BitKnit = 4,
}
impl Compression {
pub fn from_u32(value: u32) -> Result<Self> {
match value {
0 => Ok(Compression::None),
1 => Ok(Compression::Oodle0),
2 => Ok(Compression::Oodle1),
4 => Ok(Compression::BitKnit),
_ => Err(Error::DecompressionError(format!(
"Unsupported GR2 compression format: {value}"
))),
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PointerSize {
Bit32,
Bit64,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct Gr2Magic {
pub signature: [u8; 16],
pub headers_size: u32,
pub header_format: u32,
pub reserved: [u8; 8],
}
impl Gr2Magic {
pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
let mut signature = [0u8; 16];
reader.read_exact(&mut signature)?;
let headers_size = reader.read_u32::<LittleEndian>()?;
let header_format = reader.read_u32::<LittleEndian>()?;
let mut reserved = [0u8; 8];
reader.read_exact(&mut reserved)?;
Ok(Self {
signature,
headers_size,
header_format,
reserved,
})
}
pub fn pointer_size(&self) -> Result<PointerSize> {
if self.signature == magic::LE32 {
Ok(PointerSize::Bit32)
} else if self.signature == magic::LE64 || self.signature == magic::LE64_V2 {
Ok(PointerSize::Bit64)
} else {
Err(Error::DecompressionError(
"Invalid GR2 magic signature".to_string(),
))
}
}
#[must_use]
pub fn is_valid(&self) -> bool {
self.signature == magic::LE32
|| self.signature == magic::LE64
|| self.signature == magic::LE64_V2
}
}
#[derive(Debug, Clone, Copy, Default)]
#[allow(dead_code)]
pub struct SectionRef {
pub section: u32,
pub offset: u32,
}
impl SectionRef {
pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
Ok(Self {
section: reader.read_u32::<LittleEndian>()?,
offset: reader.read_u32::<LittleEndian>()?,
})
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct Gr2Header {
pub version: u32,
pub file_size: u32,
pub crc: u32,
pub sections_offset: u32,
pub num_sections: u32,
pub root_type: SectionRef,
pub root_node: SectionRef,
pub tag: u32,
pub extra_tags: [u32; 4],
pub string_table_crc: Option<u32>,
}
impl Gr2Header {
pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
let version = reader.read_u32::<LittleEndian>()?;
if version != 6 && version != 7 {
return Err(Error::DecompressionError(format!(
"Unsupported GR2 version: {version}"
)));
}
let file_size = reader.read_u32::<LittleEndian>()?;
let crc = reader.read_u32::<LittleEndian>()?;
let sections_offset = reader.read_u32::<LittleEndian>()?;
let num_sections = reader.read_u32::<LittleEndian>()?;
let root_type = SectionRef::read(reader)?;
let root_node = SectionRef::read(reader)?;
let tag = reader.read_u32::<LittleEndian>()?;
let mut extra_tags = [0u32; 4];
for tag in &mut extra_tags {
*tag = reader.read_u32::<LittleEndian>()?;
}
let string_table_crc = if version == 7 {
let crc = reader.read_u32::<LittleEndian>()?;
let mut reserved = [0u8; 12];
reader.read_exact(&mut reserved)?;
Some(crc)
} else {
None
};
Ok(Self {
version,
file_size,
crc,
sections_offset,
num_sections,
root_type,
root_node,
tag,
extra_tags,
string_table_crc,
})
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct SectionHeader {
pub compression: Compression,
pub offset_in_file: u32,
pub compressed_size: u32,
pub uncompressed_size: u32,
pub alignment: u32,
pub first_16bit: u32,
pub first_8bit: u32,
pub relocations_offset: u32,
pub num_relocations: u32,
pub mixed_marshalling_offset: u32,
pub num_mixed_marshalling: u32,
}
impl SectionHeader {
pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
let compression_raw = reader.read_u32::<LittleEndian>()?;
let compression = Compression::from_u32(compression_raw)?;
Ok(Self {
compression,
offset_in_file: reader.read_u32::<LittleEndian>()?,
compressed_size: reader.read_u32::<LittleEndian>()?,
uncompressed_size: reader.read_u32::<LittleEndian>()?,
alignment: reader.read_u32::<LittleEndian>()?,
first_16bit: reader.read_u32::<LittleEndian>()?,
first_8bit: reader.read_u32::<LittleEndian>()?,
relocations_offset: reader.read_u32::<LittleEndian>()?,
num_relocations: reader.read_u32::<LittleEndian>()?,
mixed_marshalling_offset: reader.read_u32::<LittleEndian>()?,
num_mixed_marshalling: reader.read_u32::<LittleEndian>()?,
})
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.compressed_size == 0
}
#[must_use]
pub fn compression_ratio(&self) -> Option<f64> {
if self.compressed_size > 0 {
Some(f64::from(self.uncompressed_size) / f64::from(self.compressed_size))
} else {
None
}
}
}
#[derive(Debug)]
pub struct Gr2File {
pub magic: Gr2Magic,
pub header: Gr2Header,
pub sections: Vec<SectionHeader>,
data: Vec<u8>,
}
impl Gr2File {
pub fn from_bytes(data: &[u8]) -> Result<Self> {
let mut cursor = std::io::Cursor::new(data);
let magic = Gr2Magic::read(&mut cursor)?;
if !magic.is_valid() {
return Err(Error::DecompressionError(
"Invalid GR2 magic signature".to_string(),
));
}
cursor.seek(SeekFrom::Start(0x20))?;
let header = Gr2Header::read(&mut cursor)?;
let section_header_offset = 0x20 + u64::from(header.sections_offset);
cursor.seek(SeekFrom::Start(section_header_offset))?;
let mut sections = Vec::with_capacity(header.num_sections as usize);
for _ in 0..header.num_sections {
sections.push(SectionHeader::read(&mut cursor)?);
}
Ok(Self {
magic,
header,
sections,
data: data.to_vec(),
})
}
pub fn section_compressed_data(&self, index: usize) -> Result<&[u8]> {
let section = self
.sections
.get(index)
.ok_or_else(|| Error::DecompressionError(format!("Invalid section index: {index}")))?;
if section.is_empty() {
return Ok(&[]);
}
let start = section.offset_in_file as usize;
let end = start + section.compressed_size as usize;
if end > self.data.len() {
return Err(Error::UnexpectedEof);
}
Ok(&self.data[start..end])
}
pub fn pointer_size(&self) -> Result<PointerSize> {
self.magic.pointer_size()
}
}