1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
use crate::FileTime;
use std::borrow::Cow;
use std::io::Cursor;
use std::io::Read;
/// An Entry, representative of a file inside a pakfile.
#[derive(Debug)]
pub struct Entry<'a> {
/// The "path" of the file.
///
/// The encoding is unspecified,
/// but this is only ascii in PopCap Games.
pub(crate) path: Box<[u8]>,
pub(crate) file_time: FileTime,
pub(crate) data: Cursor<Cow<'a, [u8]>>,
}
impl<'a> Entry<'a> {
/// Get the starting index into the path variable of the name of the file, if it exists.
///
/// # Returns
/// If the path only contains the file name, this returns `None`.
fn get_file_name_start_index(&self) -> Option<usize> {
self.path
.iter()
.rev()
.position(|&b| b == b'/' || b == b'\\')
.map(|index| self.path.len() - index)
}
/// Get the directory of the file, or [`None`] if it doesn't exist.
pub fn dir(&self) -> Option<&[u8]> {
let index = self.get_file_name_start_index()?.saturating_sub(1);
Some(&self.path[..index])
}
/// The same as [`Self::dir`],
/// but tries to make the byte slice a str.
///
/// This is provided as most paths are ascii.
pub fn dir_str(&self) -> Option<Result<&str, std::str::Utf8Error>> {
self.dir().map(std::str::from_utf8)
}
/// Get the name of the file.
pub fn name(&self) -> &[u8] {
let index = self.get_file_name_start_index().unwrap_or(0);
&self.path[index..]
}
/// The same as [`Self::name`],
/// but tries to make the byte slice a str.
///
/// This is provided as most paths are ascii.
pub fn name_str(&self) -> Result<&str, std::str::Utf8Error> {
std::str::from_utf8(self.name())
}
/// Get the entire path of the file.
pub fn path(&self) -> &[u8] {
&self.path
}
/// The same as [`Self::path`],
/// but tries to make the byte slice a str.
///
/// This is provided as most paths are ascii.
pub fn path_str(&self) -> Result<&str, std::str::Utf8Error> {
std::str::from_utf8(self.path())
}
/// Get the [`FileTime`].
///
/// This represents access and modification time; there is no distinction.
pub fn file_time(&self) -> FileTime {
self.file_time
}
/// Get an iterator over all the bytes in the file.
pub fn iter_data(&self) -> impl Iterator<Item = u8> + '_ {
let borrowed = self.is_borrowed();
self.data
.get_ref()
.iter()
.copied()
.map(move |b| if borrowed { b ^ 0xf7 } else { b })
}
/// Check if the file is borrowed aka does not own its data.
pub fn is_borrowed(&self) -> bool {
match self.data.get_ref() {
Cow::Owned(_) => false,
Cow::Borrowed(_) => true,
}
}
/// Get the filesize.
pub fn size(&self) -> usize {
self.data.get_ref().len()
}
/// Decrypt the data and take ownership if it is borrowed.
///
/// Position data of the file is lost.
pub fn into_owned(mut self) -> Entry<'static> {
Entry {
path: self.path,
file_time: self.file_time,
data: {
// TODO: Preserve position data. Just reuse the old cursor, swap new data in/out.
match self.data.get_mut() {
Cow::Owned(data) => Cursor::new(std::mem::take(data).into()),
Cow::Borrowed(data) => Cursor::new(data.iter().map(|b| b ^ 0xf7).collect()),
}
},
}
}
}
impl<'a> Read for Entry<'a> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
let n = self.data.read(buf)?;
if self.is_borrowed() {
buf.iter_mut().for_each(|el| *el ^= 0xf7);
}
Ok(n)
}
}
impl<'a> PartialEq for Entry<'a> {
fn eq(&self, other: &Self) -> bool {
self.path == other.path // TODO: Consider not including filetime/path checks
&& self.file_time == other.file_time
&& self.iter_data().eq(other.iter_data())
}
}