1use crate::{Error, Result};
4use byteorder::{LittleEndian, ReadBytesExt};
5use std::io::Read;
6
7const PTCH_SIGNATURE: u32 = 0x48435450; const MD5_SIGNATURE: u32 = 0x5f35444d; const XFRM_SIGNATURE: u32 = 0x4d524658; #[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum PatchType {
19 Copy,
21 Bsd0,
23}
24
25impl PatchType {
26 fn from_magic(magic: u32) -> Result<Self> {
28 match magic {
29 0x59504f43 => Ok(PatchType::Copy), 0x30445342 => Ok(PatchType::Bsd0), _ => Err(Error::invalid_format(format!(
32 "Unknown patch type: 0x{magic:08X}"
33 ))),
34 }
35 }
36
37 pub fn to_magic(self) -> u32 {
39 match self {
40 PatchType::Copy => 0x59504f43,
41 PatchType::Bsd0 => 0x30445342,
42 }
43 }
44}
45
46#[derive(Debug, Clone)]
48pub struct PatchHeader {
49 pub patch_data_size: u32,
51 pub size_before: u32,
53 pub size_after: u32,
55 pub md5_before: [u8; 16],
57 pub md5_after: [u8; 16],
59 pub patch_type: PatchType,
61 pub xfrm_data_size: u32,
63}
64
65impl PatchHeader {
66 pub fn parse<R: Read>(reader: &mut R) -> Result<Self> {
75 let ptch_sig = reader.read_u32::<LittleEndian>()?;
77 if ptch_sig != PTCH_SIGNATURE {
78 return Err(Error::invalid_format(format!(
79 "Invalid PTCH signature: expected 0x{PTCH_SIGNATURE:08X}, got 0x{ptch_sig:08X}"
80 )));
81 }
82
83 let patch_data_size = reader.read_u32::<LittleEndian>()?;
84 let size_before = reader.read_u32::<LittleEndian>()?;
85 let size_after = reader.read_u32::<LittleEndian>()?;
86
87 log::debug!(
88 "PTCH header: patch_size={patch_data_size}, before={size_before}, after={size_after}"
89 );
90
91 let md5_sig = reader.read_u32::<LittleEndian>()?;
93 if md5_sig != MD5_SIGNATURE {
94 return Err(Error::invalid_format(format!(
95 "Invalid MD5 signature: expected 0x{MD5_SIGNATURE:08X}, got 0x{md5_sig:08X}"
96 )));
97 }
98
99 let md5_block_size = reader.read_u32::<LittleEndian>()?;
100 if md5_block_size != 40 {
101 return Err(Error::invalid_format(format!(
102 "Invalid MD5 block size: expected 40, got {md5_block_size}"
103 )));
104 }
105
106 let mut md5_before = [0u8; 16];
107 reader.read_exact(&mut md5_before)?;
108
109 let mut md5_after = [0u8; 16];
110 reader.read_exact(&mut md5_after)?;
111
112 log::debug!(
113 "MD5 before: {}, after: {}",
114 hex::encode(md5_before),
115 hex::encode(md5_after)
116 );
117
118 let xfrm_sig = reader.read_u32::<LittleEndian>()?;
120 if xfrm_sig != XFRM_SIGNATURE {
121 return Err(Error::invalid_format(format!(
122 "Invalid XFRM signature: expected 0x{XFRM_SIGNATURE:08X}, got 0x{xfrm_sig:08X}"
123 )));
124 }
125
126 let xfrm_block_size = reader.read_u32::<LittleEndian>()?;
127 let patch_type_magic = reader.read_u32::<LittleEndian>()?;
128
129 let patch_type = PatchType::from_magic(patch_type_magic)?;
130
131 let xfrm_data_size = xfrm_block_size.saturating_sub(12);
133
134 log::debug!(
135 "XFRM header: type={:?}, block_size={xfrm_block_size}, data_size={xfrm_data_size}",
136 patch_type
137 );
138
139 Ok(PatchHeader {
140 patch_data_size,
141 size_before,
142 size_after,
143 md5_before,
144 md5_after,
145 patch_type,
146 xfrm_data_size,
147 })
148 }
149
150 pub const HEADER_SIZE: usize = 12 + 40 + 12; }
153
154#[derive(Debug, Clone)]
156pub struct PatchFile {
157 pub header: PatchHeader,
159 pub data: Vec<u8>,
161}
162
163impl PatchFile {
164 pub fn parse(data: &[u8]) -> Result<Self> {
166 if data.len() < PatchHeader::HEADER_SIZE {
167 return Err(Error::invalid_format(format!(
168 "Patch file too small: {} bytes, need at least {}",
169 data.len(),
170 PatchHeader::HEADER_SIZE
171 )));
172 }
173
174 let mut reader = std::io::Cursor::new(data);
175 let header = PatchHeader::parse(&mut reader)?;
176
177 let mut patch_data = Vec::new();
179 reader.read_to_end(&mut patch_data)?;
180
181 log::debug!(
182 "Parsed patch file: type={:?}, data_size={}",
183 header.patch_type,
184 patch_data.len()
185 );
186
187 Ok(PatchFile {
188 header,
189 data: patch_data,
190 })
191 }
192
193 pub fn verify_base(&self, base_data: &[u8]) -> Result<()> {
195 use md5::{Digest, Md5};
196
197 let mut hasher = Md5::new();
198 hasher.update(base_data);
199 let result = hasher.finalize();
200
201 if result.as_slice() != self.header.md5_before {
202 return Err(Error::invalid_format(format!(
203 "Base file MD5 mismatch: expected {}, got {}",
204 hex::encode(self.header.md5_before),
205 hex::encode(result)
206 )));
207 }
208
209 Ok(())
210 }
211
212 pub fn verify_patched(&self, patched_data: &[u8]) -> Result<()> {
214 use md5::{Digest, Md5};
215
216 let mut hasher = Md5::new();
217 hasher.update(patched_data);
218 let result = hasher.finalize();
219
220 if result.as_slice() != self.header.md5_after {
221 return Err(Error::invalid_format(format!(
222 "Patched file MD5 mismatch: expected {}, got {}",
223 hex::encode(self.header.md5_after),
224 hex::encode(result)
225 )));
226 }
227
228 Ok(())
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn test_patch_type_magic() {
238 assert_eq!(PatchType::Copy.to_magic(), 0x59504f43);
239 assert_eq!(PatchType::Bsd0.to_magic(), 0x30445342);
240
241 assert_eq!(PatchType::from_magic(0x59504f43).unwrap(), PatchType::Copy);
242 assert_eq!(PatchType::from_magic(0x30445342).unwrap(), PatchType::Bsd0);
243
244 assert!(PatchType::from_magic(0xDEADBEEF).is_err());
245 }
246
247 #[test]
248 fn test_header_size() {
249 assert_eq!(PatchHeader::HEADER_SIZE, 64);
250 }
251}