Skip to content

Commit

Permalink
Add icon and nacp support to linkle nro (#19)
Browse files Browse the repository at this point in the history
* Add icon and nacp support to linkle nro

* Get RomFS, NACP and Icon from Cargo.toml

* Document package.metadata key in README. Fix title_id consistency
  • Loading branch information
roblabla authored and marysaka committed Sep 14, 2018
1 parent da99773 commit f05d088
Show file tree
Hide file tree
Showing 7 changed files with 267 additions and 45 deletions.
50 changes: 50 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ serde = "1"
serde_derive = "1"
serde_json = "1"
cargo_metadata = { git = "https://github.com/roblabla/cargo_metadata", optional = true }
url = "1.7.1"

[features]
binaries = ["clap", "cargo_metadata"]
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,42 @@ Compiling and creating an NRO file (requires xargo from https://github.com/robla

cargo nro

# Cargo.toml metadata format

When compiling a project with `cargo nro`, a special `[package.metadata.linkle.BINARY_NAME]` key is
used to allow customizing the build. This is an example Cargo.toml:

```
[package]
name = "link"
version = "0.1.0"
authors = ["linkle"]
[package.metadata.linkle.megaton-example]
romfs = "res/"
icon = "icon.jpeg"
titleid = "0100000000819"
[package.metadata.linkle.megaton-example.nacp]
name = "Link"
[package.metadata.linkle.megaton-example.nacp.lang.ja]
"name": "リンク",
"author": "リンクル"
```

All paths are relative to the project root (where the Cargo.toml file is located).

Every field has a sane default:

| Field | Description | Default value |
| ----------------- |:------------------------------------------------:| -------------------:|
| romfs | The application romfs directory. | res/ |
| icon | The application icon. | icon.jpg |
| title_id | The application title id. | 0000000000000000 |

The `[package.metadata.linkle.BINARY_NAME.nacp]` key follows the [NACP input format](#nacp-input-format)

# NACP input format

This is an example of a compatible JSON:
Expand Down
99 changes: 85 additions & 14 deletions src/bin/cargo-nro.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#[macro_use]
extern crate clap;

extern crate url;
extern crate linkle;
extern crate serde;
extern crate serde_json;
Expand All @@ -9,13 +9,13 @@ extern crate serde_derive;
extern crate cargo_metadata;

use std::env::{self, VarError};
use std::fmt;
use std::process::{Command, Stdio};
use std::path::{Path, PathBuf};
use std::fs::File;
use linkle::format::nxo::NxoFile;
use cargo_metadata::Message;
use linkle::format::{nxo::NxoFile, nacp::NacpFile};
use cargo_metadata::{Package, Message};
use clap::{Arg, App};
use url::Url;

fn find_project_root(path: &Path) -> Option<&Path> {
for parent in path.ancestors() {
Expand Down Expand Up @@ -62,6 +62,21 @@ const CARGO_OPTIONS: &'static str = "CARGO OPTIONS:
-h, --help Prints help information";


fn get_metadata(manifest_path: &Path, package_id: &str, target_name: &str) -> (Package, PackageMetadata) {
let metadata = cargo_metadata::metadata(Some(&manifest_path)).unwrap();
let package = metadata.packages.into_iter().find(|v| v.id == package_id).unwrap();
let package_metadata = serde_json::from_value(package.metadata.pointer(&format!("linkle/{}", target_name)).cloned().unwrap_or(serde_json::Value::Null)).unwrap_or(PackageMetadata::default());
(package, package_metadata)
}

#[derive(Debug, Serialize, Deserialize, Default)]
struct PackageMetadata {
romfs: Option<String>,
nacp: Option<NacpFile>,
icon: Option<String>,
title_id: Option<String>
}

fn main() {
let args = if env::args().nth(1) == Some("nro".to_string()) {
// Skip the subcommand when running through cargo
Expand Down Expand Up @@ -105,16 +120,75 @@ fn main() {
match message {
Ok(Message::CompilerArtifact(ref artifact)) if artifact.target.kind[0] == "bin" => {
// Find the artifact's source. This is not going to be pretty.
let src = Path::new(&artifact.target.src_path);
let mut romfs = None;
let root = find_project_root(&src).unwrap();
if root.join("res").is_dir() {
romfs = Some(root.join("res").to_string_lossy().into_owned());
// For whatever reason, cargo thought it'd be a *great idea* to make file URLs use
// the non-standard "path+file:///" scheme, instead of, y'know, the ""file:///" everyone
// knows.
//
// So we check if it starts with path+file, and if it does, we skip the path+ part when
// parsing it.
let url = if artifact.package_id.url().starts_with("path+file") {
&artifact.package_id.url()["path+".len()..]
} else {
artifact.package_id.url()
};
let url = Url::parse(url).unwrap();
if url.scheme() != "file" {
continue;
}

let root = url.to_file_path().unwrap();
let manifest = root.join("Cargo.toml");

let (package, target_metadata) = get_metadata(&manifest, &artifact.package_id.raw, &artifact.target.name);

let romfs = if let Some(romfs) = target_metadata.romfs {
let romfs_path = root.join(romfs);
if !romfs_path.is_dir() {
panic!("Invalid romfs directory {:?}", romfs_path);
} else {
Some(romfs_path)
}
} else if root.join("res").is_dir() {
Some(root.join("res"))
} else {
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() {
panic!("Invalid icon file {:?}", icon_path);
} else {
Some(icon_path)
}
} else if root.join("icon.jpg").is_file() {
Some(root.join("icon.jpg"))
} else {
None
};

let icon_file = icon_file.map(|v| v.to_string_lossy().into_owned());
let icon_file = icon_file.as_ref().map(|v| v.as_ref());

let mut nacp = target_metadata.nacp.unwrap_or(Default::default());
nacp.name.get_or_insert(package.name);
nacp.author.get_or_insert(package.authors[0].clone());
nacp.version.get_or_insert(package.version);
nacp.title_id.get_or_insert(package.title_id);

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.as_ref().map(|v| v.as_ref())).unwrap();
println!("Built {} (using {:?} as romfs)", new_name.to_string_lossy(), romfs);
NxoFile::from_elf(&artifact.filenames[0]).unwrap()
.write_nro(&mut File::create(new_name.clone()).unwrap(),
romfs,
icon_file,
Some(nacp)
).unwrap();

println!("Built {}", new_name.to_string_lossy());
},
Ok(Message::CompilerArtifact(_artifact)) => {
//println!("{:#?}", artifact);
Expand All @@ -127,9 +201,6 @@ fn main() {
}
},
Ok(_) => (),
Err(ref err) if err.is_data() => {
println!("{:?}", err);
},
Err(err) => {
panic!("{:?}", err);
}
Expand Down
22 changes: 20 additions & 2 deletions src/bin/linkle_clap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,18 @@ 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 nacp_file = if let Some(nacp_path) = matches.value_of("NACP_FILE") {
Some(linkle::format::nacp::NacpFile::from_file(nacp_path)?)
} else {
None
};

let mut nxo = linkle::format::nxo::NxoFile::from_elf(input_file)?;
let mut option = OpenOptions::new();
let output_option = option.write(true).create(true).truncate(true);
match format {
"nro" => nxo.write_nro(&mut output_option.open(output_file)?, romfs_dir),
"nro" => nxo.write_nro(&mut output_option.open(output_file)?, romfs_dir, icon_file, nacp_file),
"nso" => nxo.write_nso(&mut output_option.open(output_file)?),
_ => process::exit(1),
}
Expand Down Expand Up @@ -85,6 +92,17 @@ fn main() {
.takes_value(true)
.value_name("ROMFS_PATH")
.help("Sets the directory to use as RomFs when bundling into an NRO");
let icon_arg = Arg::with_name("ROMFS_PATH")
.long("icon-path")
.takes_value(true)
.value_name("ICON_PATH")
.help("Sets the icon to use when bundling into an NRO");
let nacp_arg = Arg::with_name("NACP_PATH")
.long("nacp-path")
.takes_value(true)
.value_name("NACP_PATH")
.help("Sets the NACP JSON to use when bundling into an NRO");

let output_file_arg = Arg::with_name("OUTPUT_FILE")
.help("Sets the output file to use")
.required(true);
Expand All @@ -95,7 +113,7 @@ fn main() {
.subcommands(vec![
SubCommand::with_name("nro")
.about("Create a NRO file from an ELF file")
.args(&vec![input_file_arg.clone(), output_file_arg.clone(), romfs_arg]),
.args(&vec![input_file_arg.clone(), output_file_arg.clone(), icon_arg, romfs_arg, nacp_arg]),
SubCommand::with_name("nso")
.about("Create a NSO file from an ELF file")
.args(&vec![input_file_arg.clone(), output_file_arg.clone()]),
Expand Down
Loading

0 comments on commit f05d088

Please sign in to comment.