psarc2 0.1.0

PlayStation archive reader
Documentation
//! Table of content reading.

use std::io::{Cursor, Read, Seek};

use crate::{
    error::{Error, Result},
    file::BasicFileEntry,
    path::ArchiveFlags,
    read::{read_u32, read_unsigned},
};

/// AES-256 key for decrypting the header.
#[derive(Default)]
pub struct DecryptionKey {
    /// Bytes for the key.
    pub key: [u8; 32],
    /// Bytes for the IV.
    pub iv: [u8; 16],
}

/// Table of contents metadata within the archive.
pub(crate) struct TableOfContent {
    /// Used for calculating the blocks.
    length: u32,
    /// Amount of entries in the buffer.
    entry_count: u32,
}

impl TableOfContent {
    /// How many bytes each entry is.
    const ENTRY_SIZE: usize = 30;

    /// Read metadata from the buffer.
    pub(crate) fn read<R>(reader: &mut R) -> Result<Self>
    where
        R: Read,
    {
        // Read the metadata first
        let length = read_u32(reader)?;

        // Ensure the entry size is correct
        let entry_size = read_u32(reader)?;
        if entry_size as usize != Self::ENTRY_SIZE {
            return Err(Error::Corrupt("table of content entry size mismatch"));
        }

        let entry_count = read_u32(reader)?;

        Ok(Self {
            length,
            entry_count,
        })
    }

    /// Read file entries from the buffer.
    pub(crate) fn read_entries<R>(
        &self,
        reader: &mut R,
        archive_flags: ArchiveFlags,
        decryption_key: Option<DecryptionKey>,
    ) -> Result<Vec<TableOfContentEntry>>
    where
        R: Read + Seek,
    {
        // Amount of bytes for the entries
        let entry_bytes = self.entry_count as usize * Self::ENTRY_SIZE;

        // Read the buffer
        let mut entries_buffer = vec![0; entry_bytes];
        reader.read_exact(&mut entries_buffer)?;

        // Decrypt the entries if necessary
        archive_flags.decrypt(&mut entries_buffer, decryption_key)?;

        // Create a reader to the file entries
        let mut cursor = Cursor::new(entries_buffer);

        // Read file entries
        let mut entries = (0..(self.entry_count as usize))
            // Each entry must read `Self::ENTRY_SIZE` bytes
            .map(|_| TableOfContentEntry::read(&mut cursor))
            .collect::<Result<Vec<_>>>()?;

        // Calculate the size of each entry by taking the difference in offsets
        // Except for the last one, for which we need the total size to know it
        for index in 0..entries.len() - 1 {
            entries[index].input_size = entries[index + 1].offset - entries[index].offset;
        }

        Ok(entries)
    }

    /// Total size in bytes of the blocks following the table of content.
    pub(crate) const fn blocks_amount(&self) -> usize {
        let blocks_offset = Self::ENTRY_SIZE * self.entry_count as usize + 32;

        (self.length as usize - blocks_offset) / 2
    }
}

/// File entry metadata from the table of contents.
#[derive(Debug)]
pub(crate) struct TableOfContentEntry {
    /// 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, will be filled when the block sizes are known.
    pub(crate) input_size: usize,
}

impl TableOfContentEntry {
    /// Read file from a buffer.
    pub(crate) fn read<R>(reader: &mut R) -> Result<Self>
    where
        R: Read + Seek,
    {
        // Skip the name digest block
        reader
            .seek_relative(i64::try_from(size_of::<u128>()).map_err(|_| Error::AddressTooSmall)?)?;

        // Read parameters
        let index_list_size = read_u32(reader)?;
        let size = read_unsigned::<5>(reader)?;
        let offset = read_unsigned::<5>(reader)?;

        Ok(Self {
            index_list_size,
            size,
            offset,

            // This will be filled later
            input_size: 0,
        })
    }
}

impl BasicFileEntry for TableOfContentEntry {
    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
    }
}