2024-07-23 18:34:45 -07:00

98 lines
2.3 KiB
Rust

//file format "sniff"
use binrw::{binrw, BinReaderExt, io::TakeSeekExt};
pub enum Error{
InvalidHeader(binrw::Error),
UnexpectedEOF,
InvalidBlockId(BlockId),
Seek(std::io::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
for block_id in 0..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)]
pub(crate) enum FourCC{
#[brw(magic=b"SNFM")]
Map,
#[brw(magic=b"SNFB")]
Bot,
#[brw(magic=b"SNFD")]
Demo,
}
#[binrw]
#[brw(little)]
struct Header{
/// Type of file
fourcc:FourCC,
/// Type version
version:u32,
/// Minimum data required to know the location of all streamable resources for this specific file
priming:u64,
/// uuid for this file
resource:u128,
//don't need try_calc: the struct will force field initialization anyways and it can be calculated there
block_count:u32,
#[br(count=block_count+1)]
block_location:Vec<u64>,
}
#[binrw]
#[brw(little)]
#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)]
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 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((&mut self.data).take_seek(block_end-block_start))
}
pub(crate) fn fourcc(&self)->FourCC{
self.header.fourcc
}
}