use super::header::{PatchFile, PatchType};
use crate::{Error, Result};
pub fn apply_patch(patch: &PatchFile, base_data: &[u8]) -> Result<Vec<u8>> {
patch.verify_base(base_data)?;
let patched_data = match patch.header.patch_type {
PatchType::Copy => apply_copy_patch(patch, base_data)?,
PatchType::Bsd0 => apply_bsd0_patch(patch, base_data)?,
};
patch.verify_patched(&patched_data)?;
Ok(patched_data)
}
fn apply_copy_patch(patch: &PatchFile, base_data: &[u8]) -> Result<Vec<u8>> {
if base_data.len() != patch.header.size_before as usize {
return Err(Error::invalid_format(format!(
"Base file size mismatch: expected {}, got {}",
patch.header.size_before,
base_data.len()
)));
}
if patch.data.len() != patch.header.size_after as usize {
return Err(Error::invalid_format(format!(
"Patch data size mismatch: expected {}, got {}",
patch.header.size_after,
patch.data.len()
)));
}
log::debug!(
"Applying COPY patch: {} -> {} bytes",
base_data.len(),
patch.data.len()
);
Ok(patch.data.clone())
}
fn apply_bsd0_patch(patch: &PatchFile, base_data: &[u8]) -> Result<Vec<u8>> {
use byteorder::{LittleEndian, ReadBytesExt};
use std::io::Cursor;
if base_data.len() != patch.header.size_before as usize {
return Err(Error::invalid_format(format!(
"Base file size mismatch: expected {}, got {}",
patch.header.size_before,
base_data.len()
)));
}
log::debug!(
"RLE decompressing BSD0 patch data: {} bytes compressed",
patch.data.len()
);
let bsdiff_data = crate::compression::rle::decompress(
&patch.data,
patch.header.patch_data_size as usize,
true, )?;
log::debug!(
"RLE decompression complete: {} bytes → {} bytes",
patch.data.len(),
bsdiff_data.len()
);
let mut reader = Cursor::new(&bsdiff_data);
let signature = reader.read_u64::<LittleEndian>()?;
if signature != 0x3034464649445342 {
return Err(Error::invalid_format(format!(
"Invalid BSDIFF40 signature: expected 0x3034464649445342, got 0x{signature:016X}"
)));
}
let ctrl_block_size = reader.read_u64::<LittleEndian>()? as usize;
let data_block_size = reader.read_u64::<LittleEndian>()? as usize;
let new_file_size = reader.read_u64::<LittleEndian>()? as usize;
if new_file_size != patch.header.size_after as usize {
return Err(Error::invalid_format(format!(
"BSD0 new file size mismatch: header says {}, bsdiff says {new_file_size}",
patch.header.size_after
)));
}
log::debug!(
"BSD0 patch: {} -> {} bytes (ctrl: {}, data: {})",
base_data.len(),
new_file_size,
ctrl_block_size,
data_block_size
);
let ctrl_start = 32; let data_start = ctrl_start + ctrl_block_size;
let extra_start = data_start + data_block_size;
if extra_start > bsdiff_data.len() {
return Err(Error::invalid_format(format!(
"BSD0 patch data too small: need {extra_start} bytes, have {}",
bsdiff_data.len()
)));
}
let ctrl_block = &bsdiff_data[ctrl_start..data_start];
let data_block = &bsdiff_data[data_start..extra_start];
let extra_block = &bsdiff_data[extra_start..];
let num_ctrl_blocks = ctrl_block_size / 12;
let mut new_data = vec![0u8; new_file_size];
let mut new_offset = 0usize;
let mut old_offset = 0usize;
let mut data_ptr = 0usize;
let mut extra_ptr = 0usize;
for i in 0..num_ctrl_blocks {
let ctrl_offset = i * 12;
if ctrl_offset + 12 > ctrl_block.len() {
return Err(Error::invalid_format(
"Control block extends beyond ctrl_block_size".to_string(),
));
}
let mut ctrl_reader = Cursor::new(&ctrl_block[ctrl_offset..ctrl_offset + 12]);
let add_data_length = ctrl_reader.read_u32::<LittleEndian>()? as usize;
let mov_data_length = ctrl_reader.read_u32::<LittleEndian>()? as usize;
let old_move_length_raw = ctrl_reader.read_u32::<LittleEndian>()?;
if new_offset + add_data_length > new_file_size {
return Err(Error::invalid_format(format!(
"BSD0: add overflow at ctrl {i}: new_offset {new_offset} + add {add_data_length} > size {new_file_size}"
)));
}
if data_ptr + add_data_length > data_block.len() {
return Err(Error::invalid_format(format!(
"BSD0: data block overflow at ctrl {i}: ptr {data_ptr} + len {add_data_length} > size {}",
data_block.len()
)));
}
new_data[new_offset..new_offset + add_data_length]
.copy_from_slice(&data_block[data_ptr..data_ptr + add_data_length]);
data_ptr += add_data_length;
let combine_size = if old_offset + add_data_length >= base_data.len() {
base_data.len().saturating_sub(old_offset)
} else {
add_data_length
};
for j in 0..combine_size {
new_data[new_offset + j] =
new_data[new_offset + j].wrapping_add(base_data[old_offset + j]);
}
new_offset += add_data_length;
old_offset += add_data_length;
if new_offset + mov_data_length > new_file_size {
return Err(Error::invalid_format(format!(
"BSD0: mov overflow at ctrl {i}: new_offset {new_offset} + mov {mov_data_length} > size {new_file_size}"
)));
}
if extra_ptr + mov_data_length > extra_block.len() {
return Err(Error::invalid_format(format!(
"BSD0: extra block overflow at ctrl {i}: ptr {extra_ptr} + len {mov_data_length} > size {}",
extra_block.len()
)));
}
new_data[new_offset..new_offset + mov_data_length]
.copy_from_slice(&extra_block[extra_ptr..extra_ptr + mov_data_length]);
extra_ptr += mov_data_length;
new_offset += mov_data_length;
let old_move_length = if old_move_length_raw & 0x80000000 != 0 {
let neg_val = 0x80000000u32.wrapping_sub(old_move_length_raw);
old_offset = old_offset.saturating_sub(neg_val as usize);
0
} else {
old_move_length_raw as usize
};
old_offset += old_move_length;
}
if new_offset != new_file_size {
return Err(Error::invalid_format(format!(
"BSD0: final offset mismatch: got {new_offset}, expected {new_file_size}"
)));
}
log::debug!("BSD0 patch applied successfully: {} bytes", new_data.len());
Ok(new_data)
}
#[cfg(test)]
mod tests {
use super::*;
fn create_copy_patch(base_size: u32, new_data: Vec<u8>) -> PatchFile {
use md5::{Digest, Md5};
let base_data = vec![0u8; base_size as usize];
let mut hasher = Md5::new();
hasher.update(&base_data);
let md5_before: [u8; 16] = hasher.finalize().into();
let mut hasher = Md5::new();
hasher.update(&new_data);
let md5_after: [u8; 16] = hasher.finalize().into();
let mut data = Vec::new();
data.extend_from_slice(&0x48435450u32.to_le_bytes());
data.extend_from_slice(&(new_data.len() as u32).to_le_bytes());
data.extend_from_slice(&base_size.to_le_bytes());
data.extend_from_slice(&(new_data.len() as u32).to_le_bytes());
data.extend_from_slice(&0x5f35444du32.to_le_bytes());
data.extend_from_slice(&40u32.to_le_bytes());
data.extend_from_slice(&md5_before);
data.extend_from_slice(&md5_after);
data.extend_from_slice(&0x4d524658u32.to_le_bytes());
data.extend_from_slice(&(12 + new_data.len() as u32).to_le_bytes());
data.extend_from_slice(&0x59504f43u32.to_le_bytes());
data.extend_from_slice(&new_data);
PatchFile::parse(&data).expect("Failed to create test patch")
}
#[test]
fn test_apply_copy_patch_simple() {
let base_data = vec![0u8; 100];
let new_data = b"Hello, Warcraft!".to_vec();
let patch = create_copy_patch(100, new_data.clone());
let result = apply_patch(&patch, &base_data).expect("Failed to apply patch");
assert_eq!(result, new_data);
}
#[test]
fn test_copy_patch_size_increase() {
let base_data = vec![0u8; 50];
let new_data = vec![0x42u8; 200];
let patch = create_copy_patch(50, new_data.clone());
let result = apply_patch(&patch, &base_data).expect("Failed to apply patch");
assert_eq!(result.len(), 200);
assert_eq!(result, new_data);
}
#[test]
fn test_copy_patch_size_decrease() {
let base_data = vec![0u8; 200];
let new_data = vec![0x42u8; 50];
let patch = create_copy_patch(200, new_data.clone());
let result = apply_patch(&patch, &base_data).expect("Failed to apply patch");
assert_eq!(result.len(), 50);
assert_eq!(result, new_data);
}
#[test]
fn test_copy_patch_wrong_base_size() {
let base_data = vec![0u8; 100]; let new_data = b"Test".to_vec();
let patch = create_copy_patch(50, new_data); let result = apply_patch(&patch, &base_data);
assert!(result.is_err());
}
#[test]
fn test_copy_patch_wrong_base_md5() {
let base_data = vec![0x42u8; 100]; let new_data = b"Test".to_vec();
let patch = create_copy_patch(100, new_data); let result = apply_patch(&patch, &base_data);
assert!(result.is_err());
}
fn rle_compress_test(data: &[u8]) -> Vec<u8> {
let mut compressed = Vec::new();
compressed.extend_from_slice(&(data.len() as u32).to_le_bytes());
for chunk in data.chunks(128) {
let count = chunk.len() as u8;
compressed.push(0x80 | (count - 1)); compressed.extend_from_slice(chunk);
}
compressed
}
fn create_bsd0_patch(base_data: &[u8], new_data: &[u8], patch_data: Vec<u8>) -> PatchFile {
use md5::{Digest, Md5};
let mut hasher = Md5::new();
hasher.update(base_data);
let md5_before: [u8; 16] = hasher.finalize().into();
let mut hasher = Md5::new();
hasher.update(new_data);
let md5_after: [u8; 16] = hasher.finalize().into();
let compressed_patch_data = rle_compress_test(&patch_data);
let mut data = Vec::new();
data.extend_from_slice(&0x48435450u32.to_le_bytes());
data.extend_from_slice(&(patch_data.len() as u32).to_le_bytes()); data.extend_from_slice(&(base_data.len() as u32).to_le_bytes());
data.extend_from_slice(&(new_data.len() as u32).to_le_bytes());
data.extend_from_slice(&0x5f35444du32.to_le_bytes());
data.extend_from_slice(&40u32.to_le_bytes());
data.extend_from_slice(&md5_before);
data.extend_from_slice(&md5_after);
data.extend_from_slice(&0x4d524658u32.to_le_bytes());
data.extend_from_slice(&(12u32 + compressed_patch_data.len() as u32).to_le_bytes());
data.extend_from_slice(&0x30445342u32.to_le_bytes());
data.extend_from_slice(&compressed_patch_data);
PatchFile::parse(&data).expect("Failed to create test BSD0 patch")
}
#[test]
fn test_apply_bsd0_simple() {
let old_data = vec![0x10u8, 0x20, 0x30];
let new_data = vec![0x15u8, 0x27, 0xFF];
let mut patch_data = Vec::new();
patch_data.extend_from_slice(&0x3034464649445342u64.to_le_bytes()); patch_data.extend_from_slice(&12u64.to_le_bytes()); patch_data.extend_from_slice(&2u64.to_le_bytes()); patch_data.extend_from_slice(&3u64.to_le_bytes());
patch_data.extend_from_slice(&2u32.to_le_bytes()); patch_data.extend_from_slice(&1u32.to_le_bytes()); patch_data.extend_from_slice(&1u32.to_le_bytes());
patch_data.push(0x05);
patch_data.push(0x07);
patch_data.push(0xFF);
let patch = create_bsd0_patch(&old_data, &new_data, patch_data);
let result = apply_patch(&patch, &old_data).expect("Failed to apply BSD0 patch");
assert_eq!(result, new_data);
}
#[test]
fn test_bsd0_wrapping_addition() {
let old_data = vec![0xFFu8];
let new_data = vec![0x01u8];
let mut patch_data = Vec::new();
patch_data.extend_from_slice(&0x3034464649445342u64.to_le_bytes());
patch_data.extend_from_slice(&12u64.to_le_bytes());
patch_data.extend_from_slice(&1u64.to_le_bytes());
patch_data.extend_from_slice(&1u64.to_le_bytes());
patch_data.extend_from_slice(&1u32.to_le_bytes()); patch_data.extend_from_slice(&0u32.to_le_bytes()); patch_data.extend_from_slice(&0u32.to_le_bytes());
patch_data.push(0x02);
let patch = create_bsd0_patch(&old_data, &new_data, patch_data);
let result = apply_patch(&patch, &old_data).expect("Failed to apply BSD0 patch");
assert_eq!(result, new_data);
assert_eq!(result[0], 0x01);
}
#[test]
fn test_bsd0_multiple_control_blocks() {
let old_data = vec![0x10u8, 0x20, 0x30, 0x40];
let new_data = vec![0x15u8, 0x27, 0xFF, 0xAA];
let mut patch_data = Vec::new();
patch_data.extend_from_slice(&0x3034464649445342u64.to_le_bytes());
patch_data.extend_from_slice(&24u64.to_le_bytes()); patch_data.extend_from_slice(&2u64.to_le_bytes()); patch_data.extend_from_slice(&4u64.to_le_bytes());
patch_data.extend_from_slice(&2u32.to_le_bytes()); patch_data.extend_from_slice(&1u32.to_le_bytes()); patch_data.extend_from_slice(&1u32.to_le_bytes());
patch_data.extend_from_slice(&0u32.to_le_bytes()); patch_data.extend_from_slice(&1u32.to_le_bytes()); patch_data.extend_from_slice(&0u32.to_le_bytes());
patch_data.push(0x05); patch_data.push(0x07);
patch_data.push(0xFF);
patch_data.push(0xAA);
let patch = create_bsd0_patch(&old_data, &new_data, patch_data);
let result = apply_patch(&patch, &old_data).expect("Failed to apply BSD0 patch");
assert_eq!(result, new_data);
}
#[test]
fn test_bsd0_invalid_signature() {
let old_data = vec![0x10u8];
let new_data = vec![0x11u8];
let mut patch_data = Vec::new();
patch_data.extend_from_slice(&0xDEADBEEFDEADBEEFu64.to_le_bytes()); patch_data.extend_from_slice(&12u64.to_le_bytes());
patch_data.extend_from_slice(&1u64.to_le_bytes());
patch_data.extend_from_slice(&1u64.to_le_bytes());
patch_data.extend_from_slice(&1u32.to_le_bytes());
patch_data.extend_from_slice(&0u32.to_le_bytes());
patch_data.extend_from_slice(&0u32.to_le_bytes());
patch_data.push(0x01);
let patch = create_bsd0_patch(&old_data, &new_data, patch_data);
let result = apply_patch(&patch, &old_data);
assert!(result.is_err());
}
#[test]
fn test_bsd0_size_mismatch() {
let old_data = vec![0x10u8, 0x20]; let new_data = vec![0x11u8];
let mut patch_data = Vec::new();
patch_data.extend_from_slice(&0x3034464649445342u64.to_le_bytes());
patch_data.extend_from_slice(&12u64.to_le_bytes());
patch_data.extend_from_slice(&1u64.to_le_bytes());
patch_data.extend_from_slice(&1u64.to_le_bytes()); patch_data.extend_from_slice(&1u32.to_le_bytes());
patch_data.extend_from_slice(&0u32.to_le_bytes());
patch_data.extend_from_slice(&0u32.to_le_bytes());
patch_data.push(0x01);
let patch = create_bsd0_patch(&[0x10], &new_data, patch_data);
let result = apply_patch(&patch, &old_data);
assert!(result.is_err());
}
}