strafe-client-jed/lib/snf/src/file.rs

111 lines
2.7 KiB
Rust

//file format "sniff"
use binrw::{binrw,BinReaderExt,io::TakeSeekExt};
#[derive(Debug)]
pub enum Error{
InvalidHeader(binrw::Error),
UnexpectedEOF,
InvalidBlockId(BlockId),
Seek(std::io::Error),
}
impl std::fmt::Display for Error{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for Error{}
/* spec
//begin global header
//global metadata (32 bytes)
b"SNFB"
u32 format_version
u64 priming_bytes
//how many bytes of the file must be read to guarantee all of the expected
//format-specific metadata is available to facilitate streaming the remaining contents
//used by the database to guarantee that it serves at least the bare minimum
u128 resource_uuid
//identifies the file from anywhere for any other file
//global block layout (variable size)
u64 num_blocks
//the start of the first block is implicitly after the global header (32)
//num_blocks+1 used in Header.block_location is implicitly the end of the file
for block_id in 1..num_blocks{
u64 first_byte
}
//end global header
//begin blocks
//each block is compressed with zstd or gz or something
*/
#[binrw]
#[brw(little)]
#[derive(Clone,Copy,Debug)]
pub(crate) enum FourCC{
#[brw(magic=b"SNFM")]
Map,
#[brw(magic=b"SNFB")]
Bot,
#[brw(magic=b"SNFD")]
Demo,
}
#[binrw]
#[brw(little)]
#[derive(Debug)]
pub struct Header{
/// Type of file
pub fourcc:FourCC,
/// File format version
pub version:u32,
/// Minimum data required to know the location of all streamable resources for this specific file
pub priming:u64,
/// uuid for this file
pub resource:u128,
//don't need try_calc: the struct will force field initialization anyways and it can be calculated there
pub block_count:u32,
#[br(count=block_count+1)]
pub block_location:Vec<u64>,
}
#[binrw]
#[brw(little)]
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,Ord,PartialEq,PartialOrd)]
pub struct BlockId(u32);
pub(crate) struct File<R:BinReaderExt>{
header:Header,
//reference to the data
data:R,
}
impl<R:BinReaderExt> File<R>{
pub(crate) fn new(mut input:R)->Result<File<R>,Error>{
Ok(File{
header:input.read_le().map_err(Error::InvalidHeader)?,
data:input,
})
}
pub(crate) fn data_mut(&mut self)->&mut R{
&mut self.data
}
pub(crate) fn block_reader(&mut self,block_id:BlockId)->Result<binrw::io::TakeSeek<&mut R>,Error>{
if self.header.block_location.len() as u32<=block_id.get(){
return Err(Error::InvalidBlockId(block_id))
}
let block_start=self.header.block_location[block_id.get() as usize];
let block_end=self.header.block_location[block_id.get() as usize+1];
self.data.seek(std::io::SeekFrom::Start(block_start)).map_err(Error::Seek)?;
Ok(self.data_mut().take_seek(block_end-block_start))
}
pub(crate) fn fourcc(&self)->FourCC{
self.header.fourcc
}
}