use crate::build;
use crate::cargo_info;
use crate::error::{Error, Result};
use crate::game_paths::{get_npdm_path, get_plugin_nro_path, get_subsdk_path};
use owo_colors::OwoColorize;
use walkdir::WalkDir;
use std::fs;
use std::io::{Cursor, Read, Write};
use std::path::Path;
use std::path::PathBuf;
use std::result::Result as StdResult;
use zip::{ZipArchive, ZipWriter};
pub struct Exefs {
pub main_npdm: Vec<u8>,
pub subsdk1: Vec<u8>,
}
pub fn get_exefs(url: &str) -> Result<Exefs> {
let zip_reader = Cursor::new(
reqwest::blocking::get(url)
.map_err(|_| Error::DownloadError)?
.bytes()
.map_err(|_| Error::DownloadError)?,
);
let mut zip = ZipArchive::new(zip_reader).unwrap();
let subsdk1 = zip
.by_name("exefs/subsdk9")?
.bytes()
.collect::<StdResult<_, _>>()?;
let main_npdm = zip
.by_name("exefs/main.npdm")?
.bytes()
.collect::<StdResult<_, _>>()?;
Ok(Exefs { main_npdm, subsdk1 })
}
pub fn package(
skyline_url: &str,
title_id: Option<&str>,
out_path: &str,
include_skyline: bool,
subsdk: bool,
) -> Result<()> {
let args = vec![String::from("--release")];
let binary_path = if subsdk {
build::build_get_nso(args)?
} else {
build::build_get_nro(args)?
};
let plugin_name = binary_path.file_name().unwrap().to_string_lossy();
println!("Built {:?}!", plugin_name);
let metadata = cargo_info::get_metadata()?;
let title_id = title_id
.or(metadata.title_id.as_deref())
.ok_or(Error::NoTitleId)?;
let exefs = if include_skyline && !subsdk {
println!("Downloading latest Skyline release...");
Some(get_exefs(skyline_url)?)
} else {
None
};
println!("Building Zip File...");
let binary_data = fs::read(&binary_path)?;
let mut zip = ZipWriter::new(fs::File::create(out_path)?);
let binary_install_path = if subsdk {
get_subsdk_path(title_id, metadata.subsdk_name.as_deref().unwrap_or("subsdk9"))[1..].to_string()
} else {
get_plugin_nro_path(title_id, plugin_name.as_ref())[1..].to_string()
};
zip.start_file(
binary_install_path,
Default::default(),
)?;
zip.write_all(&binary_data)?;
let main_npdm = metadata
.npdm_path
.as_ref()
.map(fs::read)
.transpose()
.map_err(|_| Error::NoNpdmFileFound)?;
if include_skyline && !subsdk {
let generated_npdm = crate::installer::generate_npdm(title_id);
main_npdm.as_ref().unwrap_or_else(|| {
eprintln!("\n{}: defaulting to a generated NPDM.", "Warning".yellow());
eprintln!(
"{}: To specify a custom npdm add the following to your Cargo.toml:",
"NOTE".bright_blue()
);
eprintln!("\n{}\n", "[package.metadata.skyline]".bright_blue());
eprintln!("{}\n", "custom-npdm = \"path/to/your.npdm\"".bright_blue());
&generated_npdm
});
let subsdk_name = metadata.subsdk_name.as_deref().unwrap_or("subsdk9");
zip.start_file(
get_subsdk_path(title_id, subsdk_name)[1..].to_string(),
Default::default(),
)?;
zip.write_all(&exefs.unwrap().subsdk1)?;
}
if let Some(main_npdm) = main_npdm {
zip.start_file(get_npdm_path(title_id)[1..].to_string(), Default::default())?;
zip.write_all(&main_npdm)?;
}
for resource in &metadata.package_resources {
let local_path = &resource.local_path;
let output_path = &resource.package_path;
if Path::new(&local_path).is_dir() {
let paths: Vec<PathBuf> = WalkDir::new(&local_path).into_iter().flatten().filter(|entry| entry.file_type().is_file()).map(|entry| entry.path().to_owned()).collect();
for path in paths {
zip.start_file(
output_path.join(&path.strip_prefix(&local_path).unwrap()).to_str().unwrap(),
Default::default(),
)?;
zip.write_all(&std::fs::read(&path).map_err(|_| Error::PackageResourceMissing(path))?)?;
}
} else {
zip.start_file(
output_path.to_str().unwrap(),
Default::default(),
)?;
zip.write_all(&std::fs::read(&local_path).map_err(|_| Error::PackageResourceMissing(local_path.to_owned()))?)?;
}
}
println!("Finished building zip at '{}'", out_path);
Ok(())
}