wow_wmo/
chunk.rs

1use crate::error::{Result, WmoError};
2use crate::types::ChunkId;
3use std::io::{Read, Seek, SeekFrom, Write};
4
5/// Helper function to handle `read_exact` operations with proper EOF handling
6fn read_exact_or_eof<R: Read>(reader: &mut R, buf: &mut [u8]) -> Result<()> {
7    match reader.read_exact(buf) {
8        Ok(()) => Ok(()),
9        Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => Err(WmoError::UnexpectedEof),
10        Err(e) => Err(WmoError::Io(e)),
11    }
12}
13
14/// Represents a chunk header in a WMO file
15#[derive(Debug, Clone, Copy)]
16pub struct ChunkHeader {
17    /// 4-byte chunk identifier (magic)
18    pub id: ChunkId,
19    /// Size of the chunk data in bytes (not including this header)
20    pub size: u32,
21}
22
23impl ChunkHeader {
24    /// Size of a chunk header in bytes
25    pub const SIZE: usize = 8;
26
27    /// Read a chunk header from a reader
28    pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
29        let mut id_bytes = [0u8; 4];
30        read_exact_or_eof(reader, &mut id_bytes)?;
31
32        // WMO files store chunk IDs in little-endian order, so we need to reverse
33        // the bytes to get the correct ASCII representation
34        id_bytes.reverse();
35
36        let mut size_bytes = [0u8; 4];
37        read_exact_or_eof(reader, &mut size_bytes)?;
38        let size = u32::from_le_bytes(size_bytes);
39
40        Ok(Self {
41            id: ChunkId(id_bytes),
42            size,
43        })
44    }
45
46    /// Write a chunk header to a writer
47    pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
48        // WMO files store chunk IDs in little-endian order, so we need to reverse
49        // the bytes when writing
50        let mut id_bytes = self.id.0;
51        id_bytes.reverse();
52        writer.write_all(&id_bytes)?;
53        writer.write_all(&self.size.to_le_bytes())?;
54        Ok(())
55    }
56}
57
58/// Represents a chunk in a WMO file
59#[derive(Debug)]
60pub struct Chunk {
61    /// Chunk header
62    pub header: ChunkHeader,
63    /// Position of the chunk data in the file
64    pub data_position: u64,
65}
66
67impl Chunk {
68    /// Read a chunk from a reader
69    pub fn read<R: Read + Seek>(reader: &mut R) -> Result<Self> {
70        let header = ChunkHeader::read(reader)?;
71        let data_position = reader.stream_position()?;
72
73        // Skip the chunk data for now
74        reader.seek(SeekFrom::Current(header.size as i64))?;
75
76        Ok(Self {
77            header,
78            data_position,
79        })
80    }
81
82    /// Read a specific chunk from a reader, checking the expected ID
83    pub fn read_expected<R: Read + Seek>(reader: &mut R, expected_id: ChunkId) -> Result<Self> {
84        let chunk = Self::read(reader)?;
85
86        if chunk.header.id != expected_id {
87            return Err(WmoError::InvalidMagic {
88                expected: *expected_id.as_bytes(),
89                found: chunk.header.id.0,
90            });
91        }
92
93        Ok(chunk)
94    }
95
96    /// Seek to the data portion of this chunk
97    pub fn seek_to_data<S: Seek>(&self, seeker: &mut S) -> Result<()> {
98        seeker.seek(SeekFrom::Start(self.data_position))?;
99        Ok(())
100    }
101
102    /// Read the data of this chunk into a buffer
103    pub fn read_data<R: Read + Seek>(&self, reader: &mut R) -> Result<Vec<u8>> {
104        self.seek_to_data(reader)?;
105
106        let mut data = vec![0; self.header.size as usize];
107        reader.read_exact(&mut data)?;
108
109        Ok(data)
110    }
111}