use std::collections::HashMap;
use std::io::Seek;
use std::io::SeekFrom;
use std::io::Write;
use std::path::Path;
use std::ptr::copy_nonoverlapping;
use log::info;
use crate::lol::hash::Xxh64;
use crate::lol::wad::entry::EntryData;
use crate::lol::io::bytes::Bytes;
use crate::lol::wad::entry::EntryLoc;
use crate::lol::wad::entry::EntryType;
use crate::lol::wad::toc_type::EntryV3_4;
use crate::lol::wad::toc_type::HeaderV3;
use crate::lol::wad::toc_type::Signature;
use crate::lol::wad::toc_type::Toc;
use crate::lol::wad::toc_type::Version;
use crate::lol_throw_if;
use crate::lol_trace_func;
#[derive(Debug, Clone, Default)]
pub struct Archive{
pub entries: HashMap<u64, EntryData>
}
impl Archive {
pub fn new() ->Self {
let entries = HashMap::<u64, EntryData>::new();
Self { entries }
}
pub fn read_from_toc(src: &Bytes, toc: Toc) -> Archive {
lol_trace_func!(read_from_toc, lol_trace_var!(toc));
let mut descriptors_by_checksum: HashMap<u64, EntryData> = HashMap::new();
descriptors_by_checksum.reserve(toc.entries.len());
let mut archive: Archive = Archive::default();
for entry in toc.entries {
if entry.loc.checksum != 0 {
if let Some(old) = descriptors_by_checksum.get(&entry.loc.checksum) {
if &entry.name != descriptors_by_checksum.iter().last().unwrap().0 {
archive.entries.insert(entry.name.clone(), old.clone());
continue;
}
}
}
let data = EntryData::from_loc(src, entry.loc);
archive.entries.insert(entry.name.clone(), data.clone());
if entry.loc.checksum !=0 {
descriptors_by_checksum.insert(entry.loc.checksum, data);
}
}
archive
}
pub fn read_from_file(path: &Path) -> Archive {
let trace =lol_trace_func!(read_from_file, lol_trace_var!(path));
let mut src = Bytes::from_file(path);
let mut toc = Toc::default();
let toc_error = toc.read(&mut src);
lol_throw_if!(!toc_error.is_null(), trace);
Archive::read_from_toc(&mut src, toc)
}
pub fn add_from_directory(&mut self, path: &Path, dir: &Path) -> Result<(), std::io::Error> {
let name = Xxh64::from_path(path.strip_prefix(dir).unwrap());
let data = EntryData::from_file(path);
self.entries.insert(name.0, data);
Ok(())
}
pub fn write_file(&self, path: &Path) -> Result<(), std::io::Error> {
info!("开始写入WAD文件: {:?}, 条目数量: {}", path, self.entries.len());
let mut file = std::fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(path)?;
let header_size = size_of::<HeaderV3>();
let toc_size = size_of::<EntryV3_4>() * self.entries.len();
let data_start_offset = header_size + toc_size;
file.write_all(&vec![0u8; header_size])?;
file.write_all(&vec![0u8; toc_size])?;
let mut toc_entries = Vec::<EntryV3_4>::new();
toc_entries.reserve(self.entries.len());
let mut entries_data: Vec<(u64, EntryData)> = self.entries.iter()
.map(|(k, v)| (*k, v.clone()))
.collect();
entries_data.sort_by(|a, b| a.0.cmp(&b.0));
let mut loc_by_checksum = HashMap::<u64, EntryLoc>::new();
let mut current_data_offset = data_start_offset;
for (_chunk_id, &(name_hash, ref entry_data)) in entries_data.iter().enumerate() {
let mut entry_data = entry_data.clone();
let _extension = entry_data.extension();
entry_data.into_optimal()?;
let (compressed_data, compression_type, subchunk_count, subchunk_index, decompressed_size, _) =
entry_data.compressed_bytes();
let checksum = entry_data.checksum();
let mut is_duplicate = false;
let mut data_offset = current_data_offset;
if let Some(existing_loc) = loc_by_checksum.get(&checksum) {
if existing_loc.size == compressed_data.len() as u64 &&
existing_loc.size_decompressed == decompressed_size as u64 {
is_duplicate = true;
data_offset = existing_loc.offset as usize;
info!("发现重复数据: 哈希={:016x}, 校验和={:016x}", name_hash, checksum);
}
}
if !is_duplicate {
file.seek(SeekFrom::Start(current_data_offset as u64))?;
file.write_all(&compressed_data)?;
let loc = EntryLoc {
offset: current_data_offset as u64,
size: compressed_data.len() as u64,
size_decompressed: decompressed_size as u64,
r#type: compression_type,
subchunk_count,
subchunk_index: subchunk_index as u32,
checksum,
};
loc_by_checksum.insert(checksum, loc);
current_data_offset += compressed_data.len();
info!("写入数据: 哈希={:016x}, 类型={:?}, 原始大小={}, 压缩大小={}",
name_hash, compression_type, decompressed_size, compressed_data.len());
}
toc_entries.push(EntryV3_4 {
name: name_hash,
offset: data_offset as u32,
size: compressed_data.len() as u32,
size_decompressed: decompressed_size as u32,
subchunk_count: reconstruct_subchunk_count(subchunk_count, compression_type, subchunk_count),
subchunk_index: subchunk_index as u16,
checksum,
is_duplicate,
});
}
toc_entries.sort_by(|a, b| {
let a_name = a.name;
let b_name = b.name;
a_name.cmp(&b_name)
});
file.seek(SeekFrom::Start(header_size as u64))?;
let toc_bytes = unsafe {
std::slice::from_raw_parts(
toc_entries.as_ptr() as *const u8,
toc_entries.len() * size_of::<EntryV3_4>()
)
};
file.write_all(toc_bytes)?;
let mut header = HeaderV3::default();
header.version = Version::latest();
header.signature = Signature::default();
header.signature_unused = [0u8; 240];
header.checksum = [0u8; 8];
header.desc_count = self.entries.len() as u32;
unsafe {
let version = Version::latest();
let mut hashstate = twox_hash::XxHash3_128::new();
hashstate.write(&[version.magic[0], version.magic[1], version.major, version.minor]);
for entry in &toc_entries {
hashstate.write(&entry.name.to_le_bytes());
hashstate.write(&entry.checksum.to_le_bytes());
}
let hashdigest = hashstate.finish_128();
copy_nonoverlapping(
hashdigest.to_ne_bytes().as_ptr() as *const u8,
header.signature.as_mut_ptr() as *mut u8,
size_of::<Signature>()
);
}
file.seek(SeekFrom::Start(0))?;
let header_bytes = unsafe {
std::slice::from_raw_parts(
&header as *const HeaderV3 as *const u8,
size_of::<HeaderV3>()
)
};
file.write_all(header_bytes)?;
file.set_len(current_data_offset as u64)?;
file.flush()?;
info!("WAD文件写入完成: 总大小={} bytes, 数据区大小={} bytes",
current_data_offset, current_data_offset - data_start_offset);
Ok(())
}
}
pub fn reconstruct_subchunk_count(count_src:u8, entry_type: EntryType, count: u8) -> u8 {
let type_mask = 0b0000_0111; let count_mask = 0b1111_0000;
let cleared = count_src & !(type_mask | count_mask);
let reconstructed = cleared | (entry_type as u8) | (count << 4);
reconstructed
}