use crate::error::{Error, Result};
#[derive(Debug, Clone)]
pub struct ContentEntry {
pub hash: [u8; 32],
pub nca_id: [u8; 16],
pub size: u64,
pub content_type: u8,
}
#[derive(Debug, Clone)]
pub struct Cnmt {
pub title_id: String,
pub version: u32,
pub title_type: i8,
pub header_offset: u16,
pub content_entries: Vec<ContentEntry>,
}
impl Cnmt {
pub fn parse(buf: &[u8]) -> Result<Cnmt> {
if buf.len() < 0x20 {
return Err(Error::Corrupt("cnmt too short".into()));
}
let title_id = hex::encode(buf[0..8].iter().rev().copied().collect::<Vec<u8>>());
let version = u32::from_le_bytes(buf[8..12].try_into().unwrap());
let title_type = buf[12] as i8;
let header_offset = u16::from_le_bytes(buf[14..16].try_into().unwrap());
let content_entry_count = u16::from_le_bytes(buf[16..18].try_into().unwrap()) as usize;
let base = 0x20 + header_offset as usize;
let need = base + content_entry_count * 56;
if buf.len() < need {
return Err(Error::Corrupt("cnmt content table truncated".into()));
}
let mut content_entries = Vec::with_capacity(content_entry_count);
for i in 0..content_entry_count {
let b = base + i * 56;
let mut hash = [0u8; 32];
hash.copy_from_slice(&buf[b..b + 32]);
let mut nca_id = [0u8; 16];
nca_id.copy_from_slice(&buf[b + 32..b + 48]);
let mut size_bytes = [0u8; 8];
size_bytes[..6].copy_from_slice(&buf[b + 48..b + 54]);
let size = u64::from_le_bytes(size_bytes);
let content_type = buf[b + 54];
content_entries.push(ContentEntry {
hash,
nca_id,
size,
content_type,
});
}
Ok(Cnmt {
title_id,
version,
title_type,
header_offset,
content_entries,
})
}
pub fn find_by_hash_hex(&self, sha256_hex: &str) -> Option<&ContentEntry> {
let lower = sha256_hex.to_lowercase();
self.content_entries
.iter()
.find(|e| hex::encode(e.hash) == lower)
}
}