wow_m2/chunks/
infrastructure.rs1use std::io::{Read, Seek, SeekFrom};
8
9use crate::error::{M2Error, Result};
10use crate::io_ext::ReadExt;
11
12#[derive(Debug, Clone)]
14pub struct ChunkHeader {
15 pub magic: [u8; 4],
17 pub size: u32,
19}
20
21impl ChunkHeader {
22 pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
24 let mut magic = [0u8; 4];
25 reader.read_exact(&mut magic)?;
26 let size = reader.read_u32_le()?;
27
28 Ok(ChunkHeader { magic, size })
29 }
30
31 pub fn magic_str(&self) -> String {
33 String::from_utf8_lossy(&self.magic).to_string()
34 }
35
36 pub fn has_magic(&self, magic: &[u8; 4]) -> bool {
38 &self.magic == magic
39 }
40}
41
42pub struct ChunkReader<R> {
44 inner: R,
45 chunk_start: u64,
46 chunk_size: u32,
47}
48
49impl<R: Read + Seek> ChunkReader<R> {
50 pub fn new(mut reader: R, header: ChunkHeader) -> Result<Self> {
53 let chunk_start = reader.stream_position()?;
54 Ok(ChunkReader {
55 inner: reader,
56 chunk_start,
57 chunk_size: header.size,
58 })
59 }
60
61 pub fn resolve_offset(&self, offset: u32) -> u64 {
63 self.chunk_start + offset as u64
64 }
65
66 pub fn chunk_position(&mut self) -> Result<u32> {
68 let current = self.inner.stream_position()?;
69 Ok((current - self.chunk_start) as u32)
70 }
71
72 pub fn remaining(&mut self) -> Result<u32> {
74 let pos = self.chunk_position()?;
75 Ok(self.chunk_size.saturating_sub(pos))
76 }
77
78 pub fn is_at_end(&mut self) -> Result<bool> {
80 Ok(self.remaining()? == 0)
81 }
82
83 pub fn seek_to_chunk_offset(&mut self, offset: u32) -> Result<()> {
85 if offset > self.chunk_size {
86 return Err(M2Error::ParseError(format!(
87 "Chunk offset {} exceeds chunk size {}",
88 offset, self.chunk_size
89 )));
90 }
91
92 let absolute_pos = self.chunk_start + offset as u64;
93 self.inner.seek(SeekFrom::Start(absolute_pos))?;
94 Ok(())
95 }
96
97 pub fn skip_to_end(&mut self) -> Result<()> {
99 let end_pos = self.chunk_start + self.chunk_size as u64;
100 self.inner.seek(SeekFrom::Start(end_pos))?;
101 Ok(())
102 }
103
104 pub fn chunk_size(&self) -> u32 {
106 self.chunk_size
107 }
108
109 pub fn inner(&mut self) -> &mut R {
111 &mut self.inner
112 }
113
114 pub fn current_position(&mut self) -> Result<u64> {
116 Ok(self.inner.stream_position()?)
117 }
118
119 pub fn seek_to_position(&mut self, position: u64) -> Result<()> {
121 self.inner.seek(SeekFrom::Start(position))?;
122 Ok(())
123 }
124}
125
126impl<R: Read + Seek> Read for ChunkReader<R> {
127 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
128 let remaining = match self.remaining() {
130 Ok(r) => r as usize,
131 Err(_) => {
132 return Err(std::io::Error::other("Failed to get remaining chunk bytes"));
133 }
134 };
135
136 if remaining == 0 {
137 return Ok(0);
138 }
139
140 let to_read = buf.len().min(remaining);
141 self.inner.read(&mut buf[..to_read])
142 }
143}
144
145impl<R: Seek> Seek for ChunkReader<R> {
146 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
147 match pos {
148 SeekFrom::Start(pos) => {
149 if pos > self.chunk_size as u64 {
151 return Err(std::io::Error::new(
152 std::io::ErrorKind::InvalidInput,
153 format!(
154 "Seek position {} exceeds chunk size {}",
155 pos, self.chunk_size
156 ),
157 ));
158 }
159 let absolute_pos = self.chunk_start + pos;
160 self.inner.seek(SeekFrom::Start(absolute_pos))
161 }
162 SeekFrom::End(offset) => {
163 let end_pos = self.chunk_start + self.chunk_size as u64;
165 let target = end_pos as i64 + offset;
166 if target < self.chunk_start as i64 || target > end_pos as i64 {
167 return Err(std::io::Error::new(
168 std::io::ErrorKind::InvalidInput,
169 "Seek position outside chunk bounds",
170 ));
171 }
172 self.inner.seek(SeekFrom::Start(target as u64))
173 }
174 SeekFrom::Current(offset) => {
175 let current = self.inner.stream_position()?;
177 let target = current as i64 + offset;
178 let chunk_end = self.chunk_start + self.chunk_size as u64;
179
180 if target < self.chunk_start as i64 || target > chunk_end as i64 {
181 return Err(std::io::Error::new(
182 std::io::ErrorKind::InvalidInput,
183 "Seek position outside chunk bounds",
184 ));
185 }
186 self.inner.seek(SeekFrom::Start(target as u64))
187 }
188 }
189 }
190
191 fn stream_position(&mut self) -> std::io::Result<u64> {
192 let absolute_pos = self.inner.stream_position()?;
194 Ok(absolute_pos - self.chunk_start)
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201 use std::io::Cursor;
202
203 #[test]
204 fn test_chunk_header_read() {
205 let data = b"TEST\x10\x00\x00\x00"; let mut cursor = Cursor::new(data);
207
208 let header = ChunkHeader::read(&mut cursor).unwrap();
209 assert_eq!(&header.magic, b"TEST");
210 assert_eq!(header.size, 16);
211 assert_eq!(header.magic_str(), "TEST");
212 assert!(header.has_magic(b"TEST"));
213 assert!(!header.has_magic(b"FAIL"));
214 }
215
216 #[test]
217 fn test_chunk_reader_basic() {
218 let data = b"TEST\x08\x00\x00\x00abcdefgh"; let mut cursor = Cursor::new(data);
220
221 let header = ChunkHeader::read(&mut cursor).unwrap();
222 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
223
224 assert_eq!(chunk_reader.chunk_size(), 8);
225 assert_eq!(chunk_reader.remaining().unwrap(), 8);
226 assert!(!chunk_reader.is_at_end().unwrap());
227
228 let mut buf = [0u8; 4];
229 assert_eq!(chunk_reader.read(&mut buf).unwrap(), 4);
230 assert_eq!(&buf, b"abcd");
231
232 assert_eq!(chunk_reader.remaining().unwrap(), 4);
233 assert_eq!(chunk_reader.chunk_position().unwrap(), 4);
234 }
235
236 #[test]
237 fn test_chunk_reader_seek() {
238 let data = b"TEST\x08\x00\x00\x00abcdefgh";
239 let mut cursor = Cursor::new(data);
240
241 let header = ChunkHeader::read(&mut cursor).unwrap();
242 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
243
244 chunk_reader.seek_to_chunk_offset(2).unwrap();
246 assert_eq!(chunk_reader.chunk_position().unwrap(), 2);
247
248 let mut buf = [0u8; 2];
249 assert_eq!(chunk_reader.read(&mut buf).unwrap(), 2);
250 assert_eq!(&buf, b"cd");
251
252 assert!(chunk_reader.seek_to_chunk_offset(10).is_err()); }
255
256 #[test]
257 fn test_chunk_reader_offset_resolution() {
258 let data = b"TEST\x08\x00\x00\x00abcdefgh";
259 let mut cursor = Cursor::new(data);
260
261 let header = ChunkHeader::read(&mut cursor).unwrap();
262 let chunk_reader = ChunkReader::new(cursor, header).unwrap();
263
264 assert_eq!(chunk_reader.resolve_offset(0), 8);
266 assert_eq!(chunk_reader.resolve_offset(4), 12);
267 }
268}