use crate::format::utils;
use byteorder::{LittleEndian, WriteBytesExt};
use serde_derive::{Deserialize, Serialize};
use std::fs::File;
use std::io::Write;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct NacpLangEntry {
pub name: String,
pub author: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct NacpLangEntries {
#[serde(rename = "en-US")]
pub en_us: Option<NacpLangEntry>,
#[serde(rename = "en-GB")]
pub en_gb: Option<NacpLangEntry>,
pub ja: Option<NacpLangEntry>,
pub fr: Option<NacpLangEntry>,
pub de: Option<NacpLangEntry>,
#[serde(rename = "es-419")]
pub es_419: Option<NacpLangEntry>,
pub es: Option<NacpLangEntry>,
pub it: Option<NacpLangEntry>,
pub nl: Option<NacpLangEntry>,
#[serde(rename = "fr-CA")]
pub fr_ca: Option<NacpLangEntry>,
pub pt: Option<NacpLangEntry>,
pub ru: Option<NacpLangEntry>,
pub ko: Option<NacpLangEntry>,
#[serde(rename = "zh-TW")]
pub zh_tw: Option<NacpLangEntry>,
#[serde(rename = "zh-CN")]
pub zh_cn: Option<NacpLangEntry>,
}
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct NacpFile {
pub name: Option<String>,
pub author: Option<String>,
pub version: Option<String>,
pub title_id: Option<String>,
pub dlc_base_title_id: Option<String>,
pub lang: Option<NacpLangEntries>,
}
#[allow(clippy::len_without_is_empty)]
impl NacpFile {
pub fn from_file(input: &str) -> std::io::Result<Self> {
let file = File::open(input)?;
match serde_json::from_reader(file) {
Ok(res) => Ok(res),
Err(error) => Err(std::io::Error::from(error)),
}
}
fn write_lang_entry<T>(
&self,
output_writter: &mut T,
lang_entry: &NacpLangEntry,
) -> std::io::Result<()>
where
T: Write,
{
let name = &lang_entry.name;
let name_padding = 0x200 - name.len();
output_writter.write_all(name.as_bytes())?;
output_writter.write_all(&vec![0; name_padding])?;
let author = &lang_entry.author;
let author_padding = 0x100 - author.len();
output_writter.write_all(author.as_bytes())?;
output_writter.write_all(&vec![0; author_padding])?;
Ok(())
}
pub fn len(&self) -> usize {
0x4000
}
pub fn write<T>(&mut self, output_writter: &mut T) -> std::io::Result<()>
where
T: Write,
{
let mut name = self
.name
.clone()
.unwrap_or_else(|| "Unknown Application".to_string());
let mut version = self.version.clone().unwrap_or_else(|| "1.0.0".to_string());
let mut author = self
.author
.clone()
.unwrap_or_else(|| "Unknown Author".to_string());
let title_id = match &self.title_id {
None => 0,
Some(title_string) => {
u64::from_str_radix(title_string.as_str(), 16).expect("Invalid title_id provided!")
}
};
let dlc_base_title_id = match &self.dlc_base_title_id {
None => title_id + 0x1000,
Some(title_string) => {
u64::from_str_radix(title_string.as_str(), 16).expect("Invalid title_id provided!")
}
};
let lang_entries = &self.lang;
utils::check_string_or_truncate(&mut name, "name", 0x200);
utils::check_string_or_truncate(&mut version, "version", 0x10);
utils::check_string_or_truncate(&mut author, "author", 0x100);
let default_lang_entry = NacpLangEntry { name, author };
match lang_entries {
None => {
for _ in 0..16 {
self.write_lang_entry(output_writter, &default_lang_entry)?;
}
}
Some(data) => {
let lang_entries = data;
self.write_lang_entry(
output_writter,
&lang_entries
.en_us
.clone()
.unwrap_or_else(|| default_lang_entry.clone()),
)?;
self.write_lang_entry(
output_writter,
&lang_entries
.en_gb
.clone()
.unwrap_or_else(|| default_lang_entry.clone()),
)?;
self.write_lang_entry(
output_writter,
&lang_entries
.ja
.clone()
.unwrap_or_else(|| default_lang_entry.clone()),
)?;
self.write_lang_entry(
output_writter,
&lang_entries
.fr
.clone()
.unwrap_or_else(|| default_lang_entry.clone()),
)?;
self.write_lang_entry(
output_writter,
&lang_entries
.de
.clone()
.unwrap_or_else(|| default_lang_entry.clone()),
)?;
self.write_lang_entry(
output_writter,
&lang_entries
.es_419
.clone()
.unwrap_or_else(|| default_lang_entry.clone()),
)?;
self.write_lang_entry(
output_writter,
&lang_entries
.es
.clone()
.unwrap_or_else(|| default_lang_entry.clone()),
)?;
self.write_lang_entry(
output_writter,
&lang_entries
.it
.clone()
.unwrap_or_else(|| default_lang_entry.clone()),
)?;
self.write_lang_entry(
output_writter,
&lang_entries
.nl
.clone()
.unwrap_or_else(|| default_lang_entry.clone()),
)?;
self.write_lang_entry(
output_writter,
&lang_entries
.fr_ca
.clone()
.unwrap_or_else(|| default_lang_entry.clone()),
)?;
self.write_lang_entry(
output_writter,
&lang_entries
.pt
.clone()
.unwrap_or_else(|| default_lang_entry.clone()),
)?;
self.write_lang_entry(
output_writter,
&lang_entries
.ru
.clone()
.unwrap_or_else(|| default_lang_entry.clone()),
)?;
self.write_lang_entry(
output_writter,
&lang_entries
.ko
.clone()
.unwrap_or_else(|| default_lang_entry.clone()),
)?;
self.write_lang_entry(
output_writter,
&lang_entries
.zh_tw
.clone()
.unwrap_or_else(|| default_lang_entry.clone()),
)?;
self.write_lang_entry(
output_writter,
&lang_entries
.zh_cn
.clone()
.unwrap_or_else(|| default_lang_entry.clone()),
)?;
self.write_lang_entry(output_writter, &default_lang_entry)?;
}
}
output_writter.write_all(&[0; 0x38])?;
output_writter.write_u64::<LittleEndian>(title_id)?;
let mut unknown = Vec::new();
unknown.resize(0x20, 0xFF);
output_writter.write_all(&unknown)?;
let version_padding = 0x10 - version.len();
output_writter.write_all(version.as_bytes())?;
output_writter.write_all(&vec![0; version_padding])?;
output_writter.write_u64::<LittleEndian>(dlc_base_title_id)?;
output_writter.write_u64::<LittleEndian>(title_id)?;
output_writter.write_all(&[0; 0x30])?;
output_writter.write_u64::<LittleEndian>(title_id)?;
output_writter.write_u64::<LittleEndian>(title_id)?;
output_writter.write_all(&[0; 0x30])?;
output_writter.write_u64::<LittleEndian>(0)?;
output_writter.write_u64::<LittleEndian>(title_id)?;
let mut end_of_file = Vec::new();
end_of_file.resize(0xF00, 0);
output_writter.write_all(&end_of_file)?;
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn nacp_is_4000_size() {
let mut buf = Vec::new();
NacpFile::default().write(&mut buf).unwrap();
assert_eq!(buf.len(), 0x4000, "Nacp length is wrong");
}
}