index_datamanip/
pigg.rs

1use std::collections::HashMap;
2use std::convert::AsRef;
3use std::fs::File;
4use std::io::Read;
5use std::path::Path;
6use std::str;
7
8use libflate::zlib;
9use memmap::MmapOptions;
10use nom::{bytes::complete::*, number::complete::*};
11
12use crate::error::{Error, Result};
13
14/// PIGG extractor
15#[derive(Debug)]
16pub struct Pigg {
17    files: HashMap<String, FileHeader>,
18    mmap: memmap::Mmap,
19}
20
21impl Pigg {
22    /// Opens and parses the given pigg file
23    pub fn new<P: AsRef<Path>>(path: P) -> Result<Pigg> {
24        let f = File::open(path)?;
25        // TODO: Document this
26        let mmap = unsafe { MmapOptions::new().map(&f)? };
27        let (mut remaining, header) = parse_header(&mmap[..])?;
28
29        let mut file_headers = vec![];
30        for _ in 0..header.num_files {
31            let (new_remaining, file_header) = parse_file_header(&remaining)?;
32            remaining = new_remaining;
33            file_headers.push(file_header);
34        }
35
36        let (_, string_pool) = StringPool::with(remaining)?;
37
38        let files: HashMap<_, _> = string_pool
39            .strings
40            .into_iter()
41            .zip(file_headers.into_iter())
42            .collect();
43
44        Ok(Pigg { files, mmap })
45    }
46
47    /// Gets the data chunk for the given path from the pigg file
48    pub fn get_data(&self, path: &str) -> Result<Vec<u8>> {
49        // TODO: Would this be better served as returning a Cow instead?
50        // Return a Cow::Borrowed in the case we can just point at the map,
51        // and return a Cow::Owned if we need to decompress
52        let header = self
53            .files
54            .get(path)
55            .ok_or_else(|| Error::ItemNotFound(path.into()))?;
56
57        // TODO: Only decompress if if header.size bigger than header.pack_size
58        let begin = header.offset as usize;
59        let end = begin + header.pack_size as usize;
60        let mut data = Vec::new();
61        let mut decoder = zlib::Decoder::new(&self.mmap[begin..end])?;
62        decoder.read_to_end(&mut data)?;
63
64        Ok(data)
65    }
66
67    // TODO: Add a method to list the "files"
68}
69
70fn parse_header(input: &[u8]) -> Result<(&[u8], Header)> {
71    let (input, _) = tag(&0x123u32.to_le_bytes())(input)?;
72
73    let (input, creator_version) = le_u16(input)?;
74    let (input, required_read_version) = le_u16(input)?;
75    let (input, archive_header_size) = le_u16(input)?;
76    let (input, file_header_size) = le_u16(input)?;
77    let (input, num_files) = le_u32(input)?;
78
79    Ok((
80        input,
81        Header {
82            creator_version,
83            required_read_version,
84            archive_header_size,
85            file_header_size,
86            num_files,
87        },
88    ))
89}
90
91#[derive(Debug)]
92struct Header {
93    creator_version: u16,
94    required_read_version: u16,
95    archive_header_size: u16,
96    file_header_size: u16,
97    num_files: u32,
98}
99
100fn parse_file_header(input: &[u8]) -> Result<(&[u8], FileHeader)> {
101    let (input, _) = tag(&0x3456u32.to_le_bytes())(input)?;
102    let (input, name_id) = le_i32(input)?;
103    let (input, size) = le_u32(input)?;
104    let (input, timestamp) = le_u32(input)?;
105    let (input, offset) = le_u32(input)?;
106    let (input, reserved) = le_u32(input)?;
107    let (input, header_data_id) = le_u32(input)?;
108    let (input, checksum) = le_u128(input)?;
109    let (input, pack_size) = le_u32(input)?;
110
111    Ok((
112        input,
113        FileHeader {
114            name_id,
115            size,
116            timestamp,
117            offset,
118            reserved,
119            header_data_id,
120            checksum,
121            pack_size,
122        },
123    ))
124}
125
126#[derive(Debug)]
127struct FileHeader {
128    name_id: i32,
129    size: u32,
130    timestamp: u32,
131    offset: u32,
132    reserved: u32,
133    header_data_id: u32,
134    checksum: u128,
135    pack_size: u32,
136}
137
138#[derive(Debug)]
139struct StringPool {
140    strings: Vec<String>,
141}
142
143impl StringPool {
144    fn with(input: &[u8]) -> Result<(&[u8], StringPool)> {
145        let (mut input, (num_strings, _pool_size)) = Self::get_header(input)?;
146        // TODO: Actually validate pool size?
147
148        let mut strings = vec![];
149
150        for _ in 0..num_strings {
151            let (remaining, s) = Self::read_string(input)?;
152            strings.push(s);
153            input = remaining;
154        }
155
156        Ok((input, StringPool { strings }))
157    }
158
159    fn get_header(input: &[u8]) -> Result<(&[u8], (u32, u32))> {
160        let (input, _) = tag(&0x6789u32.to_le_bytes())(input)?;
161        let (input, num_strings) = le_u32(input)?;
162        let (input, pool_size) = le_u32(input)?;
163        Ok((input, (num_strings, pool_size)))
164    }
165
166    fn read_string(input: &[u8]) -> Result<(&[u8], String)> {
167        let (input, str_length) = le_u32(input)?;
168        let (input, str_bytes) = take(str_length - 1)(input)?;
169        let (input, _) = tag(&0u8.to_le_bytes())(input)?;
170
171        // Probably should change this to from_utf8_lossy
172        Ok((input, str::from_utf8(str_bytes)?.to_string()))
173    }
174}