From fa963cda82ab71ba9824e16dd9e4ba046e5673db Mon Sep 17 00:00:00 2001 From: roblabla Date: Sat, 15 Sep 2018 13:01:32 +0000 Subject: [PATCH 1/4] Fix broken NACP - NACP files have 16 lang entries (with the last one being unused) - Fix copy pasta fail, last lang is chinese. --- src/format/nacp.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/format/nacp.rs b/src/format/nacp.rs index 88dc14a..9cf8b02 100644 --- a/src/format/nacp.rs +++ b/src/format/nacp.rs @@ -125,7 +125,7 @@ impl NacpFile { let default_lang_entry = NacpLangEntry { name, author }; match lang_entries { None => { - for _ in 0..15 { + for _ in 0..16 { self.write_lang_entry(output_writter, &default_lang_entry)?; } } @@ -234,10 +234,12 @@ impl NacpFile { self.write_lang_entry( output_writter, &lang_entries - .zh_tw + .zh_cn .clone() .unwrap_or(default_lang_entry.clone()), )?; + // There are 16 entries. One is missing :eyes: + self.write_lang_entry(output_writter, &default_lang_entry)?; } } @@ -279,3 +281,15 @@ impl NacpFile { 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"); + } +} From a12e06af300ec93dc75c0da06b5fbd52bcf5e091 Mon Sep 17 00:00:00 2001 From: roblabla Date: Sat, 15 Sep 2018 13:03:20 +0000 Subject: [PATCH 2/4] ASET offsets are relative to the start of the ASET section. It was previously assumed to be an absolute offset in the file. --- src/format/nxo.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/format/nxo.rs b/src/format/nxo.rs index 8997aa1..4eda304 100644 --- a/src/format/nxo.rs +++ b/src/format/nxo.rs @@ -234,7 +234,7 @@ impl NxoFile { output_writter.write_u32::(0)?; // version // Offset to the next available region. - let mut offset = total_len as u64 + 8 + 16 + 16 + 16; + let mut offset = 8 + 16 + 16 + 16; let icon_len = if let Some(icon) = &icon { // TODO: Check if icon is a 256x256 JPEG. Convert it if it isn't? From a4fb879f799a342981d57a479eef698757f6303b Mon Sep 17 00:00:00 2001 From: roblabla Date: Sat, 15 Sep 2018 13:13:28 +0000 Subject: [PATCH 3/4] Refactor RomFs to allow pushing new files after it was built. - RomFs::from_directory now takes a &Path. - NxoFile::write now takes an Option instead of a path. - Add RomFs::push_file, allowing the user to inject new files into a built romfs. Current implementation isn't great: arrays are sorted multiple times, and offsets are recalculated all the time. --- src/bin/cargo-nro.rs | 12 ++- src/bin/linkle_clap.rs | 11 ++- src/format/nxo.rs | 7 +- src/format/romfs.rs | 189 +++++++++++++++++++++++++++++++---------- 4 files changed, 163 insertions(+), 56 deletions(-) diff --git a/src/bin/cargo-nro.rs b/src/bin/cargo-nro.rs index fc09d1d..18a8720 100644 --- a/src/bin/cargo-nro.rs +++ b/src/bin/cargo-nro.rs @@ -12,7 +12,7 @@ use std::env::{self, VarError}; use std::process::{Command, Stdio}; use std::path::{Path, PathBuf}; use std::fs::File; -use linkle::format::{nxo::NxoFile, nacp::NacpFile}; +use linkle::format::{romfs::RomFs, nxo::NxoFile, nacp::NacpFile}; use cargo_metadata::{Package, Message}; use clap::{Arg, App}; use url::Url; @@ -154,9 +154,6 @@ fn main() { None }; - let romfs = romfs.map(|v| v.to_string_lossy().into_owned()); - let romfs = romfs.as_ref().map(|v: &String| v.as_ref()); - let icon_file = if let Some(icon) = target_metadata.icon { let icon_path = root.join(icon); if !icon_path.is_file() { @@ -181,8 +178,15 @@ fn main() { nacp.title_id = target_metadata.title_id; } + let romfs = if let Some(romfs) = romfs { + Some(RomFs::from_directory(&romfs).unwrap()) + } else { + None + }; + let mut new_name = PathBuf::from(artifact.filenames[0].clone()); assert!(new_name.set_extension("nro")); + NxoFile::from_elf(&artifact.filenames[0]).unwrap() .write_nro(&mut File::create(new_name.clone()).unwrap(), romfs, diff --git a/src/bin/linkle_clap.rs b/src/bin/linkle_clap.rs index a9925d3..562a3dc 100644 --- a/src/bin/linkle_clap.rs +++ b/src/bin/linkle_clap.rs @@ -6,13 +6,18 @@ extern crate linkle; use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; use std::fs::OpenOptions; +use std::path::Path; use std::process; fn create_nxo(format: &str, matches: &ArgMatches) -> std::io::Result<()> { let input_file = matches.value_of("INPUT_FILE").unwrap(); let output_file = matches.value_of("OUTPUT_FILE").unwrap(); - let romfs_dir = matches.value_of("ROMFS_PATH"); let icon_file = matches.value_of("ICON_PATH"); + let romfs_dir = if let Some(romfs_path) = matches.value_of_os("ROMFS_PATH") { + Some(linkle::format::romfs::RomFs::from_directory(Path::new(romfs_path))?) + } else { + None + }; let nacp_file = if let Some(nacp_path) = matches.value_of("NACP_FILE") { Some(linkle::format::nacp::NacpFile::from_file(nacp_path)?) } else { @@ -50,9 +55,9 @@ fn create_nacp(matches: &ArgMatches) -> std::io::Result<()> { } fn create_romfs(matches: &ArgMatches) -> std::io::Result<()> { - let input_directory = matches.value_of("INPUT_DIRECTORY").unwrap(); + let input_directory = matches.value_of_os("INPUT_DIRECTORY").unwrap(); let output_file = matches.value_of("OUTPUT_FILE").unwrap(); - let romfs = linkle::format::romfs::RomFs::from_directory(input_directory)?; + let romfs = linkle::format::romfs::RomFs::from_directory(Path::new(input_directory))?; let mut option = OpenOptions::new(); let output_option = option.write(true).create(true).truncate(true); romfs.write(&mut output_option.open(output_file)?)?; diff --git a/src/format/nxo.rs b/src/format/nxo.rs index 4eda304..172277e 100644 --- a/src/format/nxo.rs +++ b/src/format/nxo.rs @@ -120,7 +120,7 @@ impl NxoFile { }) } - pub fn write_nro(&mut self, output_writter: &mut T, romfs: Option<&str>, icon: Option<&str>, nacp: Option) -> std::io::Result<()> + pub fn write_nro(&mut self, output_writter: &mut T, romfs: Option, icon: Option<&str>, nacp: Option) -> std::io::Result<()> where T: Write, { @@ -263,15 +263,12 @@ impl NxoFile { offset += nacp_len; - let romfs = if let Some(romfs) = &romfs { - let romfs = RomFs::from_directory(romfs)?; + if let Some(romfs) = &romfs { output_writter.write_u64::(offset)?; output_writter.write_u64::(romfs.len() as u64)?; - Some(romfs) } else { output_writter.write_u64::(0)?; output_writter.write_u64::(0)?; - None }; if let Some(icon) = icon { diff --git a/src/format/romfs.rs b/src/format/romfs.rs index 9cf394d..ab4fa57 100644 --- a/src/format/romfs.rs +++ b/src/format/romfs.rs @@ -3,11 +3,12 @@ use std::rc::{Rc, Weak}; use std::io::{self, Write, Cursor}; use std::fs::{self, File}; use std::cell::RefCell; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use byteorder::{WriteBytesExt, LE}; #[derive(Debug)] struct RomFsDirEntCtx { + // Only used in `RomFs::from_directory` system_path: PathBuf, name: String, entry_offset: u32, @@ -16,6 +17,19 @@ struct RomFsDirEntCtx { file: Vec>>, } +impl RomFsDirEntCtx { + fn internal_path(&self) -> String { + let mut path = self.name.clone(); + let mut cur = self.parent.upgrade().unwrap(); + while cur.borrow().name != "" { + path = cur.borrow().name.clone() + "/" + &path; + let new_cur = cur.borrow().parent.upgrade().unwrap(); + cur = new_cur; + } + path + } +} + #[derive(Debug)] struct RomFsFileEntCtx { system_path: PathBuf, @@ -26,6 +40,14 @@ struct RomFsFileEntCtx { parent: Weak>, } +impl RomFsFileEntCtx { + fn internal_path(&self) -> String { + let parent = self.parent.upgrade().unwrap(); + let parent_borrow = parent.borrow(); + parent_borrow.internal_path() + "/" + &self.name + } +} + #[repr(C)] #[derive(Debug)] struct RomFsDirEntryHdr { @@ -59,6 +81,23 @@ impl RomFsDirEntCtx { file: vec![] })) } + + fn new_root() -> Rc> { + // We'll want the parent to point to itself, so let's first + // set it to an unbound Weak, and set it to itself + // after creation. + let root = Rc::new(RefCell::new(RomFsDirEntCtx { + system_path: PathBuf::from(""), + name: String::from(""), + entry_offset: 0, + parent: Weak::new(), + child: vec![], + file: vec![] + })); + let weak = Rc::downgrade(&root); + root.borrow_mut().parent = weak; + root + } } // From https://www.3dbrew.org/wiki/RomFS @@ -116,30 +155,69 @@ pub struct RomFs { } impl RomFs { - pub fn from_directory(path: &str) -> io::Result { - // Stack of directories to visit. We'll iterate over it. When finding - // new directories, we'll push them to this stack, so that iteration may - // continue. This avoids doing recursive functions (which runs the risk - // of stack overflowing). - let mut dirs = vec![]; - - // First, let's create our root folder. We'll want the parent to be - // itself, so let's first set it to an unbound Weak, and set it to itself - // after creation. - let root_folder = RomFsDirEntCtx::new(Weak::new(), PathBuf::from(path)); - { - let mut root_folder_borrow = root_folder.borrow_mut(); - - root_folder_borrow.parent = Rc::downgrade(&root_folder.clone()); - // The name is empty for the root dir. - root_folder_borrow.name = String::from(""); + // Internal path + pub fn push_file(&mut self, file_path: &Path, internal_path: String) -> io::Result<()> { + let mut parent = self.dirs[0].clone(); + + let mut components = internal_path.split("/").peekable(); + while let Some(component) = components.next() { + if components.peek().is_none() { + let metadata = file_path.metadata()?; + // Handling last component. Add the file. + let file_to_add = Rc::new(RefCell::new(RomFsFileEntCtx { + system_path: PathBuf::from(file_path), + name: String::from(component), + entry_offset: 0, + offset: 0, + size: metadata.len(), + parent: Rc::downgrade(&parent), + })); + self.files.push(file_to_add.clone()); + parent.borrow_mut().file.push(file_to_add.clone()); + parent.borrow_mut().file.sort_by_key(|v| v.borrow().name.clone()); + + self.file_table_size += mem::size_of::() as u64 + align64(file_to_add.borrow().name.len() as u64, 4); + } else { + // Handling a parent component. Find the directory, create if it doesn't exist. + if component == "" { + continue; + } + let new_parent = if let Some(child) = parent.borrow().child.iter().find(|v| v.borrow().name == component) { + child.clone() + } else { + // system_path is not used outside from_directory. It's okay if it doesn't + // point to something "safe" (or to anything at all) + let child = Rc::new(RefCell::new(RomFsDirEntCtx { + system_path: PathBuf::from(""), + name: String::from(component), + entry_offset: 0, + parent: Rc::downgrade(&parent), + child: vec![], + file: vec![] + })); + self.dirs.push(child.clone()); + parent.borrow_mut().child.push(child.clone()); + parent.borrow_mut().child.sort_by_key(|v| v.borrow().name.clone()); + + self.dir_table_size += mem::size_of::() as u64 + align64(child.borrow().name.len() as u64, 4); + child + }; + parent = new_parent; + } } + self.files.sort_by_key(|v| v.borrow().internal_path()); + self.dirs.sort_by_key(|v| v.borrow().internal_path()); + self.calculate_offsets(); + Ok(()) + } - // Let's build our context. This will be returned. It contains the graph - // of files/directories, and some meta-information that will be used to - // write the romfs file afterwards. + pub fn empty() -> RomFs { + // First, let's create our root folder. + let root_folder = RomFsDirEntCtx::new_root(); + + // And now, build the empty context, with just the root directory in it. let mut ctx = RomFs { - dirs: vec![], + dirs: vec![root_folder], files: vec![], // We have the root dir already. dir_table_size: mem::size_of::() as u64, // Root Dir @@ -147,15 +225,32 @@ impl RomFs { file_partition_size: 0, }; - // Let's start iterating. - ctx.dirs.push(root_folder.clone()); - dirs.push(root_folder); + ctx.calculate_offsets(); + + ctx + } + + pub fn from_directory(path: &Path) -> io::Result { + // Stack of directories to visit. We'll iterate over it. When finding + // new directories, we'll push them to this stack, so that iteration may + // continue. This avoids doing recursive functions (which runs the risk + // of stack overflowing). + let mut dirs = vec![]; + + // Let's build our context. This will be returned. It contains the graph + // of files/directories, and some meta-information that will be used to + // write the romfs file afterwards. + let mut ctx = RomFs::empty(); + + // Set the system_path of the root directory. + ctx.dirs[0].borrow_mut().system_path = PathBuf::from(path); + + // Let's start iterating with the root directory. + dirs.push(ctx.dirs[0].clone()); while let Some(parent_dir) = dirs.pop() { let path = parent_dir.borrow().system_path.clone(); - let cur_dir_idx = ctx.dirs.len(); - for entry in fs::read_dir(path)? { let entry = entry?; let file_type = entry.file_type()?; @@ -198,22 +293,37 @@ impl RomFs { } parent_dir.borrow_mut().child.sort_by_key(|v| v.borrow().name.clone()); parent_dir.borrow_mut().file.sort_by_key(|v| v.borrow().name.clone()); - ctx.dirs[cur_dir_idx..].sort_by_key(|v| v.borrow().name.clone()); } - ctx.files.sort_by_key(|v| v.borrow().system_path.to_string_lossy().into_owned()); + ctx.files.sort_by_key(|v| v.borrow().internal_path()); + ctx.dirs.sort_by_key(|v| v.borrow().internal_path()); + + ctx.calculate_offsets(); + + Ok(ctx) + } + + pub fn len(&self) -> usize { + (align64(ROMFS_FILEPARTITION_OFS + self.file_partition_size, 4) + + romfs_get_hash_table_count(self.dirs.len() * mem::size_of::()) as u64 + + self.dir_table_size + + romfs_get_hash_table_count(self.files.len() * mem::size_of::()) as u64 + + self.file_table_size) as usize + } + fn calculate_offsets(&mut self) { // Calculate file offset and file partition size. let mut entry_offset = 0; - for file in ctx.files.iter_mut() { + self.file_partition_size = 0; + for file in self.files.iter_mut() { // Files have to start aligned at 0x10. We do this at the start to // avoid useless padding after the last file. - ctx.file_partition_size = align64(ctx.file_partition_size, 0x10); + self.file_partition_size = align64(self.file_partition_size, 0x10); // Update the data section size and set the file offset in the data // section. - file.borrow_mut().offset = ctx.file_partition_size; - ctx.file_partition_size += file.borrow().size; + file.borrow_mut().offset = self.file_partition_size; + self.file_partition_size += file.borrow().size; // Set the file offset in the file table section. file.borrow_mut().entry_offset = entry_offset; @@ -222,21 +332,12 @@ impl RomFs { // Calculate directory offsets. let mut entry_offset = 0; - for dir in ctx.dirs.iter_mut() { + for dir in self.dirs.iter_mut() { dir.borrow_mut().entry_offset = entry_offset; entry_offset += mem::size_of::() as u32 + align32(dir.borrow().name.len() as u32, 4); } - - Ok(ctx) } - pub fn len(&self) -> usize { - (align64(ROMFS_FILEPARTITION_OFS + self.file_partition_size, 4) + - romfs_get_hash_table_count(self.dirs.len() * mem::size_of::()) as u64 + - self.dir_table_size + - romfs_get_hash_table_count(self.files.len() * mem::size_of::()) as u64 + - self.file_table_size) as usize - } pub fn write(&self, to: &mut Write) -> io::Result<()> { const ROMFS_ENTRY_EMPTY: u32 = 0xFFFFFFFF; @@ -274,7 +375,7 @@ impl RomFs { let dir = dir.borrow(); let parent = dir.parent.upgrade().unwrap(); let parent = parent.borrow(); - let sibling = parent.child.windows(2).find(|window| window[0].borrow().system_path == dir.system_path).map(|window| window[1].borrow().entry_offset); + let sibling = parent.child.windows(2).find(|window| window[0].borrow().internal_path() == dir.internal_path()).map(|window| window[1].borrow().entry_offset); let hash = calc_path_hash(parent.entry_offset, &dir.name); let mut cursor = Cursor::new(&mut dir_table[dir.entry_offset as usize..]); From 3c25712e5ab70d361fc10a8580af3bbbd5a2c678 Mon Sep 17 00:00:00 2001 From: roblabla Date: Sat, 15 Sep 2018 13:39:39 +0000 Subject: [PATCH 4/4] Insert a debuginfo.elf file in the RomFs when running cargo nro This file is an ELF containing only the debug section of the original ELF. It should be similar to having ran objcopy --keep-only-debug on the original elf (although it is reimplemented in rust, and is not yet perfect). --- Cargo.lock | 48 ++++++++++++++++++ Cargo.toml | 4 +- src/bin/cargo-nro.rs | 115 ++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 160 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac34a57..2cbeb74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -198,6 +198,16 @@ name = "glob" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "goblin" +version = "0.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "plain 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "scroll 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "idna" version = "0.1.5" @@ -226,7 +236,9 @@ dependencies = [ "cargo_metadata 0.6.0 (git+https://github.com/roblabla/cargo_metadata)", "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", "elf 0.0.10 (registry+https://github.com/rust-lang/crates.io-index)", + "goblin 0.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "lz4 1.22.0 (git+https://github.com/bozaro/lz4-rs.git)", + "scroll 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", @@ -234,6 +246,14 @@ dependencies = [ "url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "log" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "lz4" version = "1.22.0" @@ -263,6 +283,11 @@ name = "percent-encoding" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "proc-macro2" version = "0.4.9" @@ -331,6 +356,24 @@ dependencies = [ "winapi-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "scroll" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "scroll_derive 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "scroll_derive" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.14.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "semver" version = "0.9.0" @@ -546,13 +589,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" "checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" +"checksum goblin 0.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "5911d7df7b8f65ab676c5327b50acea29d3c6a1a4ad05e444cf5dce321b26db2" "checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" "checksum itoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5adb58558dcd1d786b5f0bd15f3226ee23486e24b7b58304b60f64dc68e62606" "checksum libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "6fd41f331ac7c5b8ac259b8bf82c75c0fb2e469bbf37d2becbba9a6a2221965b" +"checksum log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f" "checksum lz4 1.22.0 (git+https://github.com/bozaro/lz4-rs.git)" = "" "checksum lz4-sys 1.8.0 (git+https://github.com/bozaro/lz4-rs.git)" = "" "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" +"checksum plain 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" "checksum proc-macro2 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "cccdc7557a98fe98453030f077df7f3a042052fae465bb61d2c2c41435cfd9b6" "checksum pulldown-cmark 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d6fdf85cda6cadfae5428a54661d431330b312bc767ddbc57adbedc24da66e32" "checksum quote 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b71f9f575d55555aa9c06188be9d4e2bfc83ed02537948ac0d520c24d0419f1a" @@ -562,6 +608,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" "checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395" "checksum same-file 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "10f7794e2fda7f594866840e95f5c5962e886e228e68b6505885811a94dd728c" +"checksum scroll 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "66f024a8cc5e456eb870f688dbd899c84f61190c82c7a911e40f926941969074" +"checksum scroll_derive 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f9a353f5dd99e42ff097d5a61db3257aa2c7127d76a3fa8287b642ef9ae0f7c5" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum serde 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)" = "0c3adf19c07af6d186d91dae8927b83b0553d07ca56cbf7f2f32560455c91920" diff --git a/Cargo.toml b/Cargo.toml index e6e0839..7b5ce13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,11 +22,13 @@ byteorder = "1" lz4 = { git = "https://github.com/bozaro/lz4-rs.git"} clap = {version = "2", optional = true} sha2 = "0.7.1" +scroll = { version = "0.9.0", optional = true } serde = "1" serde_derive = "1" serde_json = "1" cargo_metadata = { git = "https://github.com/roblabla/cargo_metadata", optional = true } url = "1.7.1" +goblin = { version = "0.0.17", optional = true } [features] -binaries = ["clap", "cargo_metadata"] +binaries = ["clap", "cargo_metadata", "scroll", "goblin"] diff --git a/src/bin/cargo-nro.rs b/src/bin/cargo-nro.rs index 18a8720..93bf1c6 100644 --- a/src/bin/cargo-nro.rs +++ b/src/bin/cargo-nro.rs @@ -7,15 +7,22 @@ extern crate serde_json; #[macro_use] extern crate serde_derive; extern crate cargo_metadata; +extern crate goblin; +extern crate scroll; use std::env::{self, VarError}; use std::process::{Command, Stdio}; use std::path::{Path, PathBuf}; use std::fs::File; +use std::io::{Write, Read}; +use scroll::IOwrite; + use linkle::format::{romfs::RomFs, nxo::NxoFile, nacp::NacpFile}; use cargo_metadata::{Package, Message}; use clap::{Arg, App}; use url::Url; +use goblin::elf::{Elf, Header as ElfHeader, ProgramHeader}; +use goblin::elf::section_header::{SHT_NOBITS, SHT_SYMTAB, SHT_STRTAB}; fn find_project_root(path: &Path) -> Option<&Path> { for parent in path.ancestors() { @@ -69,6 +76,106 @@ fn get_metadata(manifest_path: &Path, package_id: &str, target_name: &str) -> (P (package, package_metadata) } +trait BetterIOWrite: IOwrite { + fn iowrite_with_try + scroll::ctx::TryIntoCtx>(&mut self, n: N, ctx: Ctx) + -> Result<(), N::Error> + where + N::Error: From + { + let mut buf = [0u8; 256]; + let size = N::size_with(&ctx); + let buf = &mut buf[0..size]; + n.try_into_ctx(buf, ctx)?; + self.write_all(buf)?; + Ok(()) + } +} + +impl + ?Sized> BetterIOWrite for W {} + +fn generate_debuginfo_romfs>(elf_path: &Path, romfs: Option

) -> goblin::error::Result { + let mut elf_file = File::open(elf_path)?; + let mut buffer = Vec::new(); + elf_file.read_to_end(&mut buffer)?; + let elf = goblin::elf::Elf::parse(&buffer)?; + let new_file = { + let mut new_path = PathBuf::from(elf_path); + new_path.set_extension("debug"); + let mut file = File::create(&new_path)?; + let Elf { + mut header, + program_headers, + mut section_headers, + is_64, + little_endian, + .. + } = elf; + + let ctx = goblin::container::Ctx { + container: if is_64 { goblin::container::Container::Big } else { goblin::container::Container::Little }, + le: if little_endian { goblin::container::Endian::Little } else { goblin::container::Endian::Big } + }; + + for section in section_headers.iter_mut() { + if section.sh_type == SHT_NOBITS || section.sh_type == SHT_SYMTAB || section.sh_type == SHT_STRTAB { + continue; + } + if let Some(Ok(s)) = elf.shdr_strtab.get(section.sh_name) { + if !(s.starts_with(".debug") || s == ".comment") { + section.sh_type = SHT_NOBITS; + } + } + } + + // Calculate section data length + elf/program headers + let data_off = ElfHeader::size(&ctx) + ProgramHeader::size(&ctx) * program_headers.len(); + let shoff = data_off as u64 + section_headers.iter().map(|v| { + if v.sh_type != SHT_NOBITS { + v.sh_size + } else { + 0 + } + }).sum::(); + + // Write ELF header + // TODO: Anything else? + header.e_phoff = ::std::mem::size_of::() as u64; + header.e_shoff = shoff; + file.iowrite_with(header, ctx)?; + + // Write program headers + for phdr in program_headers { + file.iowrite_with_try(phdr, ctx)?; + } + + // Write section data + let mut cur_idx = data_off; + for section in section_headers.iter_mut().filter(|v| v.sh_type != SHT_NOBITS) { + file.write_all(&buffer[section.sh_offset as usize..(section.sh_offset + section.sh_size) as usize])?; + section.sh_offset = cur_idx as u64; + cur_idx += section.sh_size as usize; + } + + // Write section headers + for section in section_headers { + file.iowrite_with(section, ctx)?; + } + + file.sync_all()?; + new_path + }; + + let mut romfs = if let Some(romfs) = romfs { + RomFs::from_directory(romfs.as_ref())? + } else { + RomFs::empty() + }; + + romfs.push_file(&new_file, String::from("debug_info.elf"))?; + + Ok(romfs) +} + #[derive(Debug, Serialize, Deserialize, Default)] struct PackageMetadata { romfs: Option, @@ -178,18 +285,14 @@ fn main() { nacp.title_id = target_metadata.title_id; } - let romfs = if let Some(romfs) = romfs { - Some(RomFs::from_directory(&romfs).unwrap()) - } else { - None - }; + let romfs = generate_debuginfo_romfs(Path::new(&artifact.filenames[0]), romfs).unwrap(); let mut new_name = PathBuf::from(artifact.filenames[0].clone()); assert!(new_name.set_extension("nro")); NxoFile::from_elf(&artifact.filenames[0]).unwrap() .write_nro(&mut File::create(new_name.clone()).unwrap(), - romfs, + Some(romfs), icon_file, Some(nacp) ).unwrap();