use clap::{Error, Parser}; use std::fs::{ File, ReadDir, copy, create_dir, read_dir, remove_dir_all, remove_file, set_permissions, }; use std::io::Write; use std::path::{Path, PathBuf, absolute}; #[cfg(target_os = "windows")] const BUILD_ROMFS_BIN: &[u8] = include_bytes!("../switch-tools/build_romfs.exe"); #[cfg(target_os = "windows")] const BUILD_PFS0_BIN: &[u8] = include_bytes!("../switch-tools/build_pfs0.exe"); #[cfg(not(target_os = "windows"))] const BUILD_ROMFS_BIN: &[u8] = include_bytes!("../switch-tools/build_romfs"); #[cfg(not(target_os = "windows"))] const BUILD_PFS0_BIN: &[u8] = include_bytes!("../switch-tools/build_pfs0"); const FILES_TO_COPY: [&str; 28] = [ "romfs.bin", "exefs.nsp", "rtld", "main", "main.npdm", "compat0", "compat1", "compat2", "compat3", "compat4", "compat5", "compat6", "compat7", "compat8", "compat9", "subsdk0", "subsdk1", "subsdk2", "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9", "sdk", "config.ini", "icon.jpg", ]; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] struct Args { #[arg(short = 'i', long = "input", default_value = ".")] input: PathBuf, #[arg(short = 'o', long = "output", default_value = "mod.msp")] output: PathBuf, #[arg(short = 'm', long = "manifest", default_value = "manifest")] manifest: PathBuf, } fn list_items(path: &Path) -> Result, Error> { let mut items: Vec = Vec::new(); let entries: ReadDir = read_dir(path)?; for entry in entries { let path: PathBuf = entry?.path(); if path.is_dir() { items.extend(list_items(&path)?); items.push(path); } else if path.is_file() { items.push(path); } } Ok(items) } fn main() -> Result<(), Error> { let args = Args::parse(); let input_path: PathBuf = absolute(args.input)?; let output_path: PathBuf = absolute(args.output)?; let manifest_path: PathBuf = absolute(args.manifest)?; assert!(input_path.exists(), "Input path does not exist"); assert!(!output_path.exists(), "Output path already exists"); assert!(manifest_path.exists(), "Manifest path does not exist"); let current_dir: PathBuf = absolute(Path::new("."))?; let temp_path: PathBuf = current_dir.join("temp"); let mod_path: PathBuf = temp_path.join("mod"); if temp_path.exists() { remove_dir_all(&temp_path)?; } create_dir(&temp_path)?; create_dir(&mod_path)?; copy(&manifest_path, mod_path.join("manifest"))?; let build_romfs_path: PathBuf; let build_pfs0_path: PathBuf; #[cfg(target_os = "windows")] { build_romfs_path = temp_path.join("build_romfs.exe"); build_pfs0_path = temp_path.join("build_pfs0.exe"); } #[cfg(not(target_os = "windows"))] { build_romfs_path = temp_path.join("build_romfs"); build_pfs0_path = temp_path.join("build_pfs0"); } #[cfg(target_os = "windows")] { let mut build_romfs_file: File = File::create(&build_romfs_path)?; let mut build_pfs0_file: File = File::create(&build_pfs0_path)?; build_romfs_file.write_all(BUILD_ROMFS_BIN)?; build_pfs0_file.write_all(BUILD_PFS0_BIN)?; } #[cfg(not(target_os = "windows"))] { let mut build_romfs_file: File = File::create(&build_romfs_path)?; build_romfs_file.write_all(BUILD_ROMFS_BIN)?; set_permissions( &build_romfs_path, std::os::unix::fs::PermissionsExt::from_mode(0o755), )?; let mut build_pfs0_file: File = File::create(&build_pfs0_path)?; build_pfs0_file.write_all(BUILD_PFS0_BIN)?; set_permissions( &build_pfs0_path, std::os::unix::fs::PermissionsExt::from_mode(0o755), )?; } let all_items: Vec = list_items(&input_path)?; for item in all_items { let name: &str = item.file_name().unwrap().to_str().unwrap(); if item.is_dir() && name.to_lowercase() == "romfs" { println!("Found romfs directory. Building romfs.bin..."); std::process::Command::new(&build_romfs_path) .current_dir(&mod_path) .arg(&item) .arg(&mod_path.join("romfs.bin")) .status()?; continue; } if FILES_TO_COPY.contains(&name) { println!("Found {}, copying...", name); copy(&item, &mod_path.join(name))?; continue; } if name.ends_with(".ips") { println!("Found {}, copying...", name); copy(&item, &mod_path.join(name))?; continue; } } println!("Building {}...", output_path.display()); std::process::Command::new(&build_pfs0_path) .current_dir(&mod_path) .arg(&mod_path) .arg(&output_path) .status()?; remove_file(&build_romfs_path)?; remove_file(&build_pfs0_path)?; remove_dir_all(&temp_path)?; Ok(()) }