rustlol 0.1.1

A wad files lib
Documentation
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 {
        //println!("{}", toc);
        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 let Some(checksum) = Some(entry.loc.checksum) {
            //     if let Some(old) = descriptors_by_checksum.get(&checksum) {
            //         archive.entries.insert(entry.name.clone(), old.clone());
            //         println!("{:#x}", entry.name);
            //         continue;
            //     }
            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);
        //println!("0.0{:?}", toc_error);
        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)?;

        // 1. 写入头部占位符(稍后会回写正确的头部)
        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])?;
        
        // 2. 写入TOC占位符
        file.write_all(&vec![0u8; toc_size])?;
        
        // 3. 准备数据写入
        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();
        
        // 按哈希排序(遵循Python版本的逻辑)
        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;
        
        // 4. 处理每个条目
        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条目
            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,
            });
        }
        
        // 5. 按哈希排序TOC条目(游戏要求)
        toc_entries.sort_by(|a, b| {
            let a_name = a.name;
            let b_name = b.name;
            a_name.cmp(&b_name)
        });
        
        // 6. 写入TOC
        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)?;
        
        // 7. 计算并写入正确的头部
        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;
        
        // 计算文件签名(遵循Python版本的逻辑)
        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)?;
        
        // 8. 设置文件大小并刷新
        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; // 0000 0111,用于提取类型
    let count_mask = 0b1111_0000; // 1111 0000,用于提取数量

    // 清除原始 subchunk_count 的类型和数量部分
    let cleared = count_src & !(type_mask | count_mask);

        // 组合类型和数量
        let reconstructed = cleared | (entry_type as u8) | (count << 4);

        reconstructed
    }