1use std::collections::HashMap;
9use std::fs::File;
10use std::path::Path;
11
12use memmap2::Mmap;
13
14use crate::error::{CascError, Result};
15use crate::util::io::read_le_u32;
16
17pub const DATA_HEADER_SIZE: usize = 30;
19
20#[derive(Debug, Clone)]
22pub struct DataHeader {
23 pub ekey_hash: [u8; 16],
25 pub size: u32,
27 pub flags: [u8; 2],
29}
30
31pub struct DataStore {
33 mmaps: HashMap<u32, Mmap>,
35}
36
37pub fn parse_data_header(data: &[u8]) -> Result<DataHeader> {
44 if data.len() < DATA_HEADER_SIZE {
45 return Err(CascError::InvalidFormat(format!(
46 "data header too short: {} bytes (need {})",
47 data.len(),
48 DATA_HEADER_SIZE
49 )));
50 }
51
52 let mut ekey_hash = [0u8; 16];
54 for i in 0..16 {
55 ekey_hash[i] = data[15 - i];
56 }
57
58 let size = read_le_u32(&data[0x10..0x14]);
59 let flags = [data[0x14], data[0x15]];
60
61 Ok(DataHeader {
62 ekey_hash,
63 size,
64 flags,
65 })
66}
67
68fn parse_data_filename(name: &str) -> Option<u32> {
71 let suffix = name.strip_prefix("data.")?;
72 suffix.parse::<u32>().ok()
73}
74
75impl DataStore {
76 pub fn open(data_dir: &Path) -> Result<Self> {
78 let pattern = data_dir.join("data.*");
79 let pattern_str = pattern.to_string_lossy().to_string();
80
81 let mut mmaps = HashMap::new();
82
83 for path in glob::glob(&pattern_str)
84 .map_err(|e| CascError::InvalidFormat(format!("glob error: {e}")))?
85 {
86 let path = path.map_err(|e| CascError::Io(e.into_error()))?;
87 let fname = match path.file_name().and_then(|f| f.to_str()) {
88 Some(f) => f.to_owned(),
89 None => continue,
90 };
91
92 if let Some(archive_num) = parse_data_filename(&fname) {
93 let file = File::open(&path)?;
94 let mmap = unsafe { Mmap::map(&file)? };
95 mmaps.insert(archive_num, mmap);
96 }
97 }
98
99 Ok(Self { mmaps })
100 }
101
102 pub fn read_entry(&self, archive_number: u32, offset: u64, size: u32) -> Result<&[u8]> {
104 let raw = self.read_raw(archive_number, offset, size)?;
105 if raw.len() < DATA_HEADER_SIZE {
106 return Err(CascError::InvalidFormat(
107 "data entry too small to contain header".into(),
108 ));
109 }
110 Ok(&raw[DATA_HEADER_SIZE..])
111 }
112
113 pub fn read_raw(&self, archive_number: u32, offset: u64, size: u32) -> Result<&[u8]> {
115 let mmap = self.mmaps.get(&archive_number).ok_or_else(|| {
116 CascError::InvalidFormat(format!("data.{:03} not found", archive_number))
117 })?;
118
119 let start = offset as usize;
120 let end = start + size as usize;
121
122 if end > mmap.len() {
123 return Err(CascError::InvalidFormat(format!(
124 "data.{:03} read out of bounds: offset={}, size={}, file_len={}",
125 archive_number,
126 offset,
127 size,
128 mmap.len()
129 )));
130 }
131
132 Ok(&mmap[start..end])
133 }
134}
135
136#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn parse_data_header_valid() {
146 let mut header = [0u8; 30];
147 for (i, slot) in header.iter_mut().enumerate().take(16) {
149 *slot = (16 - i) as u8;
150 }
151 header[0x10..0x14].copy_from_slice(&1000u32.to_le_bytes());
153 header[0x14] = 0;
155 header[0x15] = 0;
156
157 let dh = parse_data_header(&header).unwrap();
158 assert_eq!(dh.ekey_hash[0], 1);
160 assert_eq!(dh.ekey_hash[15], 16);
161 assert_eq!(dh.size, 1000);
162 assert_eq!(dh.flags, [0, 0]);
163 }
164
165 #[test]
166 fn data_header_size_includes_header() {
167 let mut header = [0u8; 30];
168 header[0x10..0x14].copy_from_slice(&30u32.to_le_bytes());
169 let dh = parse_data_header(&header).unwrap();
170 assert_eq!(dh.size, 30);
171 }
172
173 #[test]
174 fn data_header_too_short() {
175 let header = [0u8; 10];
176 assert!(parse_data_header(&header).is_err());
177 }
178}