use std::{
fs::File,
io::{BufReader, Read, Seek, SeekFrom},
sync::Arc,
};
use anyhow::Context;
use binrw::{BinReaderExt, Endian, VecArgs};
use parking_lot::RwLock;
use crate::{
block_cache::BlockCache,
d1_internal_alpha::structs::{BlockHeader, EntryHeader, PackageHeader},
d1_roi::structs::NamedTagEntryD1,
oodle,
package::{Package, PackageLanguage, PackagePlatform, ReadSeek, UEntryHeader, UHashTableEntry},
PackageNamedTagEntry,
};
pub const BLOCK_SIZE: usize = 0x40000;
pub struct PackageD1InternalAlpha {
pub header: PackageHeader,
unified_entries: Vec<UEntryHeader>,
blocks: Vec<BlockHeader>,
named_tags: Vec<PackageNamedTagEntry>,
reader: RwLock<Box<dyn ReadSeek>>,
block_cache: BlockCache,
}
unsafe impl Send for PackageD1InternalAlpha {}
unsafe impl Sync for PackageD1InternalAlpha {}
impl PackageD1InternalAlpha {
pub fn open(path: &str) -> anyhow::Result<PackageD1InternalAlpha> {
let reader = File::open(path)?;
Self::from_reader(path, reader)
}
pub fn from_reader<R: ReadSeek + 'static>(
_path: &str,
reader: R,
) -> anyhow::Result<PackageD1InternalAlpha> {
let mut reader = BufReader::new(reader);
let header: PackageHeader = reader.read_be()?;
reader.seek(SeekFrom::Start(header.entry_table_offset as u64))?;
let entries: Vec<EntryHeader> = reader.read_be_args(
VecArgs::builder()
.count(header.entry_table_size as usize)
.finalize(),
)?;
reader.seek(SeekFrom::Start(header.block_table_offset as u64))?;
let blocks: Vec<BlockHeader> = reader.read_be_args(
VecArgs::builder()
.count(header.block_table_size as usize)
.finalize(),
)?;
reader.seek(SeekFrom::Start(header.named_tag_table_offset as u64))?;
let named_tags: Vec<NamedTagEntryD1> = reader.read_be_args(
VecArgs::builder()
.count(header.named_tag_table_size as usize)
.finalize(),
)?;
let unified_entries = entries
.iter()
.map(|e| UEntryHeader {
reference: e.reference,
file_type: e.file_type,
file_subtype: e.file_subtype,
starting_block: e.starting_block,
starting_block_offset: e.starting_block_offset,
file_size: e.file_size,
})
.collect();
Ok(PackageD1InternalAlpha {
reader: RwLock::new(Box::new(reader.into_inner())),
header,
unified_entries,
blocks,
block_cache: BlockCache::new(),
named_tags: named_tags
.into_iter()
.map(|n: NamedTagEntryD1| PackageNamedTagEntry {
hash: n.hash,
class_hash: n.class_hash,
name: String::from_utf8_lossy(&n.name)
.trim_end_matches('\0')
.to_owned(),
})
.collect(),
})
}
fn get_block_raw(&self, block_index: usize) -> anyhow::Result<Vec<u8>> {
let bh = &self.blocks[block_index];
let mut data = vec![0u8; bh.size as usize];
self.reader
.write()
.seek(SeekFrom::Start(bh.offset as u64))?;
self.reader.write().read_exact(&mut data)?;
Ok(data)
}
fn read_block(&self, block_index: usize) -> anyhow::Result<Vec<u8>> {
let bh = &self
.blocks
.get(block_index)
.context("Block index out of bounds")?;
let block_data = self.get_block_raw(block_index)?.to_vec();
Ok(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
})
}
}
impl Package for PackageD1InternalAlpha {
fn endianness(&self) -> Endian {
Endian::Big
}
fn pkg_id(&self) -> u16 {
self.header.pkg_id
}
fn patch_id(&self) -> u16 {
0
}
fn language(&self) -> PackageLanguage {
self.header.language
}
fn platform(&self) -> PackagePlatform {
self.header.platform
}
fn hash64_table(&self) -> Vec<UHashTableEntry> {
vec![]
}
fn named_tags(&self) -> Vec<PackageNamedTagEntry> {
self.named_tags.clone()
}
fn entries(&self) -> &[UEntryHeader] {
&self.unified_entries
}
fn entry(&self, index: usize) -> Option<UEntryHeader> {
self.unified_entries.get(index).cloned()
}
fn get_block(&self, block_index: usize) -> anyhow::Result<Arc<Vec<u8>>> {
self.block_cache.get(block_index, |i| self.read_block(i))
}
}