#![warn(missing_docs)]
pub mod apply;
pub mod chunk;
pub mod error;
pub(crate) mod reader;
pub use apply::{Apply, ApplyContext};
pub use chunk::{Chunk, ZiPatchReader};
pub use error::ZiPatchError;
pub type Result<T> = std::result::Result<T, ZiPatchError>;
impl<R: std::io::Read> chunk::ZiPatchReader<R> {
pub fn apply_to(self, ctx: &mut apply::ApplyContext) -> Result<()> {
use apply::Apply;
for chunk in self {
chunk?.apply(ctx)?;
}
Ok(())
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Platform {
Win32,
Ps3,
Ps4,
Unknown(u16),
}
impl std::fmt::Display for Platform {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Platform::Win32 => f.write_str("Win32"),
Platform::Ps3 => f.write_str("PS3"),
Platform::Ps4 => f.write_str("PS4"),
Platform::Unknown(id) => write!(f, "Unknown({id})"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
const MAGIC: [u8; 12] = [
0x91, 0x5A, 0x49, 0x50, 0x41, 0x54, 0x43, 0x48, 0x0D, 0x0A, 0x1A, 0x0A,
];
fn make_chunk(tag: [u8; 4], body: &[u8]) -> Vec<u8> {
let mut crc_input = Vec::with_capacity(4 + body.len());
crc_input.extend_from_slice(&tag);
crc_input.extend_from_slice(body);
let crc = crc32fast::hash(&crc_input);
let mut out = Vec::with_capacity(4 + 4 + body.len() + 4);
out.extend_from_slice(&(body.len() as u32).to_be_bytes());
out.extend_from_slice(&tag);
out.extend_from_slice(body);
out.extend_from_slice(&crc.to_be_bytes());
out
}
#[test]
fn platform_display_all_variants() {
assert_eq!(format!("{}", Platform::Win32), "Win32");
assert_eq!(format!("{}", Platform::Ps3), "PS3");
assert_eq!(format!("{}", Platform::Ps4), "PS4");
assert_eq!(format!("{}", Platform::Unknown(42)), "Unknown(42)");
}
#[test]
fn apply_to_runs_every_chunk_to_eof() {
let mut adir_body = Vec::new();
adir_body.extend_from_slice(&7u32.to_be_bytes()); adir_body.extend_from_slice(b"created");
let mut patch = Vec::new();
patch.extend_from_slice(&MAGIC);
patch.extend_from_slice(&make_chunk(*b"ADIR", &adir_body));
patch.extend_from_slice(&make_chunk(*b"EOF_", &[]));
let tmp = tempfile::tempdir().unwrap();
let mut ctx = ApplyContext::new(tmp.path());
let reader = ZiPatchReader::new(Cursor::new(patch)).unwrap();
reader.apply_to(&mut ctx).unwrap();
assert!(tmp.path().join("created").is_dir());
}
#[test]
fn apply_to_propagates_parse_error() {
let mut patch = Vec::new();
patch.extend_from_slice(&MAGIC);
patch.extend_from_slice(&make_chunk(*b"ZZZZ", &[]));
let tmp = tempfile::tempdir().unwrap();
let mut ctx = ApplyContext::new(tmp.path());
let reader = ZiPatchReader::new(Cursor::new(patch)).unwrap();
let err = reader.apply_to(&mut ctx).unwrap_err();
assert!(matches!(err, ZiPatchError::UnknownChunkTag(_)));
}
#[test]
fn apply_to_propagates_apply_error() {
let mut deld_body = Vec::new();
deld_body.extend_from_slice(&14u32.to_be_bytes()); deld_body.extend_from_slice(b"does_not_exist");
let mut patch = Vec::new();
patch.extend_from_slice(&MAGIC);
patch.extend_from_slice(&make_chunk(*b"DELD", &deld_body));
patch.extend_from_slice(&make_chunk(*b"EOF_", &[]));
let tmp = tempfile::tempdir().unwrap();
let mut ctx = ApplyContext::new(tmp.path());
let reader = ZiPatchReader::new(Cursor::new(patch)).unwrap();
assert!(reader.apply_to(&mut ctx).is_err());
}
}