tiger_pkg/
d2_shared.rs

1use std::{
2    borrow::Cow,
3    collections::hash_map::Entry,
4    fs::File,
5    io::{Read, Seek, SeekFrom},
6    sync::Arc,
7};
8
9use anyhow::Context;
10use binrw::{BinRead, BinReaderExt, NullString};
11use parking_lot::RwLock;
12use rustc_hash::FxHashMap;
13
14use crate::{
15    block_cache::BlockCache,
16    crypto::PkgGcmState,
17    oodle,
18    package::{PackageLanguage, ReadSeek, UEntryHeader},
19    DestinyVersion, GameVersion, TagHash,
20};
21
22#[derive(BinRead, Debug, Clone)]
23pub struct EntryHeader {
24    pub reference: u32,
25
26    _type_info: u32,
27
28    #[br(calc = (_type_info >> 9) as u8 & 0x7f)]
29    pub file_type: u8,
30    #[br(calc = (_type_info >> 6) as u8 & 0x7)]
31    pub file_subtype: u8,
32
33    _block_info: u64,
34
35    #[br(calc = _block_info as u32 & 0x3fff)]
36    pub starting_block: u32,
37
38    #[br(calc = ((_block_info >> 14) as u32 & 0x3FFF) << 4)]
39    pub starting_block_offset: u32,
40
41    #[br(calc = (_block_info >> 28) as u32)]
42    pub file_size: u32,
43}
44
45#[derive(BinRead, Debug, Clone)]
46pub struct BlockHeader {
47    pub offset: u32,
48    pub size: u32,
49    pub patch_id: u16,
50    pub flags: u16,
51    pub _hash: [u8; 20],
52    pub gcm_tag: [u8; 16],
53}
54
55#[derive(BinRead, Debug, Clone)]
56pub struct HashTableEntry {
57    pub hash64: u64,
58    pub hash32: TagHash,
59    pub reference: TagHash,
60}
61
62pub const BLOCK_SIZE: usize = 0x40000;
63
64pub struct CommonPackageData {
65    pub pkg_id: u16,
66    pub patch_id: u16,
67    pub group_id: u64,
68    pub entries: Vec<EntryHeader>,
69    pub blocks: Vec<BlockHeader>,
70    pub wide_hashes: Vec<HashTableEntry>,
71    pub language: PackageLanguage,
72}
73
74pub struct PackageCommonD2 {
75    pub(crate) version: DestinyVersion,
76    pub(crate) pkg_id: u16,
77    pub(crate) patch_id: u16,
78    pub(crate) language: PackageLanguage,
79
80    pub(crate) gcm: RwLock<PkgGcmState>,
81    pub(crate) _entries: Vec<EntryHeader>,
82    pub(crate) entries_unified: Arc<[UEntryHeader]>,
83    pub(crate) blocks: Vec<BlockHeader>,
84    pub(crate) wide_hashes: Vec<HashTableEntry>,
85
86    pub(crate) reader: RwLock<Box<dyn ReadSeek>>,
87    pub(crate) path_base: String,
88
89    block_cache: BlockCache,
90    pub(crate) file_handles: RwLock<FxHashMap<usize, File>>,
91}
92
93impl PackageCommonD2 {
94    pub fn new<R: ReadSeek + 'static>(
95        reader: R,
96        version: DestinyVersion,
97        path: String,
98        data: CommonPackageData,
99    ) -> anyhow::Result<PackageCommonD2> {
100        let CommonPackageData {
101            pkg_id,
102            patch_id,
103            group_id,
104            entries,
105            blocks,
106            wide_hashes,
107            language,
108        } = data;
109
110        let last_underscore_pos = path.rfind('_').unwrap();
111        let path_base = path[..last_underscore_pos].to_owned();
112
113        let entries_unified: Vec<UEntryHeader> = entries
114            .iter()
115            .map(|e| UEntryHeader {
116                reference: e.reference,
117                file_type: e.file_type,
118                file_subtype: e.file_subtype,
119                starting_block: e.starting_block,
120                starting_block_offset: e.starting_block_offset,
121                file_size: e.file_size,
122            })
123            .collect();
124
125        Ok(PackageCommonD2 {
126            version,
127            pkg_id,
128            patch_id,
129            language,
130            gcm: RwLock::new(PkgGcmState::new(
131                pkg_id,
132                GameVersion::Destiny(version),
133                group_id,
134            )),
135            _entries: entries,
136            entries_unified: entries_unified.into(),
137            blocks,
138            wide_hashes,
139            reader: RwLock::new(Box::new(reader)),
140            path_base,
141            block_cache: BlockCache::new(),
142            file_handles: Default::default(),
143        })
144    }
145
146    fn get_block_raw(&self, block_index: usize) -> anyhow::Result<Cow<[u8]>> {
147        let _span = tracing::debug_span!("PackageCommonD2::get_block_raw", block_index).entered();
148
149        let bh = &self.blocks[block_index];
150        let mut data = vec![0u8; bh.size as usize];
151
152        if self.patch_id == bh.patch_id {
153            self.reader
154                .write()
155                .seek(SeekFrom::Start(bh.offset as u64))?;
156            self.reader.write().read_exact(&mut data)?;
157        } else {
158            match self.file_handles.write().entry(bh.patch_id as _) {
159                Entry::Occupied(mut f) => {
160                    let f = f.get_mut();
161                    f.seek(SeekFrom::Start(bh.offset as u64))?;
162                    f.read_exact(&mut data)?;
163                }
164                Entry::Vacant(e) => {
165                    let f = File::open(format!("{}_{}.pkg", self.path_base, bh.patch_id))
166                        .with_context(|| {
167                            format!(
168                                "Failed to open package file {}_{}.pkg",
169                                self.path_base, bh.patch_id
170                            )
171                        })?;
172
173                    let f = e.insert(f);
174                    f.seek(SeekFrom::Start(bh.offset as u64))?;
175                    f.read_exact(&mut data)?;
176                }
177            };
178        };
179
180        Ok(Cow::Owned(data))
181    }
182
183    /// Reads, decrypts and decompresses the specified block
184    fn read_block(&self, block_index: usize) -> anyhow::Result<Vec<u8>> {
185        let _span = tracing::debug_span!("PackageCommonD2::read_block", block_index).entered();
186
187        let bh = self.blocks[block_index].clone();
188
189        let mut block_data = self.get_block_raw(block_index)?.to_vec();
190
191        if (bh.flags & 0x2) != 0 {
192            let _espan =
193                tracing::debug_span!("PackageCommonD2::get_block_raw decrypt", block_index)
194                    .entered();
195            self.gcm
196                .write()
197                .decrypt_block_in_place(bh.flags, &bh.gcm_tag, &mut block_data)?;
198        };
199
200        let decompressed_data = if (bh.flags & 0x1) != 0 {
201            let _dspan =
202                tracing::debug_span!("PackageCommonD2::get_block_raw decompress", block_index)
203                    .entered();
204
205            let mut buffer = vec![0u8; BLOCK_SIZE];
206            let _decompressed_size = match self.version {
207                // Destiny 1
208                DestinyVersion::DestinyInternalAlpha
209                | DestinyVersion::DestinyFirstLookAlpha
210                | DestinyVersion::DestinyTheTakenKing
211                | DestinyVersion::DestinyRiseOfIron => oodle::decompress_3,
212
213                // Destiny 2 (Red War - Beyond Light)
214                DestinyVersion::Destiny2Beta
215                | DestinyVersion::Destiny2Forsaken
216                | DestinyVersion::Destiny2Shadowkeep => oodle::decompress_3,
217
218                // Destiny 2 (Beyond Light - Latest)
219                DestinyVersion::Destiny2BeyondLight
220                | DestinyVersion::Destiny2WitchQueen
221                | DestinyVersion::Destiny2Lightfall
222                | DestinyVersion::Destiny2TheFinalShape
223                | DestinyVersion::Destiny2TheEdgeOfFate => oodle::decompress_9,
224            }(&block_data, &mut buffer)?;
225
226            buffer
227        } else {
228            block_data
229        };
230
231        Ok(decompressed_data)
232    }
233
234    pub fn get_block(&self, block_index: usize) -> anyhow::Result<Arc<Vec<u8>>> {
235        let _span = tracing::debug_span!("PackageCommonD2::get_block", block_index).entered();
236        self.block_cache.get(block_index, |i| self.read_block(i))
237    }
238}
239
240#[derive(Debug, Clone, bincode::Decode, bincode::Encode)]
241pub struct PackageNamedTagEntry {
242    pub hash: TagHash,
243    pub class_hash: u32,
244    pub name: String,
245}
246
247impl BinRead for PackageNamedTagEntry {
248    type Args<'a> = ();
249
250    fn read_options<R: Read + Seek>(
251        reader: &mut R,
252        endian: binrw::Endian,
253        _args: Self::Args<'_>,
254    ) -> binrw::BinResult<Self> {
255        let hash = reader.read_type(endian)?;
256        let class_hash = reader.read_type(endian)?;
257
258        let name_offset: u64 = reader.read_type(endian)?;
259        let pos_save = reader.stream_position()?;
260
261        reader.seek(SeekFrom::Start(pos_save - 8 + name_offset))?;
262        let name_cstring: NullString = reader.read_type(endian)?;
263        reader.seek(SeekFrom::Start(pos_save))?;
264
265        Ok(Self {
266            hash,
267            class_hash,
268            name: name_cstring.to_string(),
269        })
270    }
271}