1use crate::replay::checksum;
7use std::{collections::BTreeMap, sync::Arc};
8const MAGIC: &[u8; 8] = b"FXPK0001";
9const MAX_BYTES: usize = 64 * 1024 * 1024;
10const MAX_FILES: usize = 4096;
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum AssetError {
13 Path,
14 Limit,
15 Format,
16 Checksum,
17}
18#[derive(Clone, Default)]
19pub struct Bundle {
20 files: BTreeMap<String, Arc<[u8]>>,
21 bytes: usize,
22}
23impl Bundle {
24 pub fn insert(&mut self, path: &str, bytes: &[u8]) -> Result<(), AssetError> {
25 if !valid_path(path) {
26 return Err(AssetError::Path);
27 }
28 let old = self.files.get(path).map_or(0, |v| v.len());
29 let total = (self.bytes - old)
30 .checked_add(bytes.len())
31 .ok_or(AssetError::Limit)?;
32 if total > MAX_BYTES
33 || self.files.len() >= MAX_FILES && old == 0 && !self.files.contains_key(path)
34 {
35 return Err(AssetError::Limit);
36 }
37 self.files.insert(path.into(), Arc::from(bytes));
38 self.bytes = total;
39 Ok(())
40 }
41 pub fn get(&self, path: &str) -> Option<Arc<[u8]>> {
42 self.files.get(path).cloned()
43 }
44 pub fn names(&self) -> impl Iterator<Item = &str> {
45 self.files.keys().map(String::as_str)
46 }
47 pub fn encode(&self) -> Vec<u8> {
48 let mut out = MAGIC.to_vec();
49 out.extend((self.files.len() as u32).to_le_bytes());
50 for (name, raw) in &self.files {
51 let mut stored_len = 0;
52 let mut offset = 0;
53 while offset < raw.len() {
54 let mut count = 1;
55 while count < 255
56 && offset + count < raw.len()
57 && raw[offset + count] == raw[offset]
58 {
59 count += 1;
60 }
61 stored_len += 2;
62 if stored_len >= raw.len() {
63 break;
64 }
65 offset += count;
66 }
67 let compressed = offset == raw.len() && stored_len < raw.len();
68 let mut rle = Vec::new();
69 if compressed {
70 rle.reserve_exact(stored_len);
71 let mut offset = 0;
72 while offset < raw.len() {
73 let mut count = 1;
74 while count < 255
75 && offset + count < raw.len()
76 && raw[offset + count] == raw[offset]
77 {
78 count += 1;
79 }
80 rle.extend([count as u8, raw[offset]]);
81 offset += count;
82 }
83 }
84 let stored: &[u8] = if compressed { &rle } else { raw };
85 out.extend((name.len() as u16).to_le_bytes());
86 out.extend(name.as_bytes());
87 out.push(u8::from(compressed));
88 out.extend((raw.len() as u32).to_le_bytes());
89 out.extend((stored.len() as u32).to_le_bytes());
90 out.extend(checksum(raw).to_le_bytes());
91 out.extend(stored);
92 }
93 out.extend(checksum(&out).to_le_bytes());
94 out
95 }
96 pub fn decode(data: &[u8]) -> Result<Self, AssetError> {
97 if data.len() < 20 || data.len() > MAX_BYTES + MAX_FILES * 280 || &data[..8] != MAGIC {
98 return Err(AssetError::Format);
99 }
100 let end = data.len() - 8;
101 if checksum(&data[..end]) != u64::from_le_bytes(data[end..].try_into().unwrap()) {
102 return Err(AssetError::Checksum);
103 }
104 let mut reader = Reader {
105 bytes: &data[8..end],
106 at: 0,
107 };
108 let count = reader.u32()? as usize;
109 if count > MAX_FILES {
110 return Err(AssetError::Limit);
111 }
112 let mut result = Self::default();
113 for _ in 0..count {
114 let len = reader.u16()? as usize;
115 let name = std::str::from_utf8(reader.take(len)?).map_err(|_| AssetError::Path)?;
116 if !valid_path(name) || result.files.contains_key(name) {
117 return Err(AssetError::Path);
118 }
119 let mode = reader.take(1)?[0];
120 let raw_len = reader.u32()? as usize;
121 let stored_len = reader.u32()? as usize;
122 let hash = reader.u64()?;
123 if raw_len > MAX_BYTES - result.bytes || stored_len > MAX_BYTES {
124 return Err(AssetError::Limit);
125 }
126 let stored = reader.take(stored_len)?;
127 let raw: Arc<[u8]> = match mode {
128 0 if raw_len == stored_len => {
129 if checksum(stored) != hash {
130 return Err(AssetError::Checksum);
131 }
132 Arc::from(stored)
133 }
134 1 if stored_len.is_multiple_of(2) => {
135 let mut expanded = 0_usize;
137 for pair in stored.as_chunks::<2>().0 {
138 let run = usize::from(pair[0]);
139 if run == 0 {
140 return Err(AssetError::Format);
141 }
142 expanded = expanded.checked_add(run).ok_or(AssetError::Limit)?;
143 if expanded > raw_len {
144 return Err(AssetError::Format);
145 }
146 }
147 if expanded != raw_len {
148 return Err(AssetError::Format);
149 }
150 let mut raw = Vec::with_capacity(raw_len);
151 for pair in stored.as_chunks::<2>().0 {
152 raw.resize(raw.len() + usize::from(pair[0]), pair[1]);
153 }
154 if checksum(&raw) != hash {
155 return Err(AssetError::Checksum);
156 }
157 Arc::from(raw)
158 }
159 _ => return Err(AssetError::Format),
160 };
161 result.bytes += raw.len();
162 result.files.insert(name.into(), raw);
163 }
164 if reader.at != reader.bytes.len() {
165 return Err(AssetError::Format);
166 }
167 Ok(result)
168 }
169}
170fn valid_path(path: &str) -> bool {
171 !path.is_empty()
172 && path.len() <= 240
173 && path.split('/').all(|part| {
174 !part.is_empty()
175 && part != "."
176 && part != ".."
177 && part
178 .bytes()
179 .all(|c| c.is_ascii_alphanumeric() || b"._-".contains(&c))
180 })
181}
182struct Reader<'a> {
183 bytes: &'a [u8],
184 at: usize,
185}
186impl<'a> Reader<'a> {
187 fn take(&mut self, len: usize) -> Result<&'a [u8], AssetError> {
188 let end = self.at.checked_add(len).ok_or(AssetError::Format)?;
189 let bytes = self.bytes.get(self.at..end).ok_or(AssetError::Format)?;
190 self.at = end;
191 Ok(bytes)
192 }
193 fn u16(&mut self) -> Result<u16, AssetError> {
194 Ok(u16::from_le_bytes(self.take(2)?.try_into().unwrap()))
195 }
196 fn u32(&mut self) -> Result<u32, AssetError> {
197 Ok(u32::from_le_bytes(self.take(4)?.try_into().unwrap()))
198 }
199 fn u64(&mut self) -> Result<u64, AssetError> {
200 Ok(u64::from_le_bytes(self.take(8)?.try_into().unwrap()))
201 }
202}
203#[derive(Default)]
205pub struct LiveBundle {
206 current: Arc<Bundle>,
207 generation: u64,
208}
209impl LiveBundle {
210 pub fn current(&self) -> Arc<Bundle> {
211 self.current.clone()
212 }
213 pub fn generation(&self) -> u64 {
214 self.generation
215 }
216 pub fn reload(&mut self, bytes: &[u8]) -> Result<(), AssetError> {
217 let generation = self.generation.checked_add(1).ok_or(AssetError::Limit)?;
218 let next = Bundle::decode(bytes)?;
219 self.current = Arc::new(next);
220 self.generation = generation;
221 Ok(())
222 }
223}
224#[cfg(test)]
225mod tests {
226 use super::*;
227 #[test]
228 fn deterministic_pack_roundtrip_compression_and_failed_reload_preserves_readers() {
229 let mut a = Bundle::default();
230 a.insert("textures/flat.rgba", &[37; 10000]).unwrap();
231 a.insert("meta/version", b"v1").unwrap();
232 let bytes = a.encode();
233 assert!(bytes.len() < 300);
234 let mut live = LiveBundle::default();
235 live.reload(&bytes).unwrap();
236 let old = live.current();
237 assert_eq!(&*old.get("meta/version").unwrap(), b"v1");
238 assert_eq!(Bundle::decode(&bytes).unwrap().encode(), bytes);
239 let mut bad = bytes.clone();
240 bad[20] ^= 1;
241 assert!(live.reload(&bad).is_err());
242 assert_eq!(live.generation(), 1);
243 a.insert("meta/version", b"v2").unwrap();
244 live.reload(&a.encode()).unwrap();
245 assert_eq!(&*old.get("meta/version").unwrap(), b"v1");
246 assert_eq!(&*live.current().get("meta/version").unwrap(), b"v2");
247 for path in ["../escape", "/root", "a//b", "a/./b", "a\\b", ""] {
248 assert_eq!(a.insert(path, b""), Err(AssetError::Path));
249 }
250 for end in 0..bytes.len() {
251 assert!(Bundle::decode(&bytes[..end]).is_err());
252 }
253 }
254}
255
256#[cfg(test)]
257mod malformed_tests {
258 use super::*;
259 fn pack(records: &[(&str, u8, u32, &[u8], u64)]) -> Vec<u8> {
260 let mut bytes = MAGIC.to_vec();
261 bytes.extend((records.len() as u32).to_le_bytes());
262 for &(name, mode, raw_len, stored, hash) in records {
263 bytes.extend((name.len() as u16).to_le_bytes());
264 bytes.extend(name.as_bytes());
265 bytes.push(mode);
266 bytes.extend(raw_len.to_le_bytes());
267 bytes.extend((stored.len() as u32).to_le_bytes());
268 bytes.extend(hash.to_le_bytes());
269 bytes.extend(stored);
270 }
271 bytes.extend(checksum(&bytes).to_le_bytes());
272 bytes
273 }
274 #[test]
275 fn checksum_valid_malformed_runs_are_rejected_before_expansion() {
276 for (raw_len, stored) in [
277 (2, &[3, 7][..]),
278 (3, &[0, 7][..]),
279 (3, &[2, 7][..]),
280 (3, &[3][..]),
281 (MAX_BYTES as u32, &[1, 7][..]),
282 ] {
283 let bytes = pack(&[("a", 1, raw_len, stored, 0)]);
284 assert!(matches!(Bundle::decode(&bytes), Err(AssetError::Format)));
285 }
286 assert!(matches!(
287 Bundle::decode(&pack(&[("a", 9, 0, &[], 0)])),
288 Err(AssetError::Format)
289 ));
290 }
291 #[test]
292 fn rejects_duplicate_paths_trailing_bytes_and_inner_checksum() {
293 let hash = checksum(b"a");
294 assert!(matches!(
295 Bundle::decode(&pack(&[("a", 0, 1, b"a", hash), ("a", 0, 1, b"a", hash)])),
296 Err(AssetError::Path)
297 ));
298 assert!(matches!(
299 Bundle::decode(&pack(&[("../a", 0, 1, b"a", hash)])),
300 Err(AssetError::Path)
301 ));
302 assert!(matches!(
303 Bundle::decode(&pack(&[("a", 0, 1, b"a", hash ^ 1)])),
304 Err(AssetError::Checksum)
305 ));
306 let mut bytes = pack(&[]);
307 bytes.truncate(bytes.len() - 8);
308 bytes.push(0);
309 bytes.extend(checksum(&bytes).to_le_bytes());
310 assert!(matches!(Bundle::decode(&bytes), Err(AssetError::Format)));
311 }
312 #[test]
313 fn raw_and_compressed_selection_roundtrip_and_file_cap() {
314 let mut bundle = Bundle::default();
315 bundle.insert("empty", b"").unwrap();
316 bundle.insert("raw", b"abcdef").unwrap();
317 bundle.insert("short", b"aaa").unwrap();
318 bundle.insert("runs", &[4; 256]).unwrap();
319 let bytes = bundle.encode();
320 let decoded = Bundle::decode(&bytes).unwrap();
321 assert_eq!(decoded.encode(), bytes);
322 for i in bundle.files.len()..MAX_FILES {
323 bundle.insert(&format!("file{i}"), b"").unwrap();
324 }
325 assert_eq!(bundle.insert("one-too-many", b""), Err(AssetError::Limit));
326 assert!(bundle.insert("empty", b"replacement").is_ok());
327 }
328}