luminol_filesystem/archiver/filesystem/
mod.rs

1// Copyright (C) 2024 Melody Madeline Lyons
2//
3// This file is part of Luminol.
4//
5// Luminol is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Luminol is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Luminol.  If not, see <http://www.gnu.org/licenses/>.
17
18use async_std::io::{BufReader as AsyncBufReader, BufWriter as AsyncBufWriter};
19use color_eyre::eyre::WrapErr;
20use itertools::Itertools;
21use rand::Rng;
22use std::io::{prelude::*, BufReader, SeekFrom};
23
24use super::util::{advance_magic, read_file_xor_async, read_header, read_u32_xor};
25use super::{Entry, File, Trie, HEADER, MAGIC};
26use crate::{Error, Result};
27
28mod impls;
29
30#[derive(Debug, Default)]
31pub struct FileSystem<T> {
32    pub(super) trie: std::sync::Arc<parking_lot::RwLock<Trie>>,
33    pub(super) archive: std::sync::Arc<parking_lot::Mutex<T>>,
34    pub(super) version: u8,
35    pub(super) base_magic: u32,
36}
37
38impl<T> Clone for FileSystem<T> {
39    fn clone(&self) -> Self {
40        Self {
41            trie: self.trie.clone(),
42            archive: self.archive.clone(),
43            version: self.version,
44            base_magic: self.base_magic,
45        }
46    }
47}
48
49impl<T> FileSystem<T>
50where
51    T: crate::File,
52{
53    /// Creates a new archiver filesystem from a file containing an existing archive.
54    pub fn new(mut file: T) -> Result<Self> {
55        file.seek(SeekFrom::Start(0))
56            .wrap_err("While detecting archive version")?;
57        let mut reader = BufReader::new(&mut file);
58
59        let version = read_header(&mut reader).wrap_err("While detecting archive version")?;
60
61        let mut trie = crate::FileSystemTrie::new();
62
63        let mut base_magic = MAGIC;
64
65        let c = format!(
66            "While performing initial parsing of the header of a version {version} archive"
67        );
68
69        match version {
70            1 | 2 => {
71                let mut magic = MAGIC;
72
73                let mut i = 0;
74
75                while let Ok(path_len) = read_u32_xor(&mut reader, advance_magic(&mut magic)) {
76                    let mut path = vec![0; path_len as usize];
77                    reader.read_exact(&mut path).wrap_err("").wrap_err_with(|| format!("While reading the path (path length = {path_len}) of file #{i} in the archive")).wrap_err_with(|| c.clone())?;
78                    for byte in path.iter_mut() {
79                        let char = *byte ^ advance_magic(&mut magic) as u8;
80                        if char == b'\\' {
81                            *byte = b'/';
82                        } else {
83                            *byte = char;
84                        }
85                    }
86                    let path = camino::Utf8PathBuf::from(String::from_utf8(path).wrap_err_with(|| format!("While reading the path (path length = {path_len}) of file #{i} in the archive)")).wrap_err_with(|| c.clone())?);
87
88                    let entry_len = read_u32_xor(&mut reader, advance_magic(&mut magic))
89                        .wrap_err_with(|| {
90                            format!("While reading the file length (path = {path:?}) of file #{i} in the archive")
91                        })
92                        .wrap_err_with(|| c.clone())?;
93
94                    let stream_position = reader
95                        .stream_position()
96                        .wrap_err_with(|| {
97                            format!("While reading the file length (path = {path:?}) of file #{i} in the archive")
98                        })
99                        .wrap_err_with(|| c.clone())?;
100                    let entry = Entry {
101                        size: entry_len as u64,
102                        header_offset: stream_position
103                            .checked_sub(path_len as u64 + 8)
104                            .ok_or(Error::InvalidHeader).wrap_err_with(|| format!("While reading the file length (path = {path:?}) of file #{i} in the archive")).wrap_err_with(|| c.clone())?,
105                        body_offset: stream_position,
106                        start_magic: magic,
107                    };
108
109                    trie.create_file(path, entry);
110
111                    reader
112                        .seek(SeekFrom::Start(entry.body_offset + entry.size))
113                        .wrap_err_with(|| {
114                            format!(
115                                "While seeking to offset {} to read file #{} in the archive",
116                                entry.body_offset + entry.size,
117                                i + 1
118                            )
119                        })
120                        .wrap_err_with(|| c.clone())?;
121                    i += 1;
122                }
123            }
124            3 => {
125                let mut u32_buf = [0; 4];
126                reader
127                    .read_exact(&mut u32_buf)
128                    .wrap_err("While reading the base magic value of the archive")
129                    .wrap_err_with(|| c.clone())?;
130
131                base_magic = u32::from_le_bytes(u32_buf);
132                base_magic = base_magic.wrapping_mul(9).wrapping_add(3);
133
134                let mut i = 0;
135
136                while let Ok(body_offset) = read_u32_xor(&mut reader, base_magic) {
137                    if body_offset == 0 {
138                        break;
139                    }
140                    let header_offset = reader
141                        .stream_position()
142                        .wrap_err_with(|| {
143                            format!("While reading the file offset of file #{i} in the archive")
144                        })
145                        .wrap_err_with(|| c.clone())?
146                        .checked_sub(4)
147                        .ok_or(Error::InvalidHeader)
148                        .wrap_err_with(|| {
149                            format!("While reading the file offset of file #{i} in the archive")
150                        })
151                        .wrap_err_with(|| c.clone())?;
152
153                    let entry_len = read_u32_xor(&mut reader, base_magic).wrap_err_with(|| format!("While reading the file length (file offset = {body_offset}) of file #{i} in the archive")).wrap_err_with(|| c.clone())?;
154                    let magic = read_u32_xor(&mut reader, base_magic).wrap_err_with(|| format!("While reading the magic value (file offset = {body_offset}, file length = {entry_len}) of file #{i} in the archive")).wrap_err_with(|| c.clone())?;
155                    let path_len = read_u32_xor(&mut reader, base_magic).wrap_err_with(|| format!("While reading the path length (file offset = {body_offset}, file length = {entry_len}) of file #{i} in the archive")).wrap_err_with(|| c.clone())?;
156
157                    let mut path = vec![0; path_len as usize];
158                    reader.read_exact(&mut path).wrap_err_with(|| format!("While reading the path (file offset = {body_offset}, file length = {entry_len}, path length = {path_len}) of file #{i} in the archive")).wrap_err_with(|| c.clone())?;
159                    for (i, byte) in path.iter_mut().enumerate() {
160                        let char = *byte ^ (base_magic >> (8 * (i % 4))) as u8;
161                        if char == b'\\' {
162                            *byte = b'/';
163                        } else {
164                            *byte = char;
165                        }
166                    }
167                    let path = camino::Utf8PathBuf::from(String::from_utf8(path).wrap_err_with(|| format!("While reading the path (file offset = {body_offset}, file length = {entry_len}, path length = {path_len}) of file #{i} in the archive")).wrap_err_with(|| c.clone())?);
168
169                    let entry = Entry {
170                        size: entry_len as u64,
171                        header_offset,
172                        body_offset: body_offset as u64,
173                        start_magic: magic,
174                    };
175                    trie.create_file(path, entry);
176                    i += 1;
177                }
178            }
179            _ => return Err(Error::InvalidArchiveVersion(version).into()),
180        }
181
182        Ok(Self {
183            trie: std::sync::Arc::new(parking_lot::RwLock::new(trie)),
184            archive: std::sync::Arc::new(parking_lot::Mutex::new(file)),
185            version,
186            base_magic,
187        })
188    }
189
190    /// Creates a new archiver filesystem from the given files.
191    /// The contents of the archive itself will be stored in `buffer`.
192    pub async fn from_buffer_and_files<'a, I, P, R>(
193        mut buffer: T,
194        version: u8,
195        files: I,
196    ) -> Result<Self>
197    where
198        T: futures_lite::AsyncWrite + futures_lite::AsyncSeek + Unpin,
199        I: Iterator<Item = Result<(&'a P, u32, R)>>,
200        P: AsRef<camino::Utf8Path> + 'a,
201        R: futures_lite::AsyncRead + Unpin,
202    {
203        use futures_lite::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
204
205        let c = format!("While creating a new version {version} archive");
206
207        buffer
208            .set_len(0)
209            .wrap_err("While clearing the archive")
210            .wrap_err_with(|| c.clone())?;
211        AsyncSeekExt::seek(&mut buffer, SeekFrom::Start(0))
212            .await
213            .wrap_err("While clearing the archive")
214            .wrap_err_with(|| c.clone())?;
215
216        let mut writer = AsyncBufWriter::new(&mut buffer);
217        writer
218            .write_all(HEADER)
219            .await
220            .wrap_err("While writing the archive version")
221            .wrap_err_with(|| c.clone())?;
222        writer
223            .write_all(&[version])
224            .await
225            .wrap_err("While writing the archive version")
226            .wrap_err_with(|| c.clone())?;
227
228        let mut trie = Trie::new();
229
230        match version {
231            1 | 2 => {
232                let mut magic = MAGIC;
233                let mut header_offset = 8;
234
235                for (i, result) in files.enumerate() {
236                    let (path, size, file) = result
237                        .wrap_err_with(|| {
238                            format!(
239                                "While getting file #{i} to add to the archive from the iterator"
240                            )
241                        })
242                        .wrap_err_with(|| c.clone())?;
243                    let reader = AsyncBufReader::new(file.take(size as u64));
244                    let path = path.as_ref();
245                    let header_size = path.as_str().bytes().len() as u64 + 8;
246
247                    // Write the header
248                    writer
249                        .write_all(
250                            &(path.as_str().bytes().len() as u32 ^ advance_magic(&mut magic))
251                                .to_le_bytes(),
252                        )
253                        .await.wrap_err_with(|| format!("While writing the path length of file #{i} (path = {path:?}, file length = {size}) to the archive")).wrap_err_with(|| c.clone())?;
254                    writer
255                        .write_all(
256                            &path
257                                .as_str()
258                                .bytes()
259                                .map(|b| {
260                                    let b = if b == b'/' { b'\\' } else { b };
261                                    b ^ advance_magic(&mut magic) as u8
262                                })
263                                .collect_vec(),
264                        )
265                        .await.wrap_err_with(|| format!("While writing the path of file #{i} (path = {path:?}, file length = {size}) to the archive")).wrap_err_with(|| c.clone())?;
266                    writer
267                        .write_all(&(size ^ advance_magic(&mut magic)).to_le_bytes())
268                        .await.wrap_err_with(|| format!("While writing the file length of file #{i} (path = {path:?}, file length = {size}) to the archive")).wrap_err_with(|| c.clone())?;
269
270                    // Write the file contents
271                    async_std::io::copy(&mut read_file_xor_async(reader, magic), &mut writer)
272                        .await.wrap_err_with(|| format!("While writing the contents of file #{i} (path = {path:?}, file length = {size}) to the archive")).wrap_err_with(|| c.clone())?;
273
274                    trie.create_file(
275                        path,
276                        Entry {
277                            header_offset,
278                            body_offset: header_offset + header_size,
279                            size: size as u64,
280                            start_magic: magic,
281                        },
282                    );
283
284                    header_offset += header_size + size as u64;
285                }
286
287                writer
288                    .flush()
289                    .await
290                    .wrap_err("While flushing the archive after writing its contents")
291                    .wrap_err_with(|| c.clone())?;
292                drop(writer);
293                Ok(Self {
294                    trie: std::sync::Arc::new(parking_lot::RwLock::new(trie)),
295                    archive: std::sync::Arc::new(parking_lot::Mutex::new(buffer)),
296                    version,
297                    base_magic: MAGIC,
298                })
299            }
300
301            3 => {
302                let mut tmp = crate::host::File::new()
303                    .wrap_err("While creating a temporary file")
304                    .wrap_err_with(|| c.clone())?;
305                let mut tmp_writer = AsyncBufWriter::new(&mut tmp);
306                let mut entries = if let (_, Some(upper_bound)) = files.size_hint() {
307                    Vec::with_capacity(upper_bound)
308                } else {
309                    Vec::new()
310                };
311
312                let base_magic: u32 = rand::thread_rng().gen();
313                writer
314                    .write_all(&(base_magic.wrapping_sub(3).wrapping_mul(954437177)).to_le_bytes())
315                    .await
316                    .wrap_err("While writing the archive base magic value")
317                    .wrap_err_with(|| c.clone())?;
318                let mut header_offset = 12;
319                let mut body_offset = 0;
320
321                for (i, result) in files.enumerate() {
322                    let (path, size, file) = result
323                        .wrap_err_with(|| {
324                            format!(
325                                "While getting file #{i} to write to the archive from the iterator"
326                            )
327                        })
328                        .wrap_err_with(|| c.clone())?;
329                    let reader = AsyncBufReader::new(file.take(size as u64));
330                    let path = path.as_ref();
331                    let entry_magic: u32 = rand::thread_rng().gen();
332
333                    // Write the header to the buffer, except for the offset
334                    writer.seek(SeekFrom::Current(4)).await.wrap_err_with(|| format!("While writing the file length of file #{i} (path = {path:?}, file length = {size}) to the archive")).wrap_err_with(|| c.clone())?;
335                    writer.write_all(&(size ^ base_magic).to_le_bytes()).await.wrap_err_with(|| format!("While writing the file length of file #{i} (path = {path:?}, file length = {size}) to the archive")).wrap_err_with(|| c.clone())?;
336                    writer
337                        .write_all(&(entry_magic ^ base_magic).to_le_bytes())
338                        .await.wrap_err_with(|| format!("While writing the magic value of file #{i} (path = {path:?}, file length = {size}) to the archive")).wrap_err_with(|| c.clone())?;
339                    writer
340                        .write_all(&(path.as_str().bytes().len() as u32 ^ base_magic).to_le_bytes())
341                        .await.wrap_err_with(|| format!("While writing the path length of file #{i} (path = {path:?}, file length = {size}) to the archive")).wrap_err_with(|| c.clone())?;
342                    writer
343                        .write_all(
344                            &path
345                                .as_str()
346                                .bytes()
347                                .enumerate()
348                                .map(|(i, b)| {
349                                    let b = if b == b'/' { b'\\' } else { b };
350                                    b ^ (base_magic >> (8 * (i % 4))) as u8
351                                })
352                                .collect_vec(),
353                        )
354                        .await.wrap_err_with(|| format!("While writing the path of file #{i} (path = {path:?}, file length = {size}) to the archive")).wrap_err_with(|| c.clone())?;
355
356                    // Write the actual file contents to a temporary file
357                    async_std::io::copy(
358                        &mut read_file_xor_async(reader, entry_magic),
359                        &mut tmp_writer,
360                    )
361                    .await.wrap_err_with(|| format!("While writing the contents of file #{i} (path = {path:?}, file length = {size}) to a temporary file before writing it to the archive")).wrap_err_with(|| c.clone())?;
362
363                    entries.push((
364                        path.to_owned(),
365                        Entry {
366                            header_offset,
367                            body_offset,
368                            size: size as u64,
369                            start_magic: entry_magic,
370                        },
371                    ));
372
373                    header_offset += path.as_str().bytes().len() as u64 + 16;
374                    body_offset += size as u64;
375                }
376
377                // Write the terminator at the end of the buffer
378                writer
379                    .write_all(&base_magic.to_le_bytes())
380                    .await
381                    .wrap_err("While writing the header terminator to the archive")
382                    .wrap_err_with(|| c.clone())?;
383
384                // Write the contents of the temporary file to the buffer after the terminator
385                tmp_writer
386                    .flush()
387                    .await
388                    .wrap_err("While flushing a temporary file containing the archive body")
389                    .wrap_err_with(|| c.clone())?;
390                drop(tmp_writer);
391                AsyncSeekExt::seek(&mut tmp, SeekFrom::Start(0)).await.wrap_err("While copying a temporary file containing the archive body into the archive").wrap_err_with(|| c.clone())?;
392                async_std::io::copy(&mut tmp, &mut writer).await.wrap_err("While copying a temporary file containin the archive body into the archive").wrap_err_with(|| c.clone())?;
393
394                // Write the offsets into the header now that we know the total size of the files
395                let header_size = header_offset + 4;
396                for (i, (path, mut entry)) in entries.into_iter().enumerate() {
397                    entry.body_offset += header_size;
398                    writer.seek(SeekFrom::Start(entry.header_offset)).await.wrap_err_with(|| format!("While writing the file offset of file #{i} (path = {path:?}, file length = {}, file offset = {})", entry.size, entry.body_offset)).wrap_err_with(|| c.clone())?;
399                    writer
400                        .write_all(&(entry.body_offset as u32 ^ base_magic).to_le_bytes())
401                        .await.wrap_err_with(|| format!("While writing the file offset of file #{i} (path = {path:?}, file length = {}, file offset = {}) to the archive", entry.size, entry.body_offset)).wrap_err_with(|| c.clone())?;
402                    trie.create_file(path, entry);
403                }
404
405                writer
406                    .flush()
407                    .await
408                    .wrap_err("While flushing the archive after writing its contents")
409                    .wrap_err_with(|| c.clone())?;
410                drop(writer);
411                Ok(Self {
412                    trie: std::sync::Arc::new(parking_lot::RwLock::new(trie)),
413                    archive: std::sync::Arc::new(parking_lot::Mutex::new(buffer)),
414                    version,
415                    base_magic,
416                })
417            }
418
419            _ => Err(Error::NotSupported.into()),
420        }
421    }
422}