1#![warn(missing_docs)]
16
17pub mod apply;
19pub mod chunk;
21pub mod error;
23pub(crate) mod reader;
24
25pub use apply::{Apply, ApplyContext};
26pub use chunk::{Chunk, ZiPatchReader};
27pub use error::ZiPatchError;
28
29pub type Result<T> = std::result::Result<T, ZiPatchError>;
31
32impl<R: std::io::Read> chunk::ZiPatchReader<R> {
33 pub fn apply_to(self, ctx: &mut apply::ApplyContext) -> Result<()> {
38 use apply::Apply;
39 for chunk in self {
40 chunk?.apply(ctx)?;
41 }
42 Ok(())
43 }
44}
45
46#[non_exhaustive]
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum Platform {
55 Win32,
57 Ps3,
59 Ps4,
61 Unknown(u16),
64}
65
66impl std::fmt::Display for Platform {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 match self {
69 Platform::Win32 => f.write_str("Win32"),
70 Platform::Ps3 => f.write_str("PS3"),
71 Platform::Ps4 => f.write_str("PS4"),
72 Platform::Unknown(id) => write!(f, "Unknown({id})"),
73 }
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use std::io::Cursor;
81
82 const MAGIC: [u8; 12] = [
83 0x91, 0x5A, 0x49, 0x50, 0x41, 0x54, 0x43, 0x48, 0x0D, 0x0A, 0x1A, 0x0A,
84 ];
85
86 fn make_chunk(tag: [u8; 4], body: &[u8]) -> Vec<u8> {
87 let mut crc_input = Vec::with_capacity(4 + body.len());
88 crc_input.extend_from_slice(&tag);
89 crc_input.extend_from_slice(body);
90 let crc = crc32fast::hash(&crc_input);
91
92 let mut out = Vec::with_capacity(4 + 4 + body.len() + 4);
93 out.extend_from_slice(&(body.len() as u32).to_be_bytes());
94 out.extend_from_slice(&tag);
95 out.extend_from_slice(body);
96 out.extend_from_slice(&crc.to_be_bytes());
97 out
98 }
99
100 #[test]
101 fn platform_display_all_variants() {
102 assert_eq!(format!("{}", Platform::Win32), "Win32");
103 assert_eq!(format!("{}", Platform::Ps3), "PS3");
104 assert_eq!(format!("{}", Platform::Ps4), "PS4");
105 assert_eq!(format!("{}", Platform::Unknown(42)), "Unknown(42)");
106 }
107
108 #[test]
109 fn apply_to_runs_every_chunk_to_eof() {
110 let mut adir_body = Vec::new();
112 adir_body.extend_from_slice(&7u32.to_be_bytes()); adir_body.extend_from_slice(b"created"); let mut patch = Vec::new();
116 patch.extend_from_slice(&MAGIC);
117 patch.extend_from_slice(&make_chunk(*b"ADIR", &adir_body));
118 patch.extend_from_slice(&make_chunk(*b"EOF_", &[]));
119
120 let tmp = tempfile::tempdir().unwrap();
121 let mut ctx = ApplyContext::new(tmp.path());
122 let reader = ZiPatchReader::new(Cursor::new(patch)).unwrap();
123 reader.apply_to(&mut ctx).unwrap();
124
125 assert!(tmp.path().join("created").is_dir());
126 }
127
128 #[test]
129 fn apply_to_propagates_parse_error() {
130 let mut patch = Vec::new();
132 patch.extend_from_slice(&MAGIC);
133 patch.extend_from_slice(&make_chunk(*b"ZZZZ", &[]));
134
135 let tmp = tempfile::tempdir().unwrap();
136 let mut ctx = ApplyContext::new(tmp.path());
137 let reader = ZiPatchReader::new(Cursor::new(patch)).unwrap();
138 let err = reader.apply_to(&mut ctx).unwrap_err();
139 assert!(matches!(err, ZiPatchError::UnknownChunkTag(_)));
140 }
141
142 #[test]
143 fn apply_to_propagates_apply_error() {
144 let mut deld_body = Vec::new();
147 deld_body.extend_from_slice(&14u32.to_be_bytes()); deld_body.extend_from_slice(b"does_not_exist"); let mut patch = Vec::new();
151 patch.extend_from_slice(&MAGIC);
152 patch.extend_from_slice(&make_chunk(*b"DELD", &deld_body));
153 patch.extend_from_slice(&make_chunk(*b"EOF_", &[]));
154
155 let tmp = tempfile::tempdir().unwrap();
156 let mut ctx = ApplyContext::new(tmp.path());
157 let reader = ZiPatchReader::new(Cursor::new(patch)).unwrap();
158 assert!(reader.apply_to(&mut ctx).is_err());
159 }
160}