pub mod anim;
pub mod bitmap;
pub mod bitmap_font;
pub mod index;
pub mod mesh;
pub mod scene;
#[cfg(feature = "bake")]
pub mod buf;
mod compression;
use {
self::{
anim::Animation, bitmap::Bitmap, bitmap_font::BitmapFont, compression::Compression,
mesh::Mesh, scene::Scene,
},
bitflags::bitflags,
log::{trace, warn},
paste::paste,
serde::{Deserialize, Serialize, de::DeserializeOwned},
std::{
collections::HashMap,
fmt::{Debug, Formatter},
fs::File,
io::{BufReader, Cursor, Error, ErrorKind, Read, Seek, SeekFrom},
mem::size_of,
ops::Range,
path::{Path, PathBuf},
},
};
pub type Vec3 = [f32; 3];
pub type Quat = [f32; 4];
pub type Mat4 = [f32; 16];
pub(crate) const PAK_HASH_LEN: usize = size_of::<u64>();
const FNV_OFFSET: u64 = 0xcbf29ce484222325;
const FNV_PRIME: u64 = 0x100000001b3;
fn update_hash(hash: u64, data: &[u8]) -> u64 {
data.iter().fold(hash, |hash, byte| {
(hash ^ u64::from(*byte)).wrapping_mul(FNV_PRIME)
})
}
pub(crate) fn pak_hash_stream(reader: &mut impl Read, len: u64) -> Result<u64, Error> {
let mut hash = FNV_OFFSET;
let mut remaining = len;
let mut buf = [0; 8192];
while remaining > 0 {
let limit = if remaining < buf.len() as u64 {
remaining as usize
} else {
buf.len()
};
let read = reader.read(&mut buf[..limit])?;
if read == 0 {
return Err(Error::from(ErrorKind::UnexpectedEof));
}
hash = update_hash(hash, &buf[..read]);
remaining -= read as u64;
}
Ok(hash)
}
fn read_hash_trailer(reader: &mut impl Read) -> Result<u64, Error> {
let mut hash = [0; PAK_HASH_LEN];
reader.read_exact(&mut hash)?;
let (hash, consumed) =
bincode::serde::decode_from_slice::<u64, _>(&hash, bincode::config::legacy())
.map_err(|_| Error::from(ErrorKind::InvalidData))?;
if consumed == PAK_HASH_LEN {
Ok(hash)
} else {
Err(Error::from(ErrorKind::InvalidData))
}
}
#[derive(Debug, Default, Deserialize, Serialize)]
struct Data {
ids: HashMap<String, Id>,
materials: Vec<MaterialInfo>,
anims: Vec<DataRef<Animation>>,
bitmap_fonts: Vec<DataRef<BitmapFont>>,
bitmaps: Vec<DataRef<Bitmap>>,
blobs: Vec<DataRef<Vec<u8>>>,
meshes: Vec<DataRef<Mesh>>,
scenes: Vec<DataRef<Scene>>,
}
#[derive(Deserialize, PartialEq, Serialize)]
enum DataRef<T> {
Data(T),
Ref(Range<u32>),
}
impl<T> DataRef<T> {
fn pos_len(&self) -> Result<(u64, usize), Error> {
match self {
Self::Ref(range) => {
let len = range
.end
.checked_sub(range.start)
.ok_or_else(|| Error::from(ErrorKind::InvalidData))?;
Ok((range.start as _, len as _))
}
_ => {
warn!("Expected position and length but found data");
Err(Error::from(ErrorKind::InvalidInput))
}
}
}
}
impl<T> DataRef<T>
where
T: Serialize,
{
#[cfg(feature = "bake")]
fn serialize(&self) -> Result<Vec<u8>, Error> {
let mut buf = vec![];
let data = match self {
Self::Data(t) => t,
Self::Ref(_) => return Err(Error::from(ErrorKind::InvalidData)),
};
bincode::serde::encode_into_std_write(data, &mut buf, bincode::config::legacy())
.map_err(|_| Error::from(ErrorKind::InvalidData))?;
Ok(buf)
}
}
impl<T> Debug for DataRef<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Data(_) => "Data",
Self::Ref(_) => "DataRef",
})
}
}
macro_rules! id_enum {
($($variant:ident),*) => {
paste::paste! {
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
enum Id {
$(
$variant([<$variant Id>]),
)*
}
impl Id {
$(
fn [<as_ $variant:snake>](&self) -> Option<[<$variant Id>]> {
match self {
Self::$variant(id) => Some(*id),
_ => None,
}
}
)*
}
$(
impl From<[<$variant Id>]> for Id {
fn from(id: [<$variant Id>]) -> Self {
Self::$variant(id)
}
}
)*
}
};
}
id_enum!(Animation, Bitmap, BitmapFont, Blob, Material, Mesh, Scene);
macro_rules! id_struct {
($name: ident) => {
paste! {
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Ord,
Serialize)]
pub struct [<$name Id>](pub usize);
}
};
}
id_struct!(Animation);
id_struct!(Bitmap);
id_struct!(BitmapFont);
id_struct!(Blob);
id_struct!(Material);
id_struct!(Mesh);
id_struct!(Scene);
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct MaterialInfo {
pub color: BitmapId,
pub emissive: Option<BitmapId>,
pub normal: Option<BitmapId>,
pub params: Option<BitmapId>,
pub params_used: MaterialParameterFlags,
}
bitflags! {
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct MaterialParameterFlags: u8 {
const METAL = 1 << 0;
const ROUGH = 1 << 1;
const HEIGHT = 1 << 2;
const TRANSMISSION = 1 << 3;
}
}
pub trait Pak {
fn animation_id(&self, key: impl AsRef<str>) -> Option<AnimationId>;
fn bitmap_font_id(&self, key: impl AsRef<str>) -> Option<BitmapFontId>;
fn bitmap_id(&self, key: impl AsRef<str>) -> Option<BitmapId>;
fn blob_id(&self, key: impl AsRef<str>) -> Option<BlobId>;
fn material_id(&self, key: impl AsRef<str>) -> Option<MaterialId>;
fn mesh_id(&self, key: impl AsRef<str>) -> Option<MeshId>;
fn scene_id(&self, key: impl AsRef<str>) -> Option<SceneId>;
fn read_animation_id(&mut self, id: impl Into<AnimationId>) -> Result<Animation, Error>;
fn read_bitmap_font_id(&mut self, id: impl Into<BitmapFontId>) -> Result<BitmapFont, Error>;
fn read_bitmap_id(&mut self, id: impl Into<BitmapId>) -> Result<Bitmap, Error>;
fn read_blob_id(&mut self, id: impl Into<BlobId>) -> Result<Vec<u8>, Error>;
fn read_material_id(&self, id: impl Into<MaterialId>) -> Option<MaterialInfo>;
fn read_mesh_id(&mut self, id: impl Into<MeshId>) -> Result<Mesh, Error>;
fn read_scene_id(&mut self, id: impl Into<SceneId>) -> Result<Scene, Error>;
fn read_material(&self, key: impl AsRef<str>) -> Option<MaterialInfo> {
trace!("Reading material {}", key.as_ref());
if let Some(id) = self.material_id(key) {
self.read_material_id(id)
} else {
None
}
}
fn read_animation(&mut self, key: impl AsRef<str>) -> Result<Animation, Error> {
trace!("Reading animation {}", key.as_ref());
if let Some(h) = self.animation_id(key) {
self.read_animation_id(h)
} else {
Err(Error::from(ErrorKind::InvalidInput))
}
}
fn read_bitmap_font(&mut self, key: impl AsRef<str>) -> Result<BitmapFont, Error> {
trace!("Reading bitmap font {}", key.as_ref());
if let Some(h) = self.bitmap_font_id(key) {
self.read_bitmap_font_id(h)
} else {
Err(Error::from(ErrorKind::InvalidInput))
}
}
fn read_bitmap(&mut self, key: impl AsRef<str>) -> Result<Bitmap, Error> {
trace!("Reading bitmap {}", key.as_ref());
if let Some(h) = self.bitmap_id(key) {
self.read_bitmap_id(h)
} else {
Err(Error::from(ErrorKind::InvalidInput))
}
}
fn read_blob(&mut self, key: impl AsRef<str>) -> Result<Vec<u8>, Error> {
trace!("Reading blob {}", key.as_ref());
if let Some(h) = self.blob_id(key) {
self.read_blob_id(h)
} else {
Err(Error::from(ErrorKind::InvalidInput))
}
}
fn read_mesh(&mut self, key: impl AsRef<str>) -> Result<Mesh, Error> {
trace!("Reading mesh {}", key.as_ref());
if let Some(h) = self.mesh_id(key) {
self.read_mesh_id(h)
} else {
Err(Error::from(ErrorKind::InvalidInput))
}
}
fn read_scene(&mut self, key: impl AsRef<str>) -> Result<Scene, Error> {
trace!("Reading scene {}", key.as_ref());
if let Some(h) = self.scene_id(key) {
self.read_scene_id(h)
} else {
Err(Error::from(ErrorKind::InvalidInput))
}
}
}
#[derive(Debug)]
pub struct PakBuf {
compression: Option<Compression>,
data: Data,
reader: Box<dyn Stream>,
}
impl PakBuf {
pub fn animation_count(&self) -> usize {
self.data.anims.len()
}
pub fn bitmap_count(&self) -> usize {
self.data.bitmaps.len()
}
pub fn bitmap_font_count(&self) -> usize {
self.data.bitmap_fonts.len()
}
pub fn blob_count(&self) -> usize {
self.data.blobs.len()
}
fn deserialize<T>(&mut self, pos: u64, len: usize) -> Result<T, Error>
where
T: DeserializeOwned,
{
trace!("Read data: {len} bytes ({pos}..{})", pos + len as u64);
let mut buf = vec![0; len];
self.reader.seek(SeekFrom::Start(pos))?;
self.reader.read_exact(&mut buf)?;
let data = buf.as_slice();
if let Some(compressed) = self.compression {
let mut reader = compressed.new_reader(data);
let decoded =
bincode::serde::decode_from_std_read(&mut reader, bincode::config::legacy())
.map_err(|err| {
warn!("Unable to deserialize: {}", err);
Error::from(ErrorKind::InvalidData)
})?;
let mut trailing = [0; 1];
match reader.read(&mut trailing) {
Ok(0) => Ok(decoded),
Ok(_) => {
warn!("Trailing bytes after deserialized data");
Err(Error::from(ErrorKind::InvalidData))
}
Err(err) => {
warn!("Unable to verify deserialized data end: {}", err);
Err(Error::from(ErrorKind::InvalidData))
}
}
} else {
let (decoded, consumed) =
bincode::serde::decode_from_slice(data, bincode::config::legacy()).map_err(
|err| {
warn!("Unable to deserialize: {}", err);
Error::from(ErrorKind::InvalidData)
},
)?;
if consumed == data.len() {
Ok(decoded)
} else {
warn!("Trailing bytes after deserialized data");
Err(Error::from(ErrorKind::InvalidData))
}
}
}
pub fn from_stream(mut stream: impl Stream + 'static) -> Result<Self, Error> {
fn decode<T>(stream: &mut impl Read, msg: &str) -> Result<T, Error>
where
T: DeserializeOwned,
{
bincode::serde::decode_from_std_read(stream, bincode::config::legacy()).map_err(|_| {
warn!("{}", msg);
Error::from(ErrorKind::InvalidData)
})
}
let magic_bytes: [u8; 20] = decode(&mut stream, "Unable to read magic bytes")?;
if &magic_bytes != b"ATTACKGOAT-PAK-V1.0 " {
warn!("Unsupported magic bytes");
return Err(Error::from(ErrorKind::InvalidData));
}
let skip: u32 = decode(&mut stream, "Unable to read skip length")?;
let compression: Option<Compression> =
decode(&mut stream, "Unable to read compression data")?;
let stream_end = stream.seek(SeekFrom::End(0))?;
let header_end = stream_end
.checked_sub(PAK_HASH_LEN as u64)
.ok_or_else(|| Error::from(ErrorKind::InvalidData))?;
let header_len = header_end
.checked_sub(skip as u64)
.ok_or_else(|| Error::from(ErrorKind::InvalidData))?;
stream.seek(SeekFrom::Start(skip as _))?;
let data: Data = if let Some(compressed) = compression {
let mut header = (&mut stream).take(header_len);
let mut compressed = compressed.new_reader(&mut header);
decode(&mut compressed, "Unable to read header")?
} else {
let mut header = (&mut stream).take(header_len);
decode(&mut header, "Unable to read header")?
};
trace!(
"Read header: {} bytes ({} keys)",
header_len,
data.ids.len()
);
Ok(Self {
compression,
data,
reader: Box::new(stream),
})
}
pub fn keys(&self) -> impl Iterator<Item = &str> {
self.data.ids.keys().map(|key| key.as_str())
}
pub fn validate_hash(&self) -> Result<bool, Error> {
let mut reader = self.reader.open()?;
let stream_end = reader.seek(SeekFrom::End(0))?;
let payload_len = stream_end
.checked_sub(PAK_HASH_LEN as u64)
.ok_or_else(|| Error::from(ErrorKind::InvalidData))?;
reader.seek(SeekFrom::Start(0))?;
let actual = pak_hash_stream(&mut reader, payload_len)?;
let expected = read_hash_trailer(&mut reader)?;
Ok(actual == expected)
}
pub fn mesh_count(&self) -> usize {
self.data.meshes.len()
}
pub fn material_count(&self) -> usize {
self.data.materials.len()
}
pub fn open(path: impl AsRef<Path>) -> Result<Self, Error> {
let path = path.as_ref().to_path_buf();
let file = File::open(&path)?;
let buf = BufReader::new(file);
Self::from_stream(PakFile { buf, path })
}
pub fn scene_count(&self) -> usize {
self.data.scenes.len()
}
}
impl Pak for PakBuf {
fn animation_id(&self, key: impl AsRef<str>) -> Option<AnimationId> {
self.data
.ids
.get(key.as_ref())
.and_then(|id| id.as_animation())
}
fn bitmap_font_id(&self, key: impl AsRef<str>) -> Option<BitmapFontId> {
self.data
.ids
.get(key.as_ref())
.and_then(|id| id.as_bitmap_font())
}
fn bitmap_id(&self, key: impl AsRef<str>) -> Option<BitmapId> {
self.data
.ids
.get(key.as_ref())
.and_then(|id| id.as_bitmap())
}
fn blob_id(&self, key: impl AsRef<str>) -> Option<BlobId> {
self.data.ids.get(key.as_ref()).and_then(|id| id.as_blob())
}
fn material_id(&self, key: impl AsRef<str>) -> Option<MaterialId> {
self.data
.ids
.get(key.as_ref())
.and_then(|id| id.as_material())
}
fn mesh_id(&self, key: impl AsRef<str>) -> Option<MeshId> {
self.data.ids.get(key.as_ref()).and_then(|id| id.as_mesh())
}
fn scene_id(&self, key: impl AsRef<str>) -> Option<SceneId> {
self.data.ids.get(key.as_ref()).and_then(|id| id.as_scene())
}
fn read_animation_id(&mut self, id: impl Into<AnimationId>) -> Result<Animation, Error> {
let id = id.into();
trace!("Deserializing animation {}", id.0);
let (pos, len) = self
.data
.anims
.get(id.0)
.ok_or_else(|| Error::from(ErrorKind::InvalidInput))?
.pos_len()?;
self.deserialize(pos, len)
}
fn read_bitmap_font_id(&mut self, id: impl Into<BitmapFontId>) -> Result<BitmapFont, Error> {
let id = id.into();
trace!("Deserializing bitmap font {}", id.0);
let (pos, len) = self
.data
.bitmap_fonts
.get(id.0)
.ok_or_else(|| Error::from(ErrorKind::InvalidInput))?
.pos_len()?;
self.deserialize(pos, len)
}
fn read_bitmap_id(&mut self, id: impl Into<BitmapId>) -> Result<Bitmap, Error> {
let id = id.into();
trace!("Deserializing bitmap {}", id.0);
let (pos, len) = self
.data
.bitmaps
.get(id.0)
.ok_or_else(|| Error::from(ErrorKind::InvalidInput))?
.pos_len()?;
self.deserialize(pos, len)
}
fn read_blob_id(&mut self, id: impl Into<BlobId>) -> Result<Vec<u8>, Error> {
let id = id.into();
trace!("Deserializing blob {}", id.0);
let (pos, len) = self
.data
.blobs
.get(id.0)
.ok_or_else(|| Error::from(ErrorKind::InvalidInput))?
.pos_len()?;
self.deserialize(pos, len)
}
fn read_material_id(&self, id: impl Into<MaterialId>) -> Option<MaterialInfo> {
let id = id.into();
self.data.materials.get(id.0).copied()
}
fn read_mesh_id(&mut self, id: impl Into<MeshId>) -> Result<Mesh, Error> {
let id = id.into();
trace!("Deserializing mesh {}", id.0);
let (pos, len) = self
.data
.meshes
.get(id.0)
.ok_or_else(|| Error::from(ErrorKind::InvalidInput))?
.pos_len()?;
self.deserialize(pos, len)
}
fn read_scene_id(&mut self, id: impl Into<SceneId>) -> Result<Scene, Error> {
let id = id.into();
trace!("Deserializing scene {}", id.0);
let (pos, len) = self
.data
.scenes
.get(id.0)
.ok_or_else(|| Error::from(ErrorKind::InvalidInput))?
.pos_len()?;
self.deserialize(pos, len)
}
}
#[derive(Debug)]
struct PakFile {
buf: BufReader<File>,
path: PathBuf,
}
impl From<&'static [u8]> for PakBuf {
fn from(data: &'static [u8]) -> Self {
Self::from_stream(Cursor::new(data)).expect("invalid pak data")
}
}
pub trait Stream: Debug + Read + Seek + Send {
fn open(&self) -> Result<Box<dyn Stream>, Error>;
}
impl Stream for PakFile {
fn open(&self) -> Result<Box<dyn Stream>, Error> {
let file = File::open(&self.path)?;
let buf = BufReader::new(file);
Ok(Box::new(PakFile {
buf,
path: self.path.clone(),
}))
}
}
impl Read for PakFile {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.buf.read(buf)
}
}
impl Seek for PakFile {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
self.buf.seek(pos)
}
}
impl Stream for Cursor<&'static [u8]> {
fn open(&self) -> Result<Box<dyn Stream>, Error> {
Ok(Box::new(Cursor::new(*self.get_ref())))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn empty_pak() -> PakBuf {
PakBuf {
compression: None,
data: Data::default(),
reader: Box::new(Cursor::new(&[] as &'static [u8])),
}
}
#[test]
fn invalid_read_ids_return_invalid_input() {
assert_eq!(
empty_pak()
.read_animation_id(AnimationId(0))
.expect_err("invalid animation id should error")
.kind(),
ErrorKind::InvalidInput,
);
assert_eq!(
empty_pak()
.read_bitmap_font_id(BitmapFontId(0))
.expect_err("invalid bitmap font id should error")
.kind(),
ErrorKind::InvalidInput,
);
assert_eq!(
empty_pak()
.read_bitmap_id(BitmapId(0))
.expect_err("invalid bitmap id should error")
.kind(),
ErrorKind::InvalidInput,
);
assert_eq!(
empty_pak()
.read_blob_id(BlobId(0))
.expect_err("invalid blob id should error")
.kind(),
ErrorKind::InvalidInput,
);
assert_eq!(
empty_pak()
.read_mesh_id(MeshId(0))
.expect_err("invalid mesh id should error")
.kind(),
ErrorKind::InvalidInput,
);
assert_eq!(
empty_pak()
.read_scene_id(SceneId(0))
.expect_err("invalid scene id should error")
.kind(),
ErrorKind::InvalidInput,
);
}
#[test]
fn invalid_data_ref_range_returns_invalid_data() {
let mut pak = empty_pak();
pak.data.blobs.push(DataRef::Ref(10..5));
assert_eq!(
pak.read_blob_id(BlobId(0))
.expect_err("invalid blob range should error")
.kind(),
ErrorKind::InvalidData,
);
}
#[test]
fn trailing_asset_bytes_return_invalid_data() {
let mut encoded = Vec::new();
bincode::serde::encode_into_std_write(
b"blob".to_vec(),
&mut encoded,
bincode::config::legacy(),
)
.unwrap();
encoded.extend_from_slice(b"junk");
let encoded: &'static [u8] = Box::leak(encoded.into_boxed_slice());
let mut pak = empty_pak();
pak.data.blobs.push(DataRef::Ref(0..encoded.len() as u32));
pak.reader = Box::new(Cursor::new(encoded));
assert_eq!(
pak.read_blob_id(BlobId(0))
.expect_err("trailing asset bytes should error")
.kind(),
ErrorKind::InvalidData,
);
}
}