commit 76cfe115942f4c8e813ccecc8cfe99acd85bdac3 Author: Meziu Date: Fri Oct 29 12:13:46 2021 +0200 Everything works diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f2f9e58 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..63cf6f5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "cargo-3ds" +version = "0.1.0" +description = "Cargo wrapper for developing Rust-based Nintendo 3DS homebrew apps" +repository = "https://github.com/Meziu/cargo-3ds" +license = "MIT" +authors = [ "Andrea Ciliberti " ] +edition = "2018" + +[dependencies] +clap = "2.33.1" +goblin = "0.4.3" +scroll = "0.10.1" +cargo_metadata = "0.14.0" +rustc_version = "0.4.0" +serde = "1.0.111" +serde_derive = "1.0.111" +toml = "0.5.6" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..859da11 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,239 @@ +use cargo_metadata::MetadataCommand; +use rustc_version::{Version, Channel}; +use std::{ + env, fs, fmt, + process::{self, Command, Stdio}, +}; + +#[derive(serde_derive::Deserialize, Default)] +struct CTRConfig { + name: String, + author: String, + description: String, + icon: String, +} + +#[derive(Ord, PartialOrd, PartialEq, Eq, Debug)] +struct CommitDate { + year: i32, + month: i32, + day: i32, +} + +impl CommitDate { + fn parse(date: &str) -> Option { + let mut iter = date.split("-"); + + let year = iter.next()?.parse().ok()?; + let month = iter.next()?.parse().ok()?; + let day = iter.next()?.parse().ok()?; + + Some(Self { year, month, day }) + } +} + +impl fmt::Display for CommitDate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day) + } +} + +const MINIMUM_COMMIT_DATE: CommitDate = CommitDate { year: 2021, month: 10, day: 01 }; +const MINIMUM_RUSTC_VERSION: Version = Version::new(1, 56, 0); + +fn main() { + check_rust_version(); + + let args: Vec = env::args().collect(); + let optimization_level = match args.contains(&String::from("--release")) { + true => String::from("release"), + false => String::from("debug"), + }; + + // Skip `cargo 3ds` + let mut args = env::args().skip(2); + + let command = args.next(); + let must_link = match command { + None => panic!("No command specified, try with \"build\" or \"link\""), + Some(s) => { + match s.as_str() { + "build" => false, + "link" => true, + _ => panic!("Invalid command, try with \"build\" or \"link\""), + } + } + }; + + build_elf(args); + + let app_conf = get_metadata(); + build_3dsx(&app_conf, &optimization_level); + + if must_link { + link(&app_conf.name, &optimization_level); + } +} + +fn check_rust_version() { + let rustc_version = rustc_version::version_meta().unwrap(); + + if rustc_version.channel > Channel::Nightly { + println!("cargo-3ds requires a nightly rustc version."); + println!( + "Please run `rustup override set nightly` to use nightly in the \ + current directory." + ); + process::exit(1); + } + + let old_version: bool = MINIMUM_RUSTC_VERSION > rustc_version.semver.clone(); + + let old_commit = match rustc_version.commit_date { + None => false, + Some(date) => MINIMUM_COMMIT_DATE > CommitDate::parse(&date) + .expect("could not parse `rustc --version` commit date"), + }; + + if old_version || old_commit { + println!( + "cargo-3ds requires rustc nightly version >= {}", + MINIMUM_COMMIT_DATE, + ); + println!( + "Please run `rustup update nightly` to upgrade your nightly version" + ); + + process::exit(1); + } +} + +fn build_elf(args: std::iter::Skip) { + let rustflags = env::var("RUSTFLAGS").unwrap_or("".into()) + + "-Clink-arg=-specs=3dsx.specs -Clink-arg=-z -Clink-arg=muldefs -Clink-arg=-D__3DS__"; + + let mut process = Command::new("cargo") + .arg("build") + .arg("-Z") + .arg("unstable-options") + .arg("-Z") + .arg("build-std") + .arg("--target") + .arg("armv6k-nintendo-3ds") + .args(args) + .env("RUSTFLAGS", rustflags) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .unwrap(); + + let status = process.wait().unwrap(); + + if !status.success() { + let code = match status.code() { + Some(i) => i, + None => 1, + }; + + process::exit(code); + } +} + +fn get_metadata() -> CTRConfig { + let metadata = MetadataCommand::new() + .exec() + .expect("Failed to get cargo metadata"); + + let root_crate = metadata.root_package().expect("No root crate found"); + + let icon = String::from("./icon.png"); + + let icon = if let Err(_) = fs::File::open(&icon) { + format!("{}/libctru/default_icon.png", env::var("DEVKITPRO").unwrap()) + } else { + icon + }; + + CTRConfig { + name: root_crate.name.clone(), + author: root_crate.authors[0].clone(), + description: root_crate.description.clone().unwrap_or(String::from("Homebrew Application")), + icon: icon, + } +} + +fn build_3dsx(config: &CTRConfig, opt_lvl: &str) { + let mut process = Command::new("smdhtool") + .arg("--create") + .arg(&config.name) + .arg(&config.description) + .arg(&config.author) + .arg(&config.icon) + .arg(format!("./target/armv6k-nintendo-3ds/{}/{}.smdh", opt_lvl, config.name)) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .unwrap(); + + let status = process.wait().unwrap(); + + if !status.success() { + let code = match status.code() { + Some(i) => i, + None => 1, + }; + + process::exit(code); + } + + let mut command = Command::new("3dsxtool"); + let mut process = command + .arg(format!("./target/armv6k-nintendo-3ds/{}/{}.elf", opt_lvl, config.name)) + .arg(format!("./target/armv6k-nintendo-3ds/{}/{}.3dsx", opt_lvl, config.name)) + .arg(format!("--smdh=./target/armv6k-nintendo-3ds/{}/{}.smdh", opt_lvl, config.name)); + + // If romfs directory exists, automatically include it + if let Ok(_) = std::fs::read_dir("./romfs") { + process = process.arg("--romfs=\"./romfs\""); + } + + let mut process = process.stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .unwrap(); + + let status = process.wait().unwrap(); + + if !status.success() { + let code = match status.code() { + Some(i) => i, + None => 1, + }; + + process::exit(code); + } +} + +fn link(name: &str, opt_lvl: &str) { + let mut process = Command::new("3dslink") + .arg(format!("./target/armv6k-nintendo-3ds/{}/{}.3dsx", opt_lvl, name)) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .unwrap(); + + let status = process.wait().unwrap(); + + if !status.success() { + let code = match status.code() { + Some(i) => i, + None => 1, + }; + + process::exit(code); + } +}