1use std::{
2 io::{self, Read as _, Seek as _},
3 rc::Rc,
4};
5
6use binrw::BinReaderExt;
7use crc::{self, Digest};
8
9use crate::{
10 Entry, Error,
11 algos::EntryReader,
12 structs::{
13 v1,
14 v5::{self, EntryBinReadArgs},
15 },
16};
17
18const VERIFICATION_CHUNK_SIZE: usize = 1 << 20 ;
19const CRC16: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_ARC);
20
21pub struct VerifyingIterator<R: io::Read + io::Seek> {
22 pub(crate) next_offset: u64,
23 pub(crate) stack: Vec<u32>,
24 pub(crate) v1: bool,
25 pub(crate) _next_file_index: usize,
26
27 pub(crate) reader: Rc<R>,
28}
29
30impl<R: io::Read + io::Seek> Iterator for VerifyingIterator<R> {
31 type Item = Entry;
32
33 fn next(&mut self) -> Option<Self::Item> {
34 let reader = unsafe { Rc::get_mut_unchecked(&mut self.reader) };
35
36 match self.stack.last_mut() {
37 None => return None,
38 Some(0) => {
39 self.stack.pop();
40
41 return if self.stack.is_empty() {
42 None
43 } else {
44 Some(Entry::DirectoryEnd(reader.stream_position().unwrap()))
45 };
46 }
47 Some(d) => *d -= 1,
48 }
49
50 let Ok(entry_offset) = reader.seek(io::SeekFrom::Start(self.next_offset)) else {
51 log::warn!("Failed seeking to next archive entry");
52 return None;
53 };
54
55 if self.v1 {
56 let Ok(entry) = reader.read_be::<v1::Entry>() else {
57 return None;
58 };
59
60 let Ok(payload_offset) = reader.stream_position() else {
61 return None;
62 };
63
64 match entry {
65 v1::Entry::Directory(dir) => {
66 self.next_offset = payload_offset;
67 self.stack.push(u32::MAX);
68
69 Some(Entry::Directory(dir.into()))
70 }
71 v1::Entry::DirectoryEnd => {
72 self.next_offset = payload_offset;
73 self.stack.pop();
74
75 Some(Entry::DirectoryEnd(payload_offset))
76 }
77 v1::Entry::File(file) => {
78 self.next_offset = payload_offset
79 + file.data_compressed_size as u64
80 + file.rsrc_compressed_size as u64;
81
82 Some(Entry::File(file.into()))
83 }
84 }
85 } else {
86 let Ok(entry) = reader.read_be_args::<v5::Entry>(
87 EntryBinReadArgs::builder().offset(entry_offset).finalize(),
88 ) else {
89 return None;
90 };
91
92 let Ok(payload_offset) = reader.stream_position() else {
93 return None;
94 };
95
96 match entry {
97 v5::Entry::Directory(dir) => {
98 if dir.marks_end() {
99 self.next_offset = payload_offset;
100 self.next();
101 }
102
103 self.stack.push(dir.child_count);
104 self.next_offset = dir.first_child_offset;
105
106 Some(Entry::Directory(dir.into()))
107 }
108 v5::Entry::File(mut file) => {
109 file.payload_offset = reader.stream_position().unwrap();
110 self.next_offset = file.next_entry_offset as u64;
111
112 Some(Entry::File(file.into()))
113 }
114 }
115 }
116 }
117}
118
119pub struct VerifyingEntryReader<'a, R: io::Read + io::Seek> {
120 inner: EntryReader<'a, R>,
121 checksum: u16,
122 digest: Digest<'a, u16>,
123 skip: bool,
124 empty: bool,
125 invalid: bool,
126}
127
128impl<'a, R: io::Seek + io::Read> VerifyingEntryReader<'a, R> {
129 pub(crate) fn new(mut inner: EntryReader<'a, R>, checksum: u16, skip: bool) -> Self {
130 let empty_stream = inner.stream_len().unwrap() == 0;
131
132 Self {
133 invalid: false,
134 empty: empty_stream,
135 skip,
136 inner,
137 checksum,
138 digest: CRC16.digest(),
139 }
140 }
141}
142
143impl<'a, R: io::Read + io::Seek> VerifyingEntryReader<'a, R> {
144 pub fn slurp(mut self) -> Result<(), Error> {
146 let mut chunk = vec![0u8; VERIFICATION_CHUNK_SIZE];
147 loop {
148 match self.read(&mut chunk)? {
149 s if s < VERIFICATION_CHUNK_SIZE => {
150 log::info!("slurpded: {s}");
151 return Ok(());
152 }
153 _ => {
154 continue;
155 }
156 }
157 }
158 }
159}
160
161impl<'a, R: io::Read + io::Seek> io::Read for VerifyingEntryReader<'a, R> {
162 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
163 if self.empty {
164 return Ok(0);
165 }
166
167 if self.invalid {
168 return Err(io::Error::other(Error::ChecksumMismatch(
169 crate::error::ChecksumLocation::DataStream,
170 )));
171 }
172
173 let size = self.inner.read(buf)?;
174 if self.skip {
175 if self.inner.ended()?
176 && let EntryReader::Arsenic { reader, .. } = &mut self.inner
177 {
178 if !reader.is_checksum_valid() {
179 self.invalid = true;
180 return Err(io::Error::other(Error::ChecksumMismatch(
181 crate::error::ChecksumLocation::DataStream,
182 )));
183 } else {
184 self.empty = true;
185 }
186 }
187
188 return Ok(size);
189 }
190 self.digest.update(&buf[0..size]);
191
192 if self.inner.ended()? {
193 self.digest.update(&[
194 (self.checksum & 0xFF) as u8,
195 ((self.checksum >> 8) & 0xFF) as u8,
196 ]);
197
198 log::info!(
199 "Checking CRC16 checksum 0x{:04x} against 0x{:04x}",
200 self.checksum,
201 self.digest.clone().finalize(),
202 );
203
204 if self.digest.clone().finalize() != 0 {
205 log::info!("Checksum is invalid");
206 self.invalid = true;
207 return Err(io::Error::other(Error::ChecksumMismatch(
208 crate::error::ChecksumLocation::DataStream,
209 )));
210 }
211
212 log::info!("Checksum is valid");
213 self.skip = true;
214 }
215
216 Ok(size)
217 }
218}