ctr_cart 0.4.0

3DS file header library and utilities.
Documentation
// SPDX-License-Identifier: LGPL-2.1-or-later OR GPL-2.0-or-later OR MPL-2.0
// SPDX-FileCopyrightText: 2024 Gabriel Marcano <gabemarcano@yahoo.com>

use crate::error::Error;
use crate::ncsd::NCCHPartitionEntry;

use std::io::Read;
use std::io::Seek;
use std::io::SeekFrom;

use byteorder::LittleEndian;
use byteorder::ReadBytesExt;

#[derive(Debug)]
pub struct NCCH {
    pub signature: [u8; 0x100],
    pub magic: [u8; 4],
    pub size: u32,
    pub partition_title_id: u64,
    pub maker_code: u16,
    pub version: u16,
    pub sha256_check: u32,
    pub program_id: u64,
    pub logo_sha256: Option<[u8; 0x20]>,
    pub product_code: [u8; 16],
    pub extended_header_sha256: [u8; 0x20],
    pub extended_header_size: u32,
    pub flags: u64,
    pub plain_region_offset: u32,        // in media units
    pub plain_region_size: u32,          // in media units
    pub logo_region_offset: Option<u32>, // in media units
    pub logo_region_size: Option<u32>,   // in media units
    pub exefs_offset: u32,               // in media units
    pub exefs_size: u32,                 // in media units
    pub exefs_hash_region_size: u32,     // in media units
    pub romfs_offset: u32,               // in media units
    pub romfs_size: u32,                 // in media units
    pub romfs_hash_region_size: u32,
    pub exefs_sha256: [u8; 0x20],
    pub romfs_sha256: [u8; 0x20],
    pub partition_entry: NCCHPartitionEntry,
}

pub trait NCCHRead {
    /// Parses the 3DS ROM metadata from the object provided, returning a Header object with the
    /// header metadata.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Parse`] if the header cannot be found or if a field in the header contains
    /// an unexpected value.
    /// Returns [`Error::Io`] if an IO error took place while reading from the file.
    fn read_ncch(&mut self, partition: NCCHPartitionEntry) -> Result<Option<NCCH>, Error>;
}

impl<T: Read + Seek> NCCHRead for T {
    fn read_ncch(&mut self, partition: NCCHPartitionEntry) -> Result<Option<NCCH>, Error> {
        // Get NCCHs
        if partition.size == 0 {
            return Ok(None);
        }

        let offset = partition.offset;
        let offset = u64::from(offset) * 0x200u64;
        self.seek(SeekFrom::Start(offset))?;
        let mut signature = [0u8; 0x100];
        self.read_exact(&mut signature)?;
        let mut magic = [0u8; 4];
        self.read_exact(&mut magic)?;
        let size = self.read_u32::<LittleEndian>()?;
        let partition_title_id = self.read_u64::<LittleEndian>()?;
        let maker_code = self.read_u16::<LittleEndian>()?;
        let version = self.read_u16::<LittleEndian>()?;
        let sha256_check = self.read_u32::<LittleEndian>()?;
        let program_id = self.read_u64::<LittleEndian>()?;
        self.seek(SeekFrom::Current(0x10))?;
        let mut logo_sha256 = [0u8; 0x20];
        self.read_exact(&mut logo_sha256)?;
        let mut product_code = [0u8; 0x10];
        self.read_exact(&mut product_code)?;
        let mut extended_header_sha256 = [0u8; 0x20];
        self.read_exact(&mut extended_header_sha256)?;
        let extended_header_size = self.read_u32::<LittleEndian>()?;
        self.seek(SeekFrom::Current(0x4))?;
        let flags = self.read_u64::<LittleEndian>()?;
        let plain_region_offset = self.read_u32::<LittleEndian>()?;
        let plain_region_size = self.read_u32::<LittleEndian>()?;
        let logo_region_offset = self.read_u32::<LittleEndian>()?;
        let logo_region_size = self.read_u32::<LittleEndian>()?;
        let (logo_sha256, logo_region_offset, logo_region_size) = if logo_region_size == 0 {
            (None, None, None)
        } else {
            (
                Some(logo_sha256),
                Some(logo_region_offset),
                Some(logo_region_size),
            )
        };
        let exefs_offset = self.read_u32::<LittleEndian>()?;
        let exefs_size = self.read_u32::<LittleEndian>()?;
        let exefs_hash_region_size = self.read_u32::<LittleEndian>()?;
        self.seek(SeekFrom::Current(0x4))?;
        let romfs_offset = self.read_u32::<LittleEndian>()?;
        let romfs_size = self.read_u32::<LittleEndian>()?;
        let romfs_hash_region_size = self.read_u32::<LittleEndian>()?;
        self.seek(SeekFrom::Current(0x4))?;
        let mut exefs_sha256 = [0u8; 0x20];
        self.read_exact(&mut exefs_sha256)?;
        let mut romfs_sha256 = [0u8; 0x20];
        self.read_exact(&mut romfs_sha256)?;

        Ok(Some(NCCH {
            signature,
            magic,
            size,
            partition_title_id,
            maker_code,
            version,
            sha256_check,
            program_id,
            logo_sha256,
            product_code,
            extended_header_sha256,
            extended_header_size,
            flags,
            plain_region_offset,
            plain_region_size,
            logo_region_offset,
            logo_region_size,
            exefs_offset,
            exefs_size,
            exefs_hash_region_size,
            romfs_offset,
            romfs_size,
            romfs_hash_region_size,
            exefs_sha256,
            romfs_sha256,
            partition_entry: partition,
        }))
    }
}