psarc2 0.1.0

PlayStation archive reader
Documentation
//! File handling within the archive.

use std::{
    io::{Cursor, Read, Seek, Write},
    path::{Path, PathBuf},
};

use crate::{
    PlaystationArchive, PlaystationFile,
    error::{Error, Result},
    toc::TableOfContentEntry,
};

/// Any file within the binary.
pub(crate) trait BasicFileEntry {
    /// Index in the block list size.
    fn index_list_size(&self) -> u32;

    /// Compressed size of blocks.
    fn input_size(&self) -> usize;

    /// Uncompressed size.
    fn output_size(&self) -> usize;

    /// Absolute byte offset.
    fn offset(&self) -> usize;
}

/// Single file entry in the archive.
#[derive(Debug, Clone)]
pub(crate) struct FileEntry {
    /// Will be set after manifest is parsed.
    pub(crate) path: PathBuf,
    /// Index in the block list size.
    pub(crate) index_list_size: u32,
    /// Uncompressed size.
    pub(crate) size: usize,
    /// Byte offset in whole file.
    pub(crate) offset: usize,
    /// Total bytes of the whole file.
    pub(crate) input_size: usize,
}

impl FileEntry {
    /// Create from table of content entry and a file path.
    pub(crate) fn from_table_of_content(
        TableOfContentEntry {
            index_list_size,
            size,
            offset,
            input_size,
        }: TableOfContentEntry,
        path: &str,
    ) -> Self {
        let path = PathBuf::from(path);

        Self {
            path,
            index_list_size,
            size,
            offset,
            input_size,
        }
    }
}

impl BasicFileEntry for FileEntry {
    fn index_list_size(&self) -> u32 {
        self.index_list_size
    }

    fn input_size(&self) -> usize {
        self.input_size
    }

    fn output_size(&self) -> usize {
        self.size
    }

    fn offset(&self) -> usize {
        self.offset
    }
}

impl<R: Read + Seek> PlaystationArchive<R> {
    /// Read a single file by index.
    ///
    /// # Errors
    ///
    /// - When index is out of bounds.
    /// - When file can't be decompressed.
    pub fn by_index(&mut self, file_index: usize) -> Result<PlaystationFile> {
        let entry = self
            .file_entries
            .get(file_index)
            .ok_or(Error::FileAtIndexDoesNotExist(file_index))?;

        self.by_entry(&entry.clone())
    }

    /// Read a single file by file name.
    ///
    /// # Errors
    ///
    /// - When no file with name can be found.
    /// - When file can't be decompressed.
    pub fn by_name(&mut self, name: &str) -> Result<PlaystationFile> {
        let entry = self.entry_by_name(name)?.clone();

        self.by_entry(&entry)
    }

    /// Read a single file by full path.
    ///
    /// # Errors
    ///
    /// - When no file with name can be found.
    /// - When file can't be decompressed.
    pub fn by_path<P>(&mut self, path: P) -> Result<PlaystationFile>
    where
        P: AsRef<Path>,
    {
        let entry = self.entry_by_path(path)?.clone();

        self.by_entry(&entry)
    }

    /// Read a single file by index.
    pub(crate) fn by_entry(&mut self, entry: &FileEntry) -> Result<PlaystationFile> {
        // Decompress if needed
        let mut bytes = vec![0; entry.input_size()];
        let mut writer = Cursor::new(&mut bytes);

        self.write_bytes_by_entry(&mut writer, entry)?;

        Ok(PlaystationFile {
            bytes,
            path: entry.path.clone(),
        })
    }

    /// Write the bytes of a file by entry directly to a writer.
    pub(crate) fn write_bytes_by_entry<W>(
        &mut self,
        writer: &mut W,
        entry: &FileEntry,
    ) -> Result<()>
    where
        W: Write,
    {
        self.compression_type.decompress(
            &mut self.reader,
            writer,
            entry,
            self.block_size,
            &self.block_sizes,
        )
    }
}

impl PlaystationFile {
    /// Get the path.
    #[must_use]
    pub fn path(&self) -> &Path {
        &self.path
    }

    /// Extract the bytes.
    #[must_use]
    pub fn into_inner(self) -> Vec<u8> {
        self.bytes
    }
}