pak_db/
lib.rs

1#![doc = include_str!("../README.md")]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/MrVintage710/pak/refs/heads/main/docs/icon.png")]
3
4use std::{collections::HashMap, fs::File, io::{BufReader, Read, Seek, SeekFrom}, path::Path, sync::{RwLock, RwLockWriteGuard}};
5use btree::PakTree;
6use item::{PakItemDeserialize, PakItemDeserializeGroup};
7use meta::{PakMeta, PakSizing};
8use pointer::{PakPointer, PakUntypedPointer};
9use query::PakQueryExpression;
10
11use crate::{error::PakResult};
12
13#[cfg(test)]
14mod test;
15
16pub mod meta;
17pub mod item;
18pub mod index;
19pub mod value;
20pub(crate) mod btree;
21pub mod query;
22pub mod error;
23pub mod pointer;
24pub mod builder;
25
26//==============================================================================================
27//        Pak File
28//==============================================================================================
29
30pub const PAK_FILE_VERSION : &'static str = "1.1";
31
32pub const PAK_SIZING_STRUCT_SIZE_IN_BYTES : u64 = 32;
33
34/// Represents a Pak file. This struct provides access to the metadata and data stored within the Pak file.
35pub struct Pak {
36    sizing : PakSizing,
37    meta : PakMeta,
38    source : RwLock<Box<dyn PakSource + Send + Sync + 'static>>
39}
40
41impl Pak {
42    /// Creates a new Pak instance from a [PakSource](crate::PakSource).
43    pub fn new<S>(mut source : S) -> PakResult<Self> where S : PakSource + Send + Sync + 'static {
44        let sizing_pointer = PakPointer::new_untyped(0, PAK_SIZING_STRUCT_SIZE_IN_BYTES);
45        let sizing_buffer = source.read(&sizing_pointer, 0)?;
46        let sizing : PakSizing = bincode::deserialize(&sizing_buffer)?;
47        
48        let meta_pointer = PakPointer::new_untyped(PAK_SIZING_STRUCT_SIZE_IN_BYTES, sizing.meta_size);
49        let meta_buffer = source.read(&meta_pointer, 0)?;
50        let meta : PakMeta = bincode::deserialize(&meta_buffer)?;
51
52        Ok(Self { sizing, source : RwLock::new(Box::new(source)), meta })
53    }
54    
55    /// Loads a Pak from the specified file path. This will not load the entire pak file into memory, just the header.
56    pub fn new_from_file<P>(path : P) -> PakResult<Self> where P : AsRef<Path> {
57        let file = File::open(path)?;
58        Self::new(BufReader::new(file))
59    }
60    
61    /// Loads an object from the pak file via queried indices. This will only load the necessary data into memory.
62    pub fn query<T>(&self, query : impl PakQueryExpression<T>) -> PakResult<T::ReturnType> where T : PakItemDeserializeGroup  {
63        let pointers = query.execute(self)?.into_iter().collect();
64        T::deserialize_group(self, pointers)
65    }
66    
67    /// Loads an object from the pak file via queried indices. This will only load the necessary data into memory.
68    pub fn query_sql<T>(&self, pql : &str) -> PakResult<T::ReturnType> where T : PakItemDeserializeGroup + 'static  {
69        let query = crate::query::pql::pql(pql)?;
70        self.query::<T>(query)
71    }
72    
73    /// Returns the size of the pak file in bytes.
74    pub fn size(&self) -> u64 {
75        24 + self.sizing.meta_size + self.sizing.indices_size + self.sizing.vault_size
76    }
77    
78    /// Returns the name given to the pak file.
79    pub fn name(&self) -> &str {
80        &self.meta.name
81    }
82    
83    /// Returns the version of the pak file.
84    pub fn version(&self) -> &str {
85        &self.meta.version
86    }
87    
88    /// Returns the author of the pak file.
89    pub fn author(&self) -> &str {
90        &self.meta.author
91    }
92    
93    /// Returns the description of the pak file.
94    pub fn description(&self) -> &str {
95        &self.meta.description
96    }
97    
98    pub fn read_err<T>(&self, pointer : &PakPointer) -> PakResult<T> where T : PakItemDeserialize {
99        if !pointer.type_is_match::<T>() { return Err(error::PakError::TypeMismatchError(pointer.type_name().to_string(), std::any::type_name::<T>().to_string())) }
100        let Ok(mut source) = self.source.write() else { return Err(error::PakError::SourceInUse)};
101        self.read_internal(pointer, &mut source)
102    }
103    
104    pub fn read<T>(&self, pointer : &PakPointer) -> Option<T> where T : PakItemDeserialize {
105        self.read_err::<T>(pointer).ok()
106    }
107    
108    fn read_internal<T>(&self, pointer : &PakPointer, source : &mut RwLockWriteGuard<Box<dyn PakSource + Send + Sync + 'static>>) -> PakResult<T> where T : PakItemDeserialize {
109        let buffer = source.read(pointer, self.get_vault_start())?;
110        let res = T::from_bytes(&buffer)?;
111        Ok(res)
112    }
113    
114    pub(crate) fn get_tree(&self, key : &str) -> PakResult<PakTree<'_>> {
115        PakTree::new(self, key)
116    }
117    
118    pub(crate) fn fetch_indices(&self) -> PakResult<HashMap<String, PakUntypedPointer>> {
119        let pointer = PakPointer::new_untyped(self.get_indices_start(), self.sizing.indices_size);
120        let Ok(mut source) = self.source.write() else { return Err(error::PakError::SourceInUse) };
121        let buffer = source.read(&pointer, 0)?;
122        let indices = bincode::deserialize(&buffer)?;
123        Ok(indices)
124    }
125    
126    pub(crate) fn fetch_all_pointers_of<T>(&self) -> PakResult<Vec<PakPointer>> where T : PakItemDeserializeGroup {
127        let Ok(mut source) = self.source.write() else { return Err(error::PakError::SourceInUse) };
128        let lists_pointer = PakPointer::new_untyped(self.get_list_start(), self.sizing.list_size);
129        let lists_buffer = source.read(&lists_pointer, 0)?;
130        let lists : HashMap<String, PakPointer> = bincode::deserialize(&lists_buffer)?;
131        let values = T::get_types().into_iter()
132            .filter_map(|type_name| lists.get(type_name))
133            .filter_map(|pointer| self.read_internal::<Vec<PakPointer>>(pointer, &mut source).ok())
134            .flatten()
135            .collect::<Vec<_>>();
136        Ok(values)
137    }
138    
139    pub(crate) fn get_vault_start(&self) -> u64 {
140        // To be honest, I'm not sure why this start is offset by 8, it just is and I am to scared to ask.
141        self.get_list_start() + self.sizing.list_size + 8
142    }
143    
144    pub(crate) fn get_list_start(&self) -> u64 {
145        self.get_indices_start() + self.sizing.indices_size
146    }
147    
148    pub(crate) fn get_indices_start(&self) -> u64 {
149        PAK_SIZING_STRUCT_SIZE_IN_BYTES + self.sizing.meta_size
150    }
151}
152
153//==============================================================================================
154//        PakSource
155//==============================================================================================
156
157///This is where a Pak file will load from. This trait is automatically implemented for any type that implements [Read](std::io::Read) and [Seek](std::io::Seek).
158pub trait PakSource {
159    ///Returns data from the source based on a [PakPointer](crate::PakPointer)
160    fn read(&mut self, pointer : &PakPointer, offset : u64) -> PakResult<Vec<u8>>;
161}
162
163impl <R> PakSource for R where R : Read + Seek {
164    fn read(&mut self, pointer : &PakPointer, offset : u64) -> PakResult<Vec<u8>> {
165        let mut buffer = vec![0u8; pointer.size() as usize];
166        self.seek(SeekFrom::Start(pointer.offset() + offset))?;
167        self.read_exact(&mut buffer)?;
168        Ok(buffer)
169    }
170}
171