#![allow(dead_code)]
use std::collections::BTreeMap;
use std::io::Write;
use std::path::{Path, PathBuf};
use flate2::Compression;
use flate2::write::DeflateEncoder;
use zipatch_rs::test_utils::{MAGIC, make_chunk};
pub fn sqpk_add_data_body(
main_id: u16,
sub_id: u16,
file_id: u32,
block_offset_raw: u32,
data: &[u8],
block_delete_raw: u32,
) -> Vec<u8> {
assert!(data.len() % 128 == 0, "AddData payload must be 128-aligned");
let data_bytes_raw = (data.len() / 128) as u32;
let total = 5 + 23 + data.len();
let mut body = Vec::with_capacity(total);
body.extend_from_slice(&(total as i32).to_be_bytes());
body.push(b'A');
body.extend_from_slice(&[0u8; 3]);
body.extend_from_slice(&main_id.to_be_bytes());
body.extend_from_slice(&sub_id.to_be_bytes());
body.extend_from_slice(&file_id.to_be_bytes());
body.extend_from_slice(&block_offset_raw.to_be_bytes());
body.extend_from_slice(&data_bytes_raw.to_be_bytes());
body.extend_from_slice(&block_delete_raw.to_be_bytes());
body.extend_from_slice(data);
body
}
pub fn sqpk_expand_data_body(
main_id: u16,
sub_id: u16,
file_id: u32,
block_offset_raw: u32,
block_count: u32,
) -> Vec<u8> {
let mut cmd = Vec::new();
cmd.extend_from_slice(&[0u8; 3]);
cmd.extend_from_slice(&main_id.to_be_bytes());
cmd.extend_from_slice(&sub_id.to_be_bytes());
cmd.extend_from_slice(&file_id.to_be_bytes());
cmd.extend_from_slice(&block_offset_raw.to_be_bytes());
cmd.extend_from_slice(&block_count.to_be_bytes());
cmd.extend_from_slice(&[0u8; 4]);
let mut body = Vec::new();
let inner_size = (5 + cmd.len()) as i32;
body.extend_from_slice(&inner_size.to_be_bytes());
body.push(b'E');
body.extend_from_slice(&cmd);
body
}
pub fn sqpk_target_info_body(platform_id: u16) -> Vec<u8> {
let mut cmd = Vec::new();
cmd.extend_from_slice(&[0u8; 3]);
cmd.extend_from_slice(&platform_id.to_be_bytes());
cmd.extend_from_slice(&0i16.to_be_bytes());
cmd.extend_from_slice(&0i16.to_be_bytes());
cmd.extend_from_slice(&0u16.to_be_bytes());
cmd.extend_from_slice(&0u64.to_le_bytes());
cmd.extend_from_slice(&0u64.to_le_bytes());
let mut body = Vec::new();
let inner_size = (5 + cmd.len()) as i32;
body.extend_from_slice(&inner_size.to_be_bytes());
body.push(b'T');
body.extend_from_slice(&cmd);
body
}
pub fn adir_body(name: &str) -> Vec<u8> {
let mut b = Vec::new();
b.extend_from_slice(&(name.len() as u32).to_be_bytes());
b.extend_from_slice(name.as_bytes());
b
}
#[derive(Clone)]
pub struct FileBlock {
pub is_compressed: bool,
pub decompressed: Vec<u8>,
}
pub fn sqpk_addfile_body(path: &str, file_offset: i64, blocks: &[FileBlock]) -> Vec<u8> {
let mut path_bytes = path.as_bytes().to_vec();
path_bytes.push(0);
let mut cmd = Vec::new();
cmd.push(b'A'); cmd.extend_from_slice(&[0u8; 2]); cmd.extend_from_slice(&(file_offset as u64).to_be_bytes());
let total_size: u64 = blocks.iter().map(|b| b.decompressed.len() as u64).sum();
cmd.extend_from_slice(&total_size.to_be_bytes());
cmd.extend_from_slice(&(path_bytes.len() as u32).to_be_bytes());
cmd.extend_from_slice(&0u16.to_be_bytes()); cmd.extend_from_slice(&[0u8; 2]); cmd.extend_from_slice(&path_bytes);
for block in blocks {
if block.is_compressed {
let mut enc = DeflateEncoder::new(Vec::new(), Compression::default());
enc.write_all(&block.decompressed).unwrap();
let compressed = enc.finish().unwrap();
let header_size: i32 = 16;
let compressed_size = compressed.len() as i32;
let decompressed_size = block.decompressed.len() as i32;
let data_len = compressed_size;
let block_len = ((data_len as u32 + 143) & !127) as usize;
let pad = block_len - 16 - compressed.len();
cmd.extend_from_slice(&header_size.to_le_bytes());
cmd.extend_from_slice(&0u32.to_le_bytes()); cmd.extend_from_slice(&compressed_size.to_le_bytes());
cmd.extend_from_slice(&decompressed_size.to_le_bytes());
cmd.extend_from_slice(&compressed);
cmd.extend_from_slice(&vec![0u8; pad]);
} else {
let header_size: i32 = 16;
let compressed_size: i32 = 0x7d00; let decompressed_size = block.decompressed.len() as i32;
let data_len = decompressed_size;
let block_len = ((data_len as u32 + 143) & !127) as usize;
let pad = block_len - 16 - block.decompressed.len();
cmd.extend_from_slice(&header_size.to_le_bytes());
cmd.extend_from_slice(&0u32.to_le_bytes());
cmd.extend_from_slice(&compressed_size.to_le_bytes());
cmd.extend_from_slice(&decompressed_size.to_le_bytes());
cmd.extend_from_slice(&block.decompressed);
cmd.extend_from_slice(&vec![0u8; pad]);
}
}
let mut body = Vec::new();
let inner_size = (5 + cmd.len()) as i32;
body.extend_from_slice(&inner_size.to_be_bytes());
body.push(b'F');
body.extend_from_slice(&cmd);
body
}
pub fn sqpk_delete_file_body(path: &str) -> Vec<u8> {
let mut path_bytes = path.as_bytes().to_vec();
path_bytes.push(0);
let mut cmd = Vec::new();
cmd.push(b'D');
cmd.extend_from_slice(&[0u8; 2]);
cmd.extend_from_slice(&0u64.to_be_bytes()); cmd.extend_from_slice(&0u64.to_be_bytes()); cmd.extend_from_slice(&(path_bytes.len() as u32).to_be_bytes());
cmd.extend_from_slice(&0u16.to_be_bytes());
cmd.extend_from_slice(&[0u8; 2]);
cmd.extend_from_slice(&path_bytes);
let mut body = Vec::new();
let inner_size = (5 + cmd.len()) as i32;
body.extend_from_slice(&inner_size.to_be_bytes());
body.push(b'F');
body.extend_from_slice(&cmd);
body
}
pub fn wrap_patch(chunks: Vec<Vec<u8>>) -> Vec<u8> {
let mut patch = Vec::new();
patch.extend_from_slice(&MAGIC);
for c in chunks {
patch.extend_from_slice(&c);
}
patch.extend_from_slice(&make_chunk(b"EOF_", &[]));
patch
}
pub fn snapshot_tree(root: &Path) -> BTreeMap<PathBuf, (u64, u32)> {
let mut out = BTreeMap::new();
if root.exists() {
visit(root, root, &mut out);
}
out
}
fn visit(root: &Path, dir: &Path, out: &mut BTreeMap<PathBuf, (u64, u32)>) {
for entry in std::fs::read_dir(dir).unwrap() {
let entry = entry.unwrap();
let path = entry.path();
let kind = entry.file_type().unwrap();
if kind.is_dir() {
visit(root, &path, out);
} else if kind.is_file() {
let bytes = std::fs::read(&path).unwrap();
let rel = path.strip_prefix(root).unwrap().to_path_buf();
out.insert(rel, (bytes.len() as u64, crc32fast::hash(&bytes)));
}
}
}
pub fn assert_trees_equal(a: &Path, b: &Path) {
let snap_a = snapshot_tree(a);
let snap_b = snapshot_tree(b);
let keys_a: Vec<_> = snap_a.keys().collect();
let keys_b: Vec<_> = snap_b.keys().collect();
assert_eq!(
keys_a, keys_b,
"directory contents differ:\n sequential: {keys_a:?}\n indexed: {keys_b:?}"
);
for (rel, (len_a, crc_a)) in &snap_a {
let (len_b, crc_b) = &snap_b[rel];
assert_eq!(len_a, len_b, "file size mismatch for {}", rel.display());
if crc_a != crc_b {
let bytes_a = std::fs::read(a.join(rel)).unwrap();
let bytes_b = std::fs::read(b.join(rel)).unwrap();
let mismatch = bytes_a.iter().zip(bytes_b.iter()).position(|(x, y)| x != y);
panic!(
"content mismatch for {}: crc {crc_a:#010x} vs {crc_b:#010x}, first diff at {:?}",
rel.display(),
mismatch
);
}
}
}