1use crate::error::{format_err, Error, Result};
16use crate::stream;
17
18#[derive(Debug, Clone)]
20pub struct Entry {
21 pub name: String,
23 pub offset: usize,
25 pub size: usize,
27}
28
29impl Entry {
30 pub fn kind(&self) -> EntryKind {
32 if self.name.ends_with(".index") {
33 EntryKind::Index
34 } else if self.name.ends_with(".str") || self.name.ends_with(".sav") {
35 EntryKind::RawStream
37 } else {
38 EntryKind::Compressed
39 }
40 }
41
42 pub fn key(&self) -> String {
44 normalise(&self.name)
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum EntryKind {
51 Index,
53 RawStream,
55 Compressed,
57}
58
59#[derive(Debug, Clone)]
61pub struct Header {
62 pub version: u32,
64 pub game_version: u32,
68 pub user_version: u32,
70 pub priority: u32,
72 pub skip: u32,
76 pub icon: (u32, u32, Vec<u8>),
79 pub preview: Vec<u8>,
82 pub can_override: bool,
84 pub crc: u32,
86 pub name: String,
88 pub author: String,
90 pub description: String,
92}
93
94pub struct Package {
96 data: Vec<u8>,
97 pub header: Header,
99 pub entries: Vec<Entry>,
101}
102
103pub fn normalise(name: &str) -> String {
105 name.replace('\\', "/").to_ascii_lowercase()
106}
107
108struct Cursor<'a> {
109 d: &'a [u8],
110 p: usize,
111}
112
113impl<'a> Cursor<'a> {
114 fn take(&mut self, n: usize) -> Result<&'a [u8]> {
115 let s = self.d.get(self.p..self.p + n).ok_or(Error::Truncated {
116 at: self.p,
117 wanted: n,
118 })?;
119 self.p += n;
120 Ok(s)
121 }
122 fn u32(&mut self) -> Result<u32> {
123 Ok(u32::from_le_bytes(self.take(4)?.try_into().unwrap()))
124 }
125 fn cstr(&mut self, n: usize) -> Result<String> {
126 let b = self.take(n)?;
127 let end = b.iter().position(|&c| c == 0).unwrap_or(b.len());
128 Ok(String::from_utf8(b[..end].to_vec())
129 .unwrap_or_else(|_| b[..end].iter().map(|&c| c as char).collect()))
130 }
131 fn wstr(&mut self, n: usize) -> Result<String> {
133 let b = self.take(n)?;
134 let units: Vec<u16> = b
135 .chunks_exact(2)
136 .map(|c| u16::from_le_bytes([c[0], c[1]]))
137 .take_while(|&u| u != 0)
138 .collect();
139 Ok(String::from_utf16_lossy(&units))
140 }
141 fn string(&mut self, version: u32) -> Result<String> {
143 let n = self.u32()? as usize;
144 if version < 3 {
145 self.wstr(n)
146 } else {
147 self.cstr(n)
148 }
149 }
150}
151
152impl Package {
153 pub fn parse(data: Vec<u8>) -> Result<Package> {
155 let head = Cursor { d: &data, p: 0 }.u32()?;
156 if head >> 31 == 0 {
157 return Err(Error::Unsupported("legacy package header".into()));
158 }
159 let toc_at = (head & 0x7FFF_FFFF) as usize;
160 let toc_size = Cursor {
161 d: &data,
162 p: toc_at,
163 }
164 .u32()? as usize;
165 let toc_bytes = data
166 .get(toc_at + 4..toc_at + 4 + toc_size)
167 .ok_or(Error::Truncated {
168 at: toc_at + 4,
169 wanted: toc_size,
170 })?;
171 let toc = stream::decode(toc_bytes)?;
172 let mut c = Cursor { d: &toc, p: 0 };
173
174 let count = c.u32()? as usize;
178 let version = c.u32()?;
179 if version == 0 {
180 return Err(Error::Unsupported(format!("package version {version}")));
181 }
182 let game_version = c.u32()?;
183 let user_version = c.u32()?;
184 let priority = c.u32()?;
185 let skip = c.u32()?;
186 let can_override = c.take(1)?[0] != 0;
187 let crc = c.u32()?;
188 let (w, h) = (c.u32()? as usize, c.u32()? as usize);
189 let icon = c.take(w * h * 4)?.to_vec();
190 let preview = if version > 1 {
191 let n = c.u32()? as usize;
192 if n > c.d.len() - c.p {
195 Vec::new()
196 } else {
197 c.take(n)?.to_vec()
198 }
199 } else {
200 Vec::new()
201 };
202 let mut strings = [String::new(), String::new(), String::new()];
203 for s in &mut strings {
204 *s = c.string(version)?;
205 }
206 let [name, author, description] = strings;
207
208 let mut entries = Vec::with_capacity(count);
209 for _ in 0..count {
210 let name = c.string(version)?;
211 let offset = c.u32()? as usize;
212 let size = c.u32()? as usize;
213 if offset + size > data.len() {
214 return format_err(format!("entry {name} extends past end of package"));
215 }
216 entries.push(Entry { name, offset, size });
217 }
218 if c.p != toc.len() {
219 return format_err(format!("{} unparsed TOC bytes", toc.len() - c.p));
220 }
221 let header = Header {
222 version,
223 game_version,
224 user_version,
225 priority,
226 skip,
227 icon: (w as u32, h as u32, icon),
228 preview,
229 can_override,
230 crc,
231 name,
232 author,
233 description,
234 };
235 Ok(Package {
236 data,
237 header,
238 entries,
239 })
240 }
241
242 pub fn raw(&self, entry: &Entry) -> &[u8] {
244 &self.data[entry.offset..entry.offset + entry.size]
245 }
246
247 pub fn read(&self, entry: &Entry) -> Result<Vec<u8>> {
249 match entry.kind() {
250 EntryKind::Compressed => stream::decode(self.raw(entry)),
251 EntryKind::RawStream | EntryKind::Index => Ok(self.raw(entry).to_vec()),
252 }
253 }
254}
255
256#[derive(Default)]
258pub struct Archive {
259 packages: Vec<Package>,
260 index: std::collections::HashMap<String, (usize, usize)>,
261 pub user_packs: usize,
265}
266
267impl Archive {
268 pub fn add(&mut self, pkg: Package) {
271 let pi = self.packages.len();
272 for (ei, e) in pkg.entries.iter().enumerate() {
273 if e.kind() == EntryKind::Index {
274 continue;
275 }
276 let key = e.key();
277 let replace = match self.index.get(&key) {
278 Some(&(p, _)) => pkg.header.priority >= self.packages[p].header.priority,
279 None => true,
280 };
281 if replace {
282 self.index.insert(key, (pi, ei));
283 }
284 }
285 self.packages.push(pkg);
286 }
287
288 pub fn add_mod(&mut self, pkg: Package) {
296 let pi = self.packages.len();
297 for (ei, e) in pkg.entries.iter().enumerate() {
298 if e.kind() == EntryKind::Index {
299 continue;
300 }
301 let key = e.key();
302 let replace = match self.index.get(&key) {
303 Some(&(p, _)) => self.packages[p].header.can_override,
304 None => true,
305 };
306 if replace {
307 self.index.insert(key, (pi, ei));
308 }
309 }
310 self.packages.push(pkg);
311 }
312
313 pub fn package_count(&self) -> usize {
315 self.packages.len()
316 }
317
318 pub fn remove_after(&mut self, keep: usize) {
321 let packages: Vec<Package> = std::mem::take(&mut self.packages);
322 self.index.clear();
323 for p in packages.into_iter().take(keep) {
324 self.add(p);
325 }
326 }
327
328 pub fn package_of(&self, name: &str) -> Option<&Package> {
330 self.index
331 .get(&normalise(name))
332 .map(|&(p, _)| &self.packages[p])
333 }
334
335 pub fn len(&self) -> usize {
337 self.index.len()
338 }
339
340 pub fn is_empty(&self) -> bool {
342 self.index.is_empty()
343 }
344
345 pub fn names(&self) -> Vec<&str> {
347 let mut v: Vec<&str> = self
348 .index
349 .values()
350 .map(|&(p, e)| self.packages[p].entries[e].name.as_str())
351 .collect();
352 v.sort_unstable();
353 v
354 }
355
356 pub fn get(&self, name: &str) -> Option<&Entry> {
358 self.index
359 .get(&normalise(name))
360 .map(|&(p, e)| &self.packages[p].entries[e])
361 }
362
363 pub fn read(&self, name: &str) -> Result<Vec<u8>> {
365 let &(p, e) = self
366 .index
367 .get(&normalise(name))
368 .ok_or_else(|| Error::Format(format!("no such resource: {name}")))?;
369 self.packages[p].read(&self.packages[p].entries[e])
370 }
371
372 pub fn read_ref(&self, reference: &str) -> Result<Vec<u8>> {
375 self.read(&ref_to_entry(reference))
376 }
377}
378
379pub fn ref_to_entry(reference: &str) -> String {
381 match reference.rsplit_once(':') {
382 Some((path, object)) => {
383 let stem = path.strip_suffix(".rsc").unwrap_or(path);
384 format!("{stem}_{object}.crs")
385 }
386 None => reference.to_string(),
387 }
388}
389
390#[cfg(test)]
391mod tests {
392 use super::*;
393
394 #[test]
395 fn reference_names() {
396 assert_eq!(
397 ref_to_entry(r"Models\Animals\Sheep\MaterialInstance\Sheep.rsc:resource"),
398 r"Models\Animals\Sheep\MaterialInstance\Sheep_resource.crs"
399 );
400 }
401}