diff --git a/src/crypto.rs b/src/crypto.rs index 439b5c0..85c90cb 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -34,9 +34,13 @@ impl PkgGcmState { } fn shift_nonce(&mut self, pkg_id: u16, version: PackageVersion) { + println!("v{version:?} pkg {pkg_id:04x}"); self.nonce[0] ^= (pkg_id >> 8) as u8; match version { - PackageVersion::Destiny2WitchQueen => self.nonce[1] = 0xea, + PackageVersion::Destiny2BeyondLight + | PackageVersion::Destiny2WitchQueen + | PackageVersion::Destiny2Lightfall + | PackageVersion::Destiny2 => self.nonce[1] = 0xea, PackageVersion::Destiny2Beta | PackageVersion::Destiny2PreBeyondLight => { self.nonce[1] = 0xf9 } diff --git a/src/d2_beta/impl.rs b/src/d2_beta/impl.rs index b7ec455..f7147f7 100644 --- a/src/d2_beta/impl.rs +++ b/src/d2_beta/impl.rs @@ -1,37 +1,19 @@ -use std::borrow::Cow; -use std::cell::RefCell; -use std::collections::hash_map::Entry; use std::fs::File; -use std::io::{BufReader, Read, Seek, SeekFrom}; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::io::{BufReader, SeekFrom}; + use std::sync::Arc; -use anyhow::Context; use binrw::{BinReaderExt, Endian, VecArgs}; -use nohash_hasher::IntMap; -use crate::crypto::PkgGcmState; use crate::d2_beta::structs::PackageHeader; -use crate::d2_shared::{BlockHeader, EntryHeader}; -use crate::package::{Package, ReadSeek, UEntryHeader, UHashTableEntry, BLOCK_CACHE_SIZE}; -use crate::{oodle, PackageVersion}; - -pub const BLOCK_SIZE: usize = 0x40000; +use crate::d2_shared::PackageCommonD2; +use crate::package::{Package, ReadSeek, UEntryHeader, UHashTableEntry}; +use crate::PackageVersion; // TODO(cohae): Ensure Send+Sync so packages can be multithreaded, should be enforced on `Package` as well pub struct PackageD2Beta { - gcm: RefCell, - + common: PackageCommonD2, pub header: PackageHeader, - entries: Vec, - blocks: Vec, - - reader: RefCell>, - path_base: String, - - /// Used for purging old blocks - block_counter: AtomicUsize, - block_cache: RefCell>)>>, } impl PackageD2Beta { @@ -60,71 +42,22 @@ impl PackageD2Beta { inner: (), })?; - let last_underscore_pos = path.rfind('_').unwrap(); - let path_base = path[..last_underscore_pos].to_owned(); - Ok(PackageD2Beta { - path_base, - reader: RefCell::new(Box::new(reader)), - gcm: RefCell::new(PkgGcmState::new( - header.pkg_id, + common: PackageCommonD2::new( + reader, PackageVersion::Destiny2Beta, - )), + header.pkg_id, + header.patch_id, + entries, + blocks, + path.to_string(), + )?, header, - entries, - blocks, - block_counter: AtomicUsize::default(), - block_cache: Default::default(), }) } - - fn get_block_raw(&self, block_index: usize) -> anyhow::Result> { - let bh = &self.blocks[block_index]; - let mut data = vec![0u8; bh.size as usize]; - - if self.header.patch_id == bh.patch_id { - self.reader - .borrow_mut() - .seek(SeekFrom::Start(bh.offset as u64))?; - self.reader.borrow_mut().read_exact(&mut data)?; - } else { - // TODO(cohae): Can we cache these? - let mut f = - File::open(format!("{}_{}.pkg", self.path_base, bh.patch_id)).context(format!( - "Failed to open package file {}_{}.pkg", - self.path_base, bh.patch_id - ))?; - - f.seek(SeekFrom::Start(bh.offset as u64))?; - f.read_exact(&mut data)?; - }; - - Ok(Cow::Owned(data)) - } - - /// Reads, decrypts and decompresses the specified block - fn read_block(&self, block_index: usize) -> anyhow::Result> { - let bh = self.blocks[block_index].clone(); - let mut block_data = self.get_block_raw(block_index)?.to_vec(); - - if (bh.flags & 0x2) != 0 { - self.gcm - .borrow_mut() - .decrypt_block_in_place(bh.flags, &bh.gcm_tag, &mut block_data)?; - }; - - let decompressed_data = if (bh.flags & 0x1) != 0 { - let mut buffer = vec![0u8; BLOCK_SIZE]; - let _decompressed_size = oodle::decompress_3(&block_data, &mut buffer)?; - buffer - } else { - block_data - }; - - Ok(decompressed_data) - } } +// TODO(cohae): Can we implement this on PackageCommon? impl Package for PackageD2Beta { fn endianness(&self) -> Endian { Endian::Little // TODO(cohae): Not necessarily @@ -139,11 +72,13 @@ impl Package for PackageD2Beta { } fn hashes64(&self) -> Vec { + // TODO(cohae): Fix hashtable vec![] } fn entries(&self) -> Vec { - self.entries + self.common + .entries .iter() .map(|e| UEntryHeader { reference: e.reference, @@ -157,7 +92,7 @@ impl Package for PackageD2Beta { } fn entry(&self, index: usize) -> Option { - self.entries.get(index).map(|e| UEntryHeader { + self.common.entries.get(index).map(|e| UEntryHeader { reference: e.reference, file_type: e.file_type, file_subtype: e.file_subtype, @@ -167,37 +102,7 @@ impl Package for PackageD2Beta { }) } - fn get_block(&self, block_index: usize) -> anyhow::Result>> { - let (_, b) = match self.block_cache.borrow_mut().entry(block_index) { - Entry::Occupied(o) => o.get().clone(), - Entry::Vacant(v) => { - let block = self.read_block(*v.key())?; - let b = v - .insert((self.block_counter.load(Ordering::Relaxed), Arc::new(block))) - .clone(); - - self.block_counter.store( - self.block_counter.load(Ordering::Relaxed) + 1, - Ordering::Relaxed, - ); - - b - } - }; - - while self.block_cache.borrow().len() > BLOCK_CACHE_SIZE { - let bc = self.block_cache.borrow(); - let (oldest, _) = bc - .iter() - .min_by(|(_, (at, _)), (_, (bt, _))| at.cmp(bt)) - .unwrap(); - - let oldest = *oldest; - drop(bc); - - self.block_cache.borrow_mut().remove(&oldest); - } - - Ok(b) + fn get_block(&self, index: usize) -> anyhow::Result>> { + self.common.get_block(index) } } diff --git a/src/d2_beta/structs.rs b/src/d2_beta/structs.rs index 2daa3f5..a53868a 100644 --- a/src/d2_beta/structs.rs +++ b/src/d2_beta/structs.rs @@ -1,5 +1,4 @@ - -use binrw::{BinRead}; +use binrw::BinRead; use std::fmt::Debug; use std::io::SeekFrom; diff --git a/src/d2_beyondlight/impl.rs b/src/d2_beyondlight/impl.rs index 25c2e31..7115617 100644 --- a/src/d2_beyondlight/impl.rs +++ b/src/d2_beyondlight/impl.rs @@ -1,49 +1,32 @@ -use std::borrow::Cow; -use std::cell::RefCell; -use std::collections::hash_map::Entry; use std::fs::File; -use std::io::{BufReader, Read, Seek, SeekFrom}; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::io::{BufReader, SeekFrom}; + use std::sync::Arc; -use anyhow::Context; use binrw::{BinReaderExt, Endian, VecArgs}; -use nohash_hasher::IntMap; -use crate::crypto::PkgGcmState; use crate::d2_beyondlight::structs::PackageHeader; -use crate::d2_shared::{BlockHeader, EntryHeader}; -use crate::package::{Package, ReadSeek, UEntryHeader, UHashTableEntry, BLOCK_CACHE_SIZE}; -use crate::{oodle, PackageVersion}; - -pub const BLOCK_SIZE: usize = 0x40000; +use crate::d2_shared::PackageCommonD2; +use crate::package::{Package, ReadSeek, UEntryHeader, UHashTableEntry}; +use crate::PackageVersion; // TODO(cohae): Ensure Send+Sync so packages can be multithreaded, should be enforced on `Package` as well pub struct PackageD2BeyondLight { - gcm: RefCell, - + common: PackageCommonD2, pub header: PackageHeader, - entries: Vec, - blocks: Vec, - - reader: RefCell>, - path_base: String, - - /// Used for purging old blocks - block_counter: AtomicUsize, - block_cache: RefCell>)>>, } impl PackageD2BeyondLight { - pub fn open(path: &str) -> anyhow::Result { + pub fn open(path: &str, version: PackageVersion) -> anyhow::Result { let reader = BufReader::new(File::open(path)?); - Self::from_reader(path, reader) + Self::from_reader(path, reader, version) } pub fn from_reader( path: &str, reader: R, + version: PackageVersion, ) -> anyhow::Result { let mut reader = reader; let header: PackageHeader = reader.read_le()?; @@ -60,71 +43,22 @@ impl PackageD2BeyondLight { inner: (), })?; - let last_underscore_pos = path.rfind('_').unwrap(); - let path_base = path[..last_underscore_pos].to_owned(); - Ok(PackageD2BeyondLight { - path_base, - reader: RefCell::new(Box::new(reader)), - gcm: RefCell::new(PkgGcmState::new( + common: PackageCommonD2::new( + reader, + version, header.pkg_id, - PackageVersion::Destiny2WitchQueen, - )), + header.patch_id, + entries, + blocks, + path.to_string(), + )?, header, - entries, - blocks, - block_counter: AtomicUsize::default(), - block_cache: Default::default(), }) } - - fn get_block_raw(&self, block_index: usize) -> anyhow::Result> { - let bh = &self.blocks[block_index]; - let mut data = vec![0u8; bh.size as usize]; - - if self.header.patch_id == bh.patch_id { - self.reader - .borrow_mut() - .seek(SeekFrom::Start(bh.offset as u64))?; - self.reader.borrow_mut().read_exact(&mut data)?; - } else { - // TODO(cohae): Can we cache these? - let mut f = - File::open(format!("{}_{}.pkg", self.path_base, bh.patch_id)).context(format!( - "Failed to open package file {}_{}.pkg", - self.path_base, bh.patch_id - ))?; - - f.seek(SeekFrom::Start(bh.offset as u64))?; - f.read_exact(&mut data)?; - }; - - Ok(Cow::Owned(data)) - } - - /// Reads, decrypts and decompresses the specified block - fn read_block(&self, block_index: usize) -> anyhow::Result> { - let bh = self.blocks[block_index].clone(); - let mut block_data = self.get_block_raw(block_index)?.to_vec(); - - if (bh.flags & 0x2) != 0 { - self.gcm - .borrow_mut() - .decrypt_block_in_place(bh.flags, &bh.gcm_tag, &mut block_data)?; - }; - - let decompressed_data = if (bh.flags & 0x1) != 0 { - let mut buffer = vec![0u8; BLOCK_SIZE]; - let _decompressed_size = oodle::decompress_9(&block_data, &mut buffer)?; - buffer - } else { - block_data - }; - - Ok(decompressed_data) - } } +// TODO(cohae): Can we implement this on PackageCommon? impl Package for PackageD2BeyondLight { fn endianness(&self) -> Endian { Endian::Little // TODO(cohae): Not necessarily @@ -139,11 +73,13 @@ impl Package for PackageD2BeyondLight { } fn hashes64(&self) -> Vec { + // TODO(cohae): Fix hashtable vec![] } fn entries(&self) -> Vec { - self.entries + self.common + .entries .iter() .map(|e| UEntryHeader { reference: e.reference, @@ -157,7 +93,7 @@ impl Package for PackageD2BeyondLight { } fn entry(&self, index: usize) -> Option { - self.entries.get(index).map(|e| UEntryHeader { + self.common.entries.get(index).map(|e| UEntryHeader { reference: e.reference, file_type: e.file_type, file_subtype: e.file_subtype, @@ -167,37 +103,7 @@ impl Package for PackageD2BeyondLight { }) } - fn get_block(&self, block_index: usize) -> anyhow::Result>> { - let (_, b) = match self.block_cache.borrow_mut().entry(block_index) { - Entry::Occupied(o) => o.get().clone(), - Entry::Vacant(v) => { - let block = self.read_block(*v.key())?; - let b = v - .insert((self.block_counter.load(Ordering::Relaxed), Arc::new(block))) - .clone(); - - self.block_counter.store( - self.block_counter.load(Ordering::Relaxed) + 1, - Ordering::Relaxed, - ); - - b - } - }; - - while self.block_cache.borrow().len() > BLOCK_CACHE_SIZE { - let bc = self.block_cache.borrow(); - let (oldest, _) = bc - .iter() - .min_by(|(_, (at, _)), (_, (bt, _))| at.cmp(bt)) - .unwrap(); - - let oldest = *oldest; - drop(bc); - - self.block_cache.borrow_mut().remove(&oldest); - } - - Ok(b) + fn get_block(&self, index: usize) -> anyhow::Result>> { + self.common.get_block(index) } } diff --git a/src/d2_beyondlight/structs.rs b/src/d2_beyondlight/structs.rs index 1879fb5..dd2fa57 100644 --- a/src/d2_beyondlight/structs.rs +++ b/src/d2_beyondlight/structs.rs @@ -1,5 +1,4 @@ - -use binrw::{BinRead}; +use binrw::BinRead; use std::fmt::Debug; use std::io::SeekFrom; diff --git a/src/d2_prebl/impl.rs b/src/d2_prebl/impl.rs index 7455bdf..88834d6 100644 --- a/src/d2_prebl/impl.rs +++ b/src/d2_prebl/impl.rs @@ -1,36 +1,19 @@ -use crate::crypto::PkgGcmState; -use crate::d2_prebl::structs::PackageHeader; -use crate::d2_shared::{BlockHeader, EntryHeader, HashTableEntry}; -use crate::package::{Package, ReadSeek, UEntryHeader, UHashTableEntry, BLOCK_CACHE_SIZE}; -use crate::{oodle, PackageVersion}; -use anyhow::Context; -use binrw::{BinReaderExt, Endian, VecArgs}; -use nohash_hasher::IntMap; -use std::borrow::Cow; -use std::cell::RefCell; -use std::collections::hash_map::Entry; use std::fs::File; -use std::io::{BufReader, Read, Seek, SeekFrom}; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::io::{BufReader, SeekFrom}; + use std::sync::Arc; -pub const BLOCK_SIZE: usize = 0x40000; +use binrw::{BinReaderExt, Endian, VecArgs}; + +use crate::d2_prebl::structs::PackageHeader; +use crate::d2_shared::PackageCommonD2; +use crate::package::{Package, ReadSeek, UEntryHeader, UHashTableEntry}; +use crate::PackageVersion; // TODO(cohae): Ensure Send+Sync so packages can be multithreaded, should be enforced on `Package` as well pub struct PackageD2PreBL { - gcm: RefCell, - + common: PackageCommonD2, pub header: PackageHeader, - entries: Vec, - blocks: Vec, - pub hashes: Vec, - - reader: RefCell>, - path_base: String, - - /// Used for purging old blocks - block_counter: AtomicUsize, - block_cache: RefCell>)>>, } impl PackageD2PreBL { @@ -64,85 +47,22 @@ impl PackageD2PreBL { inner: (), })?; - let hashes: Vec = if header.unkf0_table_offset != 0 { - reader.seek(SeekFrom::Start((header.unkf0_table_offset + 48) as _))?; - let h64_table_size: u64 = reader.read_le()?; - let real_h64_table_offset: u64 = reader.read_le()?; - reader.seek(SeekFrom::Current(-8 + real_h64_table_offset as i64 + 16))?; - reader.read_le_args(VecArgs { - count: h64_table_size as _, - inner: (), - })? - } else { - vec![] - }; - - let last_underscore_pos = path.rfind('_').unwrap(); - let path_base = path[..last_underscore_pos].to_owned(); - Ok(PackageD2PreBL { - path_base, - reader: RefCell::new(Box::new(reader)), - gcm: RefCell::new(PkgGcmState::new( - header.pkg_id, + common: PackageCommonD2::new( + reader, PackageVersion::Destiny2PreBeyondLight, - )), + header.pkg_id, + header.patch_id, + entries, + blocks, + path.to_string(), + )?, header, - entries, - blocks, - hashes, - block_counter: AtomicUsize::default(), - block_cache: Default::default(), }) } - - fn get_block_raw(&self, block_index: usize) -> anyhow::Result> { - let bh = &self.blocks[block_index]; - let mut data = vec![0u8; bh.size as usize]; - - if self.header.patch_id == bh.patch_id { - self.reader - .borrow_mut() - .seek(SeekFrom::Start(bh.offset as u64))?; - self.reader.borrow_mut().read_exact(&mut data)?; - } else { - // TODO(cohae): Can we cache these? - let mut f = - File::open(format!("{}_{}.pkg", self.path_base, bh.patch_id)).context(format!( - "Failed to open package file {}_{}.pkg", - self.path_base, bh.patch_id - ))?; - - f.seek(SeekFrom::Start(bh.offset as u64))?; - f.read_exact(&mut data)?; - }; - - Ok(Cow::Owned(data)) - } - - /// Reads, decrypts and decompresses the specified block - fn read_block(&self, block_index: usize) -> anyhow::Result> { - let bh = self.blocks[block_index].clone(); - let mut block_data = self.get_block_raw(block_index)?.to_vec(); - - if (bh.flags & 0x2) != 0 { - self.gcm - .borrow_mut() - .decrypt_block_in_place(bh.flags, &bh.gcm_tag, &mut block_data)?; - }; - - let decompressed_data = if (bh.flags & 0x1) != 0 { - let mut buffer = vec![0u8; BLOCK_SIZE]; - let _decompressed_size = oodle::decompress_3(&block_data, &mut buffer)?; - buffer - } else { - block_data - }; - - Ok(decompressed_data) - } } +// TODO(cohae): Can we implement this on PackageCommon? impl Package for PackageD2PreBL { fn endianness(&self) -> Endian { Endian::Little // TODO(cohae): Not necessarily @@ -157,18 +77,13 @@ impl Package for PackageD2PreBL { } fn hashes64(&self) -> Vec { - self.hashes - .iter() - .map(|h| UHashTableEntry { - hash32: h.hash32, - hash64: h.hash64, - reference: h.reference, - }) - .collect() + // TODO(cohae): Fix hashtable + vec![] } fn entries(&self) -> Vec { - self.entries + self.common + .entries .iter() .map(|e| UEntryHeader { reference: e.reference, @@ -182,7 +97,7 @@ impl Package for PackageD2PreBL { } fn entry(&self, index: usize) -> Option { - self.entries.get(index).map(|e| UEntryHeader { + self.common.entries.get(index).map(|e| UEntryHeader { reference: e.reference, file_type: e.file_type, file_subtype: e.file_subtype, @@ -192,37 +107,7 @@ impl Package for PackageD2PreBL { }) } - fn get_block(&self, block_index: usize) -> anyhow::Result>> { - let (_, b) = match self.block_cache.borrow_mut().entry(block_index) { - Entry::Occupied(o) => o.get().clone(), - Entry::Vacant(v) => { - let block = self.read_block(*v.key())?; - let b = v - .insert((self.block_counter.load(Ordering::Relaxed), Arc::new(block))) - .clone(); - - self.block_counter.store( - self.block_counter.load(Ordering::Relaxed) + 1, - Ordering::Relaxed, - ); - - b - } - }; - - while self.block_cache.borrow().len() > BLOCK_CACHE_SIZE { - let bc = self.block_cache.borrow(); - let (oldest, _) = bc - .iter() - .min_by(|(_, (at, _)), (_, (bt, _))| at.cmp(bt)) - .unwrap(); - - let oldest = *oldest; - drop(bc); - - self.block_cache.borrow_mut().remove(&oldest); - } - - Ok(b) + fn get_block(&self, index: usize) -> anyhow::Result>> { + self.common.get_block(index) } } diff --git a/src/d2_prebl/structs.rs b/src/d2_prebl/structs.rs index 968abe7..d5ef090 100644 --- a/src/d2_prebl/structs.rs +++ b/src/d2_prebl/structs.rs @@ -1,5 +1,4 @@ - -use binrw::{BinRead}; +use binrw::BinRead; use std::fmt::Debug; use std::io::SeekFrom; diff --git a/src/d2_shared.rs b/src/d2_shared.rs index 3e69c33..ed9f631 100644 --- a/src/d2_shared.rs +++ b/src/d2_shared.rs @@ -1,5 +1,16 @@ -use crate::TagHash; +use crate::crypto::PkgGcmState; +use crate::package::{ReadSeek, BLOCK_CACHE_SIZE}; +use crate::{oodle, PackageVersion, TagHash}; +use anyhow::Context; use binrw::BinRead; +use nohash_hasher::IntMap; +use std::borrow::Cow; +use std::cell::RefCell; +use std::collections::hash_map::Entry; +use std::fs::File; +use std::io::{Read, Seek, SeekFrom}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; #[derive(BinRead, Debug, Clone)] pub struct EntryHeader { @@ -40,3 +51,141 @@ pub struct HashTableEntry { pub hash32: TagHash, pub reference: TagHash, } + +pub const BLOCK_SIZE: usize = 0x40000; + +pub struct PackageCommonD2 { + pub(crate) version: PackageVersion, + pub(crate) pkg_id: u16, + pub(crate) patch_id: u16, + + pub(crate) gcm: RefCell, + pub(crate) entries: Vec, + pub(crate) blocks: Vec, + pub(crate) hashes: Vec, + + pub(crate) reader: RefCell>, + pub(crate) path_base: String, + + /// Used for purging old blocks + pub(crate) block_counter: AtomicUsize, + pub(crate) block_cache: RefCell>)>>, +} + +impl PackageCommonD2 { + pub fn new( + reader: R, + version: PackageVersion, + pkg_id: u16, + patch_id: u16, + entries: Vec, + blocks: Vec, + path: String, + ) -> anyhow::Result { + let last_underscore_pos = path.rfind('_').unwrap(); + let path_base = path[..last_underscore_pos].to_owned(); + + Ok(PackageCommonD2 { + version, + pkg_id, + patch_id, + gcm: RefCell::new(PkgGcmState::new(pkg_id, version)), + entries, + blocks, + hashes: vec![], // TODO(cohae): fix hash tables + reader: RefCell::new(Box::new(reader)), + path_base, + block_counter: AtomicUsize::default(), + block_cache: Default::default(), + }) + } + + fn get_block_raw(&self, block_index: usize) -> anyhow::Result> { + let bh = &self.blocks[block_index]; + let mut data = vec![0u8; bh.size as usize]; + + if self.patch_id == bh.patch_id { + self.reader + .borrow_mut() + .seek(SeekFrom::Start(bh.offset as u64))?; + self.reader.borrow_mut().read_exact(&mut data)?; + } else { + let mut f = + File::open(format!("{}_{}.pkg", self.path_base, bh.patch_id)).context(format!( + "Failed to open package file {}_{}.pkg", + self.path_base, bh.patch_id + ))?; + + f.seek(SeekFrom::Start(bh.offset as u64))?; + f.read_exact(&mut data)?; + }; + + Ok(Cow::Owned(data)) + } + + /// Reads, decrypts and decompresses the specified block + fn read_block(&self, block_index: usize) -> anyhow::Result> { + let bh = self.blocks[block_index].clone(); + let mut block_data = self.get_block_raw(block_index)?.to_vec(); + + if (bh.flags & 0x2) != 0 { + self.gcm + .borrow_mut() + .decrypt_block_in_place(bh.flags, &bh.gcm_tag, &mut block_data)?; + }; + + let decompressed_data = if (bh.flags & 0x1) != 0 { + let mut buffer = vec![0u8; BLOCK_SIZE]; + let _decompressed_size = match self.version { + PackageVersion::DestinyLegacy => oodle::decompress_3, + PackageVersion::Destiny => oodle::decompress_3, + PackageVersion::Destiny2Beta => oodle::decompress_3, + PackageVersion::Destiny2PreBeyondLight => oodle::decompress_3, + PackageVersion::Destiny2BeyondLight => oodle::decompress_9, + PackageVersion::Destiny2WitchQueen => oodle::decompress_9, + PackageVersion::Destiny2Lightfall => oodle::decompress_9, + PackageVersion::Destiny2 => oodle::decompress_9, + }(&block_data, &mut buffer)?; + + buffer + } else { + block_data + }; + + Ok(decompressed_data) + } + + pub fn get_block(&self, block_index: usize) -> anyhow::Result>> { + let (_, b) = match self.block_cache.borrow_mut().entry(block_index) { + Entry::Occupied(o) => o.get().clone(), + Entry::Vacant(v) => { + let block = self.read_block(*v.key())?; + let b = v + .insert((self.block_counter.load(Ordering::Relaxed), Arc::new(block))) + .clone(); + + self.block_counter.store( + self.block_counter.load(Ordering::Relaxed) + 1, + Ordering::Relaxed, + ); + + b + } + }; + + while self.block_cache.borrow().len() > BLOCK_CACHE_SIZE { + let bc = self.block_cache.borrow(); + let (oldest, _) = bc + .iter() + .min_by(|(_, (at, _)), (_, (bt, _))| at.cmp(bt)) + .unwrap(); + + let oldest = *oldest; + drop(bc); + + self.block_cache.borrow_mut().remove(&oldest); + } + + Ok(b) + } +} diff --git a/src/package.rs b/src/package.rs index aa33154..b361e31 100644 --- a/src/package.rs +++ b/src/package.rs @@ -68,14 +68,14 @@ impl PackageVersion { Ok(match self { PackageVersion::DestinyLegacy => Arc::new(PackageD1Legacy::open(path)?), PackageVersion::Destiny => { - anyhow::bail!("The latest version of Destiny is not supported yet") + anyhow::bail!("The latest version of Destiny 1 is not supported yet") } PackageVersion::Destiny2Beta => Arc::new(PackageD2Beta::open(path)?), PackageVersion::Destiny2PreBeyondLight => Arc::new(PackageD2PreBL::open(path)?), PackageVersion::Destiny2BeyondLight | PackageVersion::Destiny2WitchQueen | PackageVersion::Destiny2Lightfall - | PackageVersion::Destiny2 => Arc::new(PackageD2BeyondLight::open(path)?), + | PackageVersion::Destiny2 => Arc::new(PackageD2BeyondLight::open(path, *self)?), }) } }