use crate::{BLTEFile, CompressionMode, Error, Result};
pub fn detect_compression_mode(blte: &BLTEFile) -> Result<CompressionMode> {
if blte.is_single_chunk() {
if let Ok(chunk) = blte.get_chunk_data(0) {
if !chunk.data.is_empty() {
return CompressionMode::from_byte(chunk.data[0])
.ok_or_else(|| Error::InvalidHeaderSize(chunk.data[0] as u32));
}
}
} else {
if let Ok(chunk) = blte.get_chunk_data(0) {
if !chunk.data.is_empty() {
return CompressionMode::from_byte(chunk.data[0])
.ok_or_else(|| Error::InvalidHeaderSize(chunk.data[0] as u32));
}
}
}
Ok(CompressionMode::None)
}
pub fn analyze_chunk_structure(blte: &BLTEFile) -> Result<ChunkStructure> {
if blte.is_single_chunk() {
let chunk = blte.get_chunk_data(0)?;
Ok(ChunkStructure::SingleChunk {
decompressed_size: chunk.decompressed_size,
})
} else {
let chunk_count = blte.chunk_count();
let mut chunk_sizes = Vec::with_capacity(chunk_count);
let mut compressed_sizes = Vec::with_capacity(chunk_count);
for i in 0..chunk_count {
let chunk = blte.get_chunk_data(i)?;
chunk_sizes.push(chunk.decompressed_size);
compressed_sizes.push(chunk.compressed_size);
}
Ok(ChunkStructure::MultiChunk {
chunk_count,
decompressed_sizes: chunk_sizes,
compressed_sizes,
})
}
}
pub fn detect_header_format(blte: &BLTEFile) -> Result<HeaderFormat> {
if blte.is_single_chunk() {
return Ok(HeaderFormat::Standard); }
let expected_chunk_table_size = 4 + (blte.chunk_count() * 24); let header_size = blte.header.header_size as usize;
if header_size == expected_chunk_table_size {
Ok(HeaderFormat::Standard)
} else if header_size == 8 + expected_chunk_table_size {
Ok(HeaderFormat::Archive)
} else {
let data_offset = blte.header.data_offset();
if data_offset == 8 + header_size {
Ok(HeaderFormat::Standard)
} else if data_offset == header_size {
Ok(HeaderFormat::Archive)
} else {
Ok(HeaderFormat::Standard)
}
}
}
pub fn extract_original_checksums(blte: &BLTEFile) -> Result<Vec<[u8; 16]>> {
let mut checksums = Vec::new();
if blte.is_single_chunk() {
checksums.push([0u8; 16]); } else {
for chunk_info in &blte.header.chunks {
checksums.push(chunk_info.checksum);
}
}
Ok(checksums)
}
pub fn extract_compressed_sizes(blte: &BLTEFile) -> Result<Vec<u32>> {
let mut sizes = Vec::new();
if blte.is_single_chunk() {
let chunk = blte.get_chunk_data(0)?;
sizes.push(chunk.compressed_size);
} else {
for chunk_info in &blte.header.chunks {
sizes.push(chunk_info.compressed_size);
}
}
Ok(sizes)
}
#[derive(Debug, Clone)]
pub enum ChunkStructure {
SingleChunk {
decompressed_size: u32,
},
MultiChunk {
chunk_count: usize,
decompressed_sizes: Vec<u32>,
compressed_sizes: Vec<u32>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum HeaderFormat {
Standard,
Archive,
}
#[derive(Debug, Clone)]
pub struct OriginalFileMetadata {
pub compression_mode: CompressionMode,
pub chunk_structure: ChunkStructure,
pub header_format: HeaderFormat,
pub checksums: Vec<[u8; 16]>,
pub compressed_sizes: Vec<u32>,
pub original_offset: usize,
pub original_size: usize,
}
#[derive(Debug, Clone)]
pub struct ExtractedFile {
pub original_index: usize,
pub data: Vec<u8>,
pub metadata: OriginalFileMetadata,
}
impl ExtractedFile {
pub fn from_blte(
index: usize,
blte: &BLTEFile,
original_offset: usize,
original_size: usize,
) -> Result<Self> {
let compression_mode = detect_compression_mode(blte)?;
let chunk_structure = analyze_chunk_structure(blte)?;
let header_format = detect_header_format(blte)?;
let checksums = extract_original_checksums(blte)?;
let compressed_sizes = extract_compressed_sizes(blte)?;
let data = crate::decompress_blte(blte.raw_data(), None)?;
Ok(ExtractedFile {
original_index: index,
data,
metadata: OriginalFileMetadata {
compression_mode,
chunk_structure,
header_format,
checksums,
compressed_sizes,
original_offset,
original_size,
},
})
}
pub fn recreate_perfect_blte(&self) -> Result<Vec<u8>> {
recreate_perfect_blte_file(self)
}
}
pub fn recreate_perfect_blte_file(file: &ExtractedFile) -> Result<Vec<u8>> {
let meta = &file.metadata;
let recompressed_blte = match &meta.chunk_structure {
ChunkStructure::SingleChunk { .. } => {
crate::compress_data_single(file.data.clone(), meta.compression_mode, None)?
}
ChunkStructure::MultiChunk { .. } => {
recreate_multichunk_blte(file)?
}
};
let recreated = BLTEFile::parse(recompressed_blte)?;
verify_recreated_blte(&recreated, meta)?;
let final_blte = apply_header_format_corrections(recreated.raw_data(), meta)?;
Ok(final_blte)
}
fn recreate_multichunk_blte(file: &ExtractedFile) -> Result<Vec<u8>> {
let meta = &file.metadata;
if let ChunkStructure::MultiChunk {
decompressed_sizes, ..
} = &meta.chunk_structure
{
let total_decompressed: u32 = decompressed_sizes.iter().sum();
let avg_chunk_size = if !decompressed_sizes.is_empty() {
total_decompressed / decompressed_sizes.len() as u32
} else {
64 * 1024 };
crate::compress_data_multi(
file.data.clone(),
avg_chunk_size as usize,
meta.compression_mode,
None,
)
} else {
Err(crate::Error::InvalidChunkCount(0))
}
}
fn verify_recreated_blte(recreated: &BLTEFile, original_meta: &OriginalFileMetadata) -> Result<()> {
let recreated_mode = detect_compression_mode(recreated)?;
if recreated_mode != original_meta.compression_mode {
tracing::warn!(
"Compression mode mismatch: expected {:?}, got {:?}",
original_meta.compression_mode,
recreated_mode
);
}
let expected_chunk_count = match &original_meta.chunk_structure {
ChunkStructure::SingleChunk { .. } => 1,
ChunkStructure::MultiChunk { chunk_count, .. } => *chunk_count,
};
if recreated.chunk_count() != expected_chunk_count {
tracing::warn!(
"Chunk count mismatch: expected {}, got {}",
expected_chunk_count,
recreated.chunk_count()
);
}
let recreated_format = detect_header_format(recreated)?;
if recreated_format != original_meta.header_format {
tracing::warn!(
"Header format mismatch: expected {:?}, got {:?}",
original_meta.header_format,
recreated_format
);
}
Ok(())
}
fn apply_header_format_corrections(
blte_data: Vec<u8>,
meta: &OriginalFileMetadata,
) -> Result<Vec<u8>> {
let blte = BLTEFile::parse(blte_data.clone())?;
let current_format = detect_header_format(&blte)?;
if current_format == meta.header_format {
return Ok(blte_data);
}
tracing::debug!(
"Header format correction needed: {:?} -> {:?} (not implemented yet)",
current_format,
meta.header_format
);
Ok(blte_data)
}
#[derive(Debug)]
pub struct PerfectArchiveBuilder {
files: Vec<ExtractedFile>,
target_size: usize,
current_size: usize,
}
impl PerfectArchiveBuilder {
pub fn new() -> Self {
Self {
files: Vec::new(),
target_size: 256 * 1024 * 1024, current_size: 0,
}
}
pub fn target_size(mut self, size: usize) -> Self {
self.target_size = size;
self
}
pub fn add_extracted_file(&mut self, file: ExtractedFile) -> Result<bool> {
let estimated_size = estimate_recreated_size(&file)?;
if self.current_size + estimated_size > self.target_size {
return Ok(false); }
self.current_size += estimated_size;
self.files.push(file);
Ok(true) }
pub fn build_perfect(mut self) -> Result<Vec<u8>> {
println!(
"Building perfect archive from {} files...",
self.files.len()
);
self.files.sort_by_key(|f| f.original_index);
let mut archive = Vec::with_capacity(self.current_size);
let mut current_offset = 0;
for (i, file) in self.files.iter().enumerate() {
if i % 1000 == 0 {
println!(" Recreating file {}/{}", i, self.files.len());
}
let recreated_blte = file.recreate_perfect_blte()?;
if current_offset != file.metadata.original_offset {
tracing::warn!(
"Offset mismatch for file {}: expected {}, got {}",
i,
file.metadata.original_offset,
current_offset
);
}
archive.extend_from_slice(&recreated_blte);
current_offset += recreated_blte.len();
}
println!("Perfect archive built: {} bytes", archive.len());
Ok(archive)
}
pub fn file_count(&self) -> usize {
self.files.len()
}
pub fn current_size(&self) -> usize {
self.current_size
}
}
impl Default for PerfectArchiveBuilder {
fn default() -> Self {
Self::new()
}
}
fn estimate_recreated_size(file: &ExtractedFile) -> Result<usize> {
let original_ratio = if !file.data.is_empty() {
file.metadata.original_size as f64 / file.data.len() as f64
} else {
1.0
};
let estimated = (file.data.len() as f64 * original_ratio * 1.1) as usize; Ok(estimated.max(file.metadata.original_size)) }
#[cfg(test)]
mod tests {
use super::*;
use crate::{BLTEFile, compress_data_single};
#[test]
fn test_compression_mode_detection() {
let test_data = b"Hello, BLTE world!".to_vec();
let compressed =
compress_data_single(test_data.clone(), CompressionMode::ZLib, None).unwrap();
let blte = BLTEFile::parse(compressed).unwrap();
let detected = detect_compression_mode(&blte).unwrap();
assert_eq!(detected, CompressionMode::ZLib);
}
#[test]
fn test_chunk_structure_analysis() {
let test_data = b"Hello, BLTE world!".to_vec();
let compressed =
compress_data_single(test_data.clone(), CompressionMode::None, None).unwrap();
let blte = BLTEFile::parse(compressed).unwrap();
let structure = analyze_chunk_structure(&blte).unwrap();
match structure {
ChunkStructure::SingleChunk { decompressed_size } => {
assert_eq!(decompressed_size, 0);
}
_ => panic!("Expected single chunk structure"),
}
}
#[test]
fn test_header_format_detection() {
let test_data = b"Hello, BLTE world!".to_vec();
let compressed = compress_data_single(test_data, CompressionMode::None, None).unwrap();
let blte = BLTEFile::parse(compressed).unwrap();
let format = detect_header_format(&blte).unwrap();
assert_eq!(format, HeaderFormat::Standard); }
}