use crate::format::utils::HexOrNum;
use crate::format::{nacp::NacpFile, npdm::KernelCapability, romfs::RomFs, utils};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use elf::types::{Machine, ProgramHeader, SectionHeader, EM_AARCH64, EM_ARM, PT_LOAD, SHT_NOTE};
use serde_derive::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::convert::{TryFrom, TryInto};
use std::fs::File;
use std::io::{self, Cursor, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};
use std::process;
pub struct NxoFile {
file: File,
machine: Machine,
text_segment: ProgramHeader,
rodata_segment: ProgramHeader,
data_segment: ProgramHeader,
bss_segment: Option<ProgramHeader>,
eh_frame_hdr_section: Option<SectionHeader>,
dynamic_section: Option<SectionHeader>,
dynstr_section: Option<SectionHeader>,
dynsym_section: Option<SectionHeader>,
build_id: Option<Vec<u8>>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct KipNpdm {
name: String,
title_id: HexOrNum,
main_thread_stack_size: HexOrNum,
main_thread_priority: u8,
default_cpu_id: u8,
process_category: u8,
flags: Option<u8>,
kernel_capabilities: Vec<KernelCapability>,
}
fn pad_segment(previous_segment_data: &mut Vec<u8>, offset: usize, segment: &ProgramHeader) {
let segment_vaddr = segment.vaddr as usize;
let segment_supposed_start = previous_segment_data.len() + offset;
if segment_vaddr > segment_supposed_start {
let real_size = previous_segment_data.len();
previous_segment_data.resize(real_size + (segment_vaddr - segment_supposed_start), 0);
}
}
fn write_build_id<T>(
build_id: &Option<Vec<u8>>,
output_writter: &mut T,
text_data: &[u8],
rodata: &[u8],
data: &[u8],
) -> std::io::Result<()>
where
T: Write,
{
match build_id {
Some(build_id) => {
let mut build_id_data = build_id.clone();
if build_id_data.len() > 0x30 {
println!(
"Warning: build-id is too big (0x{:x} > 0x30), the content will be shrink.",
build_id_data.len()
);
}
build_id_data.resize(0x30, 0);
output_writter.write_all(&build_id_data[0x10..])?;
}
None => {
let mut hasher = Sha256::default();
hasher.update(text_data);
hasher.update(rodata);
hasher.update(data);
output_writter.write_all(&hasher.finalize().as_slice()[..0x20])?;
}
}
Ok(())
}
fn write_mod0<T>(
nxo_file: &NxoFile,
offset: u32,
output_writter: &mut T,
bss_addr: u32,
bss_size: u32,
) -> std::io::Result<()>
where
T: Write,
{
output_writter.write_all(b"MOD0")?;
output_writter.write_u32::<LittleEndian>(
nxo_file
.dynamic_section
.as_ref()
.map(|v| v.addr as u32 - offset)
.unwrap_or(0),
)?;
output_writter.write_u32::<LittleEndian>(bss_addr - offset)?;
output_writter.write_u32::<LittleEndian>(bss_addr + bss_size - offset)?;
let (eh_frame_hdr_addr, eh_frame_hdr_size) = nxo_file
.eh_frame_hdr_section
.as_ref()
.map(|v| (v.addr, v.size))
.unwrap_or((0, 0));
output_writter.write_u32::<LittleEndian>(eh_frame_hdr_addr as u32 - offset)?;
output_writter
.write_u32::<LittleEndian>(eh_frame_hdr_addr as u32 + eh_frame_hdr_size as u32 - offset)?;
output_writter.write_u32::<LittleEndian>(0)?;
Ok(())
}
impl NxoFile {
pub fn from_elf(input: &str) -> std::io::Result<Self> {
let path = PathBuf::from(input);
let mut file = File::open(path)?;
let elf_file = elf::File::open_stream(&mut file).unwrap();
if elf_file.ehdr.machine != EM_AARCH64 && elf_file.ehdr.machine != EM_ARM {
println!("Error: Invalid ELF file (expected ARM or AArch64 machine)");
process::exit(1)
}
let sections = &elf_file.sections;
let phdrs: Vec<ProgramHeader> = elf_file.phdrs.to_vec();
let text_segment = phdrs.get(0).unwrap_or_else(|| {
println!("Error: .text not found in ELF file");
process::exit(1)
});
let rodata_segment = phdrs.get(1).unwrap_or_else(|| {
println!("Error: .rodata not found in ELF file");
process::exit(1)
});
let data_segment = phdrs.get(2).unwrap_or_else(|| {
println!("Error: .data not found in ELF file");
process::exit(1)
});
let bss_segment = match phdrs.get(3) {
Some(s) => {
if s.progtype == PT_LOAD {
Some(*s)
} else {
None
}
}
None => None,
};
let mut build_id = None;
let mut dynamic_section = None;
let mut dynstr_section = None;
let mut dynsym_section = None;
let mut eh_frame_hdr_section = None;
for section in sections {
if section.shdr.shtype == SHT_NOTE {
let mut data = Cursor::new(section.data.clone());
data.seek(SeekFrom::Start(0x8)).unwrap();
let n_type = data.read_u32::<LittleEndian>().unwrap();
if n_type == 0x3 {
build_id = Some(data.into_inner());
}
}
match &*section.shdr.name {
".dynamic" => dynamic_section = Some(section.shdr.clone()),
".dynstr" => dynstr_section = Some(section.shdr.clone()),
".dynsym" => dynsym_section = Some(section.shdr.clone()),
".eh_frame_hdr" => eh_frame_hdr_section = Some(section.shdr.clone()),
_ => (),
}
}
Ok(NxoFile {
file,
machine: elf_file.ehdr.machine,
text_segment: *text_segment,
rodata_segment: *rodata_segment,
data_segment: *data_segment,
bss_segment,
build_id,
dynamic_section,
dynstr_section,
dynsym_section,
eh_frame_hdr_section,
})
}
pub fn write_nro<T>(
&mut self,
output_writter: &mut T,
romfs: Option<RomFs>,
icon: Option<&str>,
nacp: Option<NacpFile>,
) -> std::io::Result<()>
where
T: Write,
{
let text_segment = &self.text_segment;
let rodata_segment = &self.rodata_segment;
let data_segment = &self.data_segment;
let mut code = utils::get_segment_data(&mut self.file, text_segment)?;
let mut rodata = utils::get_segment_data(&mut self.file, rodata_segment)?;
let mut data = utils::get_segment_data(&mut self.file, data_segment)?;
utils::add_padding(&mut code, 0xFFF);
utils::add_padding(&mut rodata, 0xFFF);
utils::add_padding(&mut data, 0xFFF);
pad_segment(&mut code, 0, rodata_segment);
pad_segment(&mut rodata, code.len(), data_segment);
if let Some(segment) = self.bss_segment {
pad_segment(&mut data, code.len() + rodata.len(), &segment);
}
let total_len: u32 = (code.len() + rodata.len() + data.len()) as u32;
output_writter.write_all(&code[..0x10])?;
output_writter.write_all(b"NRO0")?;
output_writter.write_u32::<LittleEndian>(0)?;
output_writter.write_u32::<LittleEndian>(total_len)?;
output_writter.write_u32::<LittleEndian>(0)?;
let mut file_offset = 0;
let code_size = code.len() as u32;
output_writter.write_u32::<LittleEndian>(file_offset)?;
output_writter.write_u32::<LittleEndian>(code_size)?;
file_offset += code_size;
let rodata_offset = file_offset;
let rodata_size = rodata.len() as u32;
output_writter.write_u32::<LittleEndian>(file_offset)?;
output_writter.write_u32::<LittleEndian>(rodata_size)?;
file_offset += rodata_size;
let data_offset = file_offset;
let data_size = data.len() as u32;
output_writter.write_u32::<LittleEndian>(file_offset)?;
output_writter.write_u32::<LittleEndian>(data_size)?;
file_offset += data_size;
let (bss_start, bss_size) = match self.bss_segment {
Some(segment) => {
if segment.vaddr != u64::from(file_offset) {
println!(
"Warning: possible misalign bss\n.bss addr: 0x{:x}\nexpected offset: 0x{:x}",
segment.vaddr, file_offset);
}
output_writter
.write_u32::<LittleEndian>(((segment.memsz + 0xFFF) & !0xFFF) as u32)?;
(
segment.vaddr as u32,
((segment.memsz + 0xFFF) & !0xFFF) as u32,
)
}
_ => {
let data_segment_size = (data_segment.filesz + 0xFFF) & !0xFFF;
let bss_size = if data_segment.memsz > data_segment_size {
(((data_segment.memsz - data_segment_size) + 0xFFF) & !0xFFF) as u32
} else {
0
};
output_writter.write_u32::<LittleEndian>(bss_size)?;
(
data_segment.vaddr as u32 + data_segment.memsz as u32,
bss_size,
)
}
};
output_writter.write_u32::<LittleEndian>(0)?;
write_build_id(&self.build_id, output_writter, &code, &rodata, &data)?;
output_writter.write_u32::<LittleEndian>(0)?;
output_writter.write_u32::<LittleEndian>(0)?;
output_writter.write_u64::<LittleEndian>(0)?;
output_writter.write_u32::<LittleEndian>(
self.dynstr_section
.as_ref()
.map(|v| u32::try_from(v.addr).unwrap())
.unwrap_or(0),
)?;
output_writter.write_u32::<LittleEndian>(
self.dynstr_section
.as_ref()
.map(|v| u32::try_from(v.size).unwrap())
.unwrap_or(0),
)?;
output_writter.write_u32::<LittleEndian>(
self.dynsym_section
.as_ref()
.map(|v| u32::try_from(v.addr).unwrap())
.unwrap_or(0),
)?;
output_writter.write_u32::<LittleEndian>(
self.dynsym_section
.as_ref()
.map(|v| u32::try_from(v.size).unwrap())
.unwrap_or(0),
)?;
let module_offset = u32::from_le_bytes(code[4..8].try_into().unwrap()) as usize;
if module_offset != 0
&& !((0x80..code_size).contains(&(module_offset as u32))
|| (rodata_offset..data_offset).contains(&(module_offset as u32))
|| (data_offset..file_offset).contains(&(module_offset as u32)))
{
panic!("Invalid module offset {}", module_offset)
}
if (0x80..code_size).contains(&(module_offset as u32))
&& &code[module_offset..module_offset + 4] != b"MOD0"
{
output_writter.write_all(&code[0x80..module_offset])?;
write_mod0(
self,
module_offset as u32,
output_writter,
bss_start,
bss_size,
)?;
output_writter.write_all(&code[module_offset + 0x1C..])?;
} else {
output_writter.write_all(&code[0x80..])?;
}
if (rodata_offset..data_offset).contains(&(module_offset as u32))
&& &rodata
[module_offset - rodata_offset as usize..module_offset - rodata_offset as usize + 4]
!= b"MOD0"
{
let rodata_module_offset = module_offset - rodata_offset as usize;
output_writter.write_all(&rodata[..rodata_module_offset])?;
write_mod0(
self,
module_offset as u32,
output_writter,
bss_start,
bss_size,
)?;
output_writter.write_all(&rodata[rodata_module_offset + 0x1C..])?;
} else {
output_writter.write_all(&rodata)?;
}
if (data_offset..file_offset).contains(&(module_offset as u32))
&& &data[module_offset - data_offset as usize..module_offset - data_offset as usize + 4]
!= b"MOD0"
{
let data_module_offset = module_offset - data_offset as usize;
output_writter.write_all(&data[..data_module_offset])?;
write_mod0(
self,
module_offset as u32,
output_writter,
bss_start,
bss_size,
)?;
output_writter.write_all(&data[data_module_offset + 0x1C..])?;
} else {
output_writter.write_all(&data)?;
}
if let (None, None, None) = (&icon, &romfs, &nacp) {
return Ok(());
}
output_writter.write_all(b"ASET")?;
output_writter.write_u32::<LittleEndian>(0)?;
let mut offset = 8 + 16 + 16 + 16;
let icon_len = if let Some(icon) = &icon {
let icon_len = Path::new(icon).metadata()?.len();
output_writter.write_u64::<LittleEndian>(offset)?;
output_writter.write_u64::<LittleEndian>(icon_len)?;
icon_len
} else {
output_writter.write_u64::<LittleEndian>(0)?;
output_writter.write_u64::<LittleEndian>(0)?;
0
};
offset += icon_len;
let nacp_len = if let Some(nacp) = &nacp {
let nacp_len = nacp.len() as u64;
output_writter.write_u64::<LittleEndian>(offset)?;
output_writter.write_u64::<LittleEndian>(nacp_len)?;
nacp_len
} else {
output_writter.write_u64::<LittleEndian>(0)?;
output_writter.write_u64::<LittleEndian>(0)?;
0
};
offset += nacp_len;
if let Some(romfs) = &romfs {
output_writter.write_u64::<LittleEndian>(offset)?;
output_writter.write_u64::<LittleEndian>(romfs.len() as u64)?;
} else {
output_writter.write_u64::<LittleEndian>(0)?;
output_writter.write_u64::<LittleEndian>(0)?;
};
if let Some(icon) = icon {
assert_eq!(
io::copy(&mut File::open(icon)?, output_writter)?,
icon_len,
"Icon changed while building."
);
}
if let Some(mut nacp) = nacp {
nacp.write(output_writter)?;
}
if let Some(romfs) = romfs {
romfs.write(output_writter)?;
}
Ok(())
}
pub fn write_nso<T>(&mut self, output_writter: &mut T) -> std::io::Result<()>
where
T: Write,
{
let text_segment = &self.text_segment;
let rodata_segment = &self.rodata_segment;
let data_segment = &self.data_segment;
let mut code = utils::get_segment_data(&mut self.file, text_segment)?;
let mut rodata = utils::get_segment_data(&mut self.file, rodata_segment)?;
let mut data = utils::get_segment_data(&mut self.file, data_segment)?;
utils::add_padding(&mut code, 0xFFF);
utils::add_padding(&mut rodata, 0xFFF);
utils::add_padding(&mut data, 0xFFF);
if let Some(segment) = self.bss_segment {
pad_segment(&mut data, data_segment.vaddr as usize, &segment);
}
output_writter.write_all(b"NSO0")?;
output_writter.write_u32::<LittleEndian>(0)?;
output_writter.write_u32::<LittleEndian>(0)?;
output_writter.write_u32::<LittleEndian>(0x3F)?;
let mut file_offset = 0x100;
let code_size = code.len() as u32;
let compressed_code = utils::compress_lz4(&mut code)?;
let compressed_code_size = compressed_code.len() as u32;
output_writter.write_u32::<LittleEndian>(file_offset as u32)?;
output_writter.write_u32::<LittleEndian>(text_segment.vaddr as u32)?;
output_writter.write_u32::<LittleEndian>(code_size as u32)?;
output_writter.write_u32::<LittleEndian>(0)?;
file_offset += compressed_code_size;
let rodata_size = rodata.len() as u32;
let compressed_rodata = utils::compress_lz4(&mut rodata)?;
let compressed_rodata_size = compressed_rodata.len() as u32;
output_writter.write_u32::<LittleEndian>(file_offset as u32)?;
output_writter.write_u32::<LittleEndian>(rodata_segment.vaddr as u32)?;
output_writter.write_u32::<LittleEndian>(rodata_size as u32)?;
output_writter.write_u32::<LittleEndian>(0)?;
file_offset += compressed_rodata_size;
let data_size = data.len() as u32;
let compressed_data = utils::compress_lz4(&mut data)?;
let compressed_data_size = compressed_data.len() as u32;
let uncompressed_data_size = data.len() as u64;
output_writter.write_u32::<LittleEndian>(file_offset as u32)?;
output_writter.write_u32::<LittleEndian>(data_segment.vaddr as u32)?;
output_writter.write_u32::<LittleEndian>(data_size as u32)?;
match self.bss_segment {
Some(segment) => {
let memory_offset = data_segment.vaddr + uncompressed_data_size;
if segment.vaddr != memory_offset {
println!(
"Warning: possible misalign bss\n.bss addr: 0x{:x}\nexpected offset: 0x{:x}",
segment.vaddr, memory_offset);
}
output_writter
.write_u32::<LittleEndian>(((segment.memsz + 0xFFF) & !0xFFF) as u32)?;
}
_ => {
output_writter
.write_u32::<LittleEndian>((data_segment.memsz - data_segment.filesz) as u32)?;
}
}
write_build_id(&self.build_id, output_writter, &code, &rodata, &data)?;
output_writter.write_u32::<LittleEndian>(compressed_code_size)?;
output_writter.write_u32::<LittleEndian>(compressed_rodata_size)?;
output_writter.write_u32::<LittleEndian>(compressed_data_size)?;
output_writter.write_u32::<LittleEndian>(0)?;
output_writter.write_u64::<LittleEndian>(0)?;
output_writter.write_u64::<LittleEndian>(0)?;
output_writter.write_u64::<LittleEndian>(0)?;
output_writter.write_u64::<LittleEndian>(0)?;
output_writter.write_u32::<LittleEndian>(
self.dynstr_section
.as_ref()
.map(|v| u32::try_from(v.addr).unwrap())
.unwrap_or(0),
)?;
output_writter.write_u32::<LittleEndian>(
self.dynstr_section
.as_ref()
.map(|v| u32::try_from(v.size).unwrap())
.unwrap_or(0),
)?;
output_writter.write_u32::<LittleEndian>(
self.dynsym_section
.as_ref()
.map(|v| u32::try_from(v.addr).unwrap())
.unwrap_or(0),
)?;
output_writter.write_u32::<LittleEndian>(
self.dynsym_section
.as_ref()
.map(|v| u32::try_from(v.size).unwrap())
.unwrap_or(0),
)?;
let text_sum = utils::calculate_sha256(&code)?;
output_writter.write_all(&text_sum)?;
let rodata_sum = utils::calculate_sha256(&rodata)?;
output_writter.write_all(&rodata_sum)?;
let data_sum = utils::calculate_sha256(&data)?;
output_writter.write_all(&data_sum)?;
output_writter.write_all(&compressed_code)?;
output_writter.write_all(&compressed_rodata)?;
output_writter.write_all(&compressed_data)?;
Ok(())
}
pub fn write_kip1<T>(&mut self, output_writer: &mut T, npdm: &KipNpdm) -> std::io::Result<()>
where
T: Write,
{
output_writer.write_all(b"KIP1")?;
let mut name: Vec<u8> = npdm.name.clone().into();
name.resize(12, 0);
output_writer.write_all(&name[..])?;
output_writer.write_u64::<LittleEndian>(npdm.title_id.0)?; output_writer.write_u32::<LittleEndian>(u32::from(npdm.process_category))?;
output_writer.write_u8(npdm.main_thread_priority)?;
output_writer.write_u8(npdm.default_cpu_id)?;
output_writer.write_u8(0)?; if let Some(flags) = npdm.flags {
output_writer.write_u8(flags)?;
} else if self.machine == EM_AARCH64 {
output_writer.write_u8(0b0011_1111)?;
} else if self.machine == EM_ARM {
output_writer.write_u8(0b0010_0111)?;
} else {
unimplemented!("Unknown machine type");
}
let mut segment_data = utils::get_segment_data(&mut self.file, &self.text_segment)?;
let text_data = utils::compress_blz(&mut segment_data).unwrap();
let mut segment_data = utils::get_segment_data(&mut self.file, &self.rodata_segment)?;
let rodata_data = utils::compress_blz(&mut segment_data).unwrap();
let mut segment_data = utils::get_segment_data(&mut self.file, &self.data_segment)?;
let data_data = utils::compress_blz(&mut segment_data).unwrap();
write_kip_segment_header(output_writer, &self.text_segment, 0, text_data.len() as u32)?;
write_kip_segment_header(
output_writer,
&self.rodata_segment,
u32::try_from(npdm.main_thread_stack_size.0)
.expect("Exected main_thread_stack_size to be an u32"),
rodata_data.len() as u32,
)?;
write_kip_segment_header(output_writer, &self.data_segment, 0, data_data.len() as u32)?;
if let Some(segment) = self.bss_segment {
output_writer.write_u32::<LittleEndian>(
u32::try_from(segment.vaddr).expect("BSS vaddr too big"),
)?;
output_writer.write_u32::<LittleEndian>(
u32::try_from(segment.memsz).expect("BSS memsize too big"),
)?;
} else {
let data_segment_size = (self.data_segment.filesz + 0xFFF) & !0xFFF;
let bss_size = if self.data_segment.memsz > data_segment_size {
(((self.data_segment.memsz - data_segment_size) + 0xFFF) & !0xFFF) as u32
} else {
0
};
output_writer.write_u32::<LittleEndian>(
u32::try_from(self.data_segment.vaddr + data_segment_size).unwrap(),
)?;
output_writer.write_u32::<LittleEndian>(bss_size)?;
}
output_writer.write_u32::<LittleEndian>(0)?;
output_writer.write_u32::<LittleEndian>(0)?;
for _ in 4..6 {
output_writer.write_u32::<LittleEndian>(0)?;
output_writer.write_u32::<LittleEndian>(0)?;
output_writer.write_u32::<LittleEndian>(0)?;
output_writer.write_u32::<LittleEndian>(0)?;
}
let caps = npdm
.kernel_capabilities
.iter()
.map(|v| v.encode())
.flatten()
.collect::<Vec<u32>>();
assert!(
caps.len() < 0x20,
"kernel_capabilities should have less than 0x20 entries!"
);
unsafe {
output_writer.write_all(std::slice::from_raw_parts(
caps.as_ptr() as *const u8,
caps.len() * 4,
))?;
}
output_writer.write_all(&vec![0xFF; (0x20 - caps.len()) * 4])?;
output_writer.write_all(&text_data)?;
output_writer.write_all(&rodata_data)?;
output_writer.write_all(&data_data)?;
Ok(())
}
}
pub fn write_kip_segment_header<T>(
output_writer: &mut T,
segment: &ProgramHeader,
attributes: u32,
compressed_size: u32,
) -> std::io::Result<()>
where
T: Write,
{
output_writer
.write_u32::<LittleEndian>(u32::try_from(segment.vaddr).expect("vaddr too big"))?;
output_writer
.write_u32::<LittleEndian>(u32::try_from(segment.filesz).expect("memsz too big"))?;
output_writer.write_u32::<LittleEndian>(compressed_size)?;
output_writer.write_u32::<LittleEndian>(attributes)?;
Ok(())
}