use std::{
fs::File,
io::{BufReader, Seek, SeekFrom},
sync::Arc,
};
use anyhow::Context;
use binrw::{BinReaderExt, Endian, VecArgs};
use crate::{
d2_beyondlight::structs::PackageHeader,
d2_shared::{
BlockFlags, CommonPackageData, HashTableEntry, PackageCommonD2, PackageNamedTagEntry,
},
package::{
Package, PackageLanguage, PackagePlatform, ReadSeek, Redaction, UEntryHeader,
UHashTableEntry,
},
DestinyVersion,
};
pub struct PackageD2BeyondLight {
common: PackageCommonD2,
pub header: PackageHeader,
pub named_tags: Vec<PackageNamedTagEntry>,
}
unsafe impl Send for PackageD2BeyondLight {}
unsafe impl Sync for PackageD2BeyondLight {}
impl PackageD2BeyondLight {
pub fn open(path: &str, version: DestinyVersion) -> anyhow::Result<PackageD2BeyondLight> {
let reader = File::open(path).with_context(|| format!("Cannot find file '{path}'"))?;
Self::from_reader(path, reader, version)
}
pub fn from_reader<R: ReadSeek + 'static>(
path: &str,
reader: R,
version: DestinyVersion,
) -> anyhow::Result<PackageD2BeyondLight> {
let mut reader = BufReader::new(reader);
let header: PackageHeader = reader.read_le()?;
reader.seek(SeekFrom::Start(header.entry_table_offset as _))?;
let entries = reader.read_le_args(VecArgs {
count: header.entry_table_size as _,
inner: (),
})?;
reader.seek(SeekFrom::Start(header.block_table_offset as _))?;
let blocks = reader.read_le_args(VecArgs {
count: header.block_table_size as _,
inner: (),
})?;
reader.seek(SeekFrom::Start(header.named_tag_table_offset as u64 + 0x30))?;
let named_tags = reader.read_le_args(VecArgs {
count: header.named_tag_table_size as _,
inner: (),
})?;
let wide_hashes: Vec<HashTableEntry> = if header.h64_table_size != 0 {
reader.seek(SeekFrom::Start((header.h64_table_offset + 0x10) as _))?;
let offset = reader.read_le::<i64>()?;
reader.seek(SeekFrom::Start(
(header.h64_table_offset as i64 + 0x20 + offset) as _,
))?;
reader.read_le_args(VecArgs {
count: header.h64_table_size as _,
inner: (),
})?
} else {
vec![]
};
Ok(PackageD2BeyondLight {
common: PackageCommonD2::new(
reader.into_inner(),
version,
path.to_string(),
CommonPackageData {
pkg_id: header.pkg_id,
patch_id: header.patch_id,
group_id: header.group_id,
entries,
blocks,
wide_hashes,
language: header.language,
},
)?,
header,
named_tags,
})
}
}
impl Package for PackageD2BeyondLight {
fn endianness(&self) -> Endian {
Endian::Little }
fn pkg_id(&self) -> u16 {
self.common.pkg_id
}
fn patch_id(&self) -> u16 {
self.common.patch_id
}
fn language(&self) -> PackageLanguage {
self.common.language
}
fn platform(&self) -> PackagePlatform {
self.header.platform
}
fn hash64_table(&self) -> Vec<UHashTableEntry> {
self.common
.wide_hashes
.iter()
.map(|h| UHashTableEntry {
hash64: h.hash64,
hash32: h.hash32,
reference: h.reference,
})
.collect()
}
fn named_tags(&self) -> Vec<PackageNamedTagEntry> {
self.named_tags.clone()
}
fn entries(&self) -> &[UEntryHeader] {
&self.common.entries_unified
}
fn entry(&self, index: usize) -> Option<UEntryHeader> {
self.common.entries_unified.get(index).cloned()
}
fn get_block(&self, index: usize) -> anyhow::Result<Arc<Vec<u8>>> {
self.common.get_block(index)
}
fn redaction_level(&self) -> Redaction {
let num_redacted = self
.common
.blocks
.iter()
.filter(|b| b.flags.contains(BlockFlags::REDACTED))
.count();
if num_redacted == 0 {
Redaction::None
} else if num_redacted == self.common.blocks.len() {
Redaction::Full
} else {
Redaction::Partial
}
}
}