use crate::cart::CartIo;
use crate::error::Error;
use crate::exefs::ExeFsRead;
use std::io::Read;
use std::io::Seek;
use std::io::SeekFrom;
use std::str;
#[derive(Debug)]
pub struct ApplicationTitle {
pub short_description: String,
pub long_description: String,
pub publisher: String,
}
#[derive(Debug)]
pub struct SMDH {
pub magic: String,
pub version: u16,
pub titles: [ApplicationTitle; 16],
pub application_settings: [u8; 0x30], pub icons: [u16; 0x1680 / 2],
}
pub trait SMDHRead {
fn read_smdh(&mut self) -> Result<Option<SMDH>, Error>;
}
fn trim(string: &str) -> &str {
string.trim_matches('\0').trim()
}
fn from_utf16(data: &[u8]) -> Result<String, Error> {
let data: Vec<u16> = data
.chunks(2)
.map(|e| u16::from_le_bytes(e.try_into().unwrap()))
.collect();
String::from_utf16(&data).map_err(|_| Error::Parse("could not parse utf16 string".into()))
}
impl<T: Read + Seek> SMDHRead for CartIo<'_, T> {
fn read_smdh(&mut self) -> Result<Option<SMDH>, Error> {
let ncch = self.cart.ncsd.find_exefs_ncch();
if ncch.is_none() {
return Ok(None);
}
let ncch = ncch.unwrap();
if ncch.exefs_size == 0 {
return Ok(None);
}
let exefs_header = self.read_exefs_header()?.unwrap();
let icon_file = exefs_header
.files
.iter()
.find(|&file| file.filename == "icon");
if icon_file.is_none() {
return Ok(None);
}
let icon_file = icon_file.unwrap();
let ncch = self.cart.ncsd.find_exefs_ncch().unwrap();
let offset =
(ncch.exefs_offset + ncch.partition_entry.offset + 1) * 0x200 + icon_file.offset;
self.seek(SeekFrom::Start(offset.into()))?;
let mut icon_data = vec![0u8; usize::try_from(icon_file.size).unwrap()];
self.read_exact(&mut icon_data)?;
let magic = str::from_utf8(&icon_data[0..4])?.to_string();
let version = u16::from_le_bytes(icon_data[4..6].try_into().unwrap());
let mut titles = Vec::<ApplicationTitle>::default();
for i in 0..16 {
titles.push(ApplicationTitle {
short_description: trim(&from_utf16(
&icon_data[0x8 + 0x200 * i..0x8 + 0x200 * i + 0x80],
)?)
.to_string(),
long_description: trim(&from_utf16(
&icon_data[0x8 + 0x200 * i + 0x80..0x8 + 0x200 * i + 0x180],
)?)
.to_string(),
publisher: trim(&from_utf16(
&icon_data[0x8 + 0x200 * i + 0x180..0x8 + 0x200 * i + 0x200],
)?)
.to_string(),
});
}
let application_settings: [u8; 0x30] = icon_data[0x2008..0x2008 + 0x30].try_into().unwrap();
let icons: [u16; 0x1680 / 2] = icon_data[0x2040..0x2040 + 0x1680]
.chunks_exact(2)
.map(|e| u16::from_le_bytes([e[0], e[1]]))
.collect::<Vec<_>>()
.try_into()
.unwrap();
Ok(Some(SMDH {
magic,
version,
titles: titles.try_into().unwrap(),
application_settings,
icons,
}))
}
}
fn place_2x2(data: &[u16], output: &mut [u8]) {
for y in 0..2 {
for x in 0..2 {
let pixel = data[x + y * 2];
let r = ((pixel & 0x1F) * 255 / 0x1F) & 0xFF;
let g = (((pixel >> 5) & 0x3F) * 255 / 0x3F) & 0xFF;
let b = (((pixel >> 11) & 0x1F) * 255 / 0x1F) & 0xFF;
output[(x + y * 48) * 4] = b as u8;
output[(x + y * 48) * 4 + 1] = g as u8;
output[(x + y * 48) * 4 + 2] = r as u8;
output[(x + y * 48) * 4 + 3] = 0xFF;
}
}
}
fn place_4x4(data: &[u16], output: &mut [u8]) {
let mut counter = 0;
for y in 0..2 {
for x in 0..2 {
let pos = (x + y * 48) * 2 * 4;
place_2x2(&data[counter..], &mut output[pos..]);
counter += 4;
}
}
}
fn place_8x8(data: &[u16], output: &mut [u8]) {
let mut counter = 0;
for y in 0..2 {
for x in 0..2 {
let pos = (x + y * 48) * 4 * 4;
place_4x4(&data[counter..], &mut output[pos..]);
counter += 16;
}
}
}
impl SMDH {
#[must_use]
pub fn extract_rgba_icon(&self) -> [u8; 9216] {
let mut output = [0u8; 9216];
let data = &self.icons[0x480 / 2..];
let mut counter = 0;
for y in (0..48).step_by(8) {
for x in (0..48).step_by(8) {
let position = (x + y * 48) * 4;
place_8x8(&data[counter..], &mut output[position..]);
counter += 64;
}
}
output
}
}