rs2cache/
cache.rs

1use crate::{
2    archive::{cache_archive::CacheArchive, Archive, ArchiveError},
3    djb2::djb2_hash,
4    js5_compression::{Js5Compression, Js5CompressionError},
5    js5_index::{Js5Index, Js5IndexError},
6    store::{store_open, Store, StoreError},
7    Cache,
8};
9use std::{collections::HashMap, io};
10use thiserror::Error;
11
12const ARCHIVESET: usize = (1 << 24) - 1;
13const UNPACKED_CACHE_SIZE_DEFAULT: usize = 1024;
14
15#[derive(Error, Debug)]
16pub enum CacheError {
17    #[error("IO error: {0}")]
18    Io(#[from] io::Error),
19    #[error("JS5 compression error: {0}")]
20    Js5Compression(#[from] Js5CompressionError),
21    #[error("JS5 index error: {0}")]
22    Js5Index(#[from] Js5IndexError),
23    #[error("Store error: {0}")]
24    Store(#[from] StoreError),
25    #[error("ArchiveError: {0}")]
26    ArchiveError(#[from] ArchiveError),
27    #[error("failed getting CacheArchive {0} from cache")]
28    ArchiveNotFound(u8),
29    #[error("failed reading CacheArchive {0} from cache")]
30    ArchiveRead(u8),
31}
32
33impl Cache {
34    /// Open a cache from a path
35    ///
36    /// # Arguments
37    ///
38    /// * `input_path` - The path to the cache
39    pub fn open(input_path: &str) -> Result<Cache, CacheError> {
40        Self::open_with_store(store_open(input_path)?)
41    }
42
43    /// Open a cache from a store
44    ///
45    /// # Arguments
46    ///
47    /// * `store` - The store to use
48    pub fn open_with_store(store: Box<dyn Store>) -> Result<Cache, CacheError> {
49        let mut cache = Self {
50            store,
51            archives: HashMap::new(),
52            _unpacked_cache_size: UNPACKED_CACHE_SIZE_DEFAULT,
53        };
54        cache.init()?;
55
56        // Return the Cache struct
57        Ok(cache)
58    }
59
60    fn init(&mut self) -> Result<(), CacheError> {
61        for archive in self.store.list(ARCHIVESET as u8)? {
62            let compressed = self.store.read(ARCHIVESET as u8, archive)?;
63
64            let buf = Js5Compression::uncompress(compressed, None)?;
65
66            let js5_index = Js5Index::read(buf)?;
67
68            let cache_archive = CacheArchive {
69                is_dirty: false,
70                index: js5_index,
71                archive: archive as u8,
72                unpacked_cache: HashMap::new(),
73            };
74
75            self.archives.insert(archive as u8, cache_archive);
76        }
77
78        Ok(())
79    }
80
81    /// Read a file from the cache
82    ///
83    /// # Arguments
84    ///
85    /// * `archive` - The archive to read from
86    /// * `group` - The group to read from
87    /// * `file` - The file to read
88    /// * `xtea_keys` - The XTEA keys to use for decryption. If None, the file will not be decrypted
89    pub fn read(
90        &mut self,
91        archive: u8,
92        group: u32,
93        file: u16,
94        xtea_keys: Option<[u32; 4]>,
95    ) -> Result<Vec<u8>, CacheError> {
96        Ok(self
97            .archives
98            .get_mut(&archive)
99            .ok_or(CacheError::ArchiveNotFound(archive))?
100            .read(group, file, xtea_keys, self.store.as_ref())?)
101    }
102
103    /// Read a file from the cache using a named group
104    ///
105    /// # Arguments
106    ///
107    /// * `archive` - The archive to read from
108    /// * `group` - The group to read from
109    /// * `file` - The file to read
110    /// * `xtea_keys` - The XTEA keys to use for decryption. If None, the file will not be decrypted
111    pub fn read_named_group(
112        &mut self,
113        archive: u8,
114        group: &str,
115        file: u16,
116        xtea_keys: Option<[u32; 4]>,
117    ) -> Result<Vec<u8>, CacheError> {
118        Ok(self
119            .archives
120            .get_mut(&archive)
121            .ok_or(CacheError::ArchiveNotFound(archive))?
122            .read_named_group(djb2_hash(group), file, xtea_keys, self.store.as_ref())?)
123    }
124}