Add setting.txt gen command

This commit is contained in:
2026-03-02 02:43:38 -05:00
parent 326bb56ece
commit 449097967c
2 changed files with 82 additions and 0 deletions

View File

@@ -188,6 +188,9 @@ fn main() -> Result<()> {
}, },
nand::setting::Commands::Encrypt { input, output } => { nand::setting::Commands::Encrypt { input, output } => {
nand::setting::encrypt_setting(input, output)?; nand::setting::encrypt_setting(input, output)?;
},
nand::setting::Commands::Gen { serno, region } => {
nand::setting::generate_setting(serno, region)?;
} }
} }
}, },

View File

@@ -7,7 +7,9 @@ use std::{str, fs};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use clap::Subcommand; use clap::Subcommand;
use regex::RegexBuilder;
use rustwii::nand::setting; use rustwii::nand::setting;
use rustwii::nand::setting::SettingTxt;
#[derive(Subcommand)] #[derive(Subcommand)]
#[command(arg_required_else_help = true)] #[command(arg_required_else_help = true)]
@@ -27,6 +29,13 @@ pub enum Commands {
/// An optional output path; defaults to setting_enc.txt /// An optional output path; defaults to setting_enc.txt
#[arg(short, long)] #[arg(short, long)]
output: Option<String>, output: Option<String>,
},
/// Generate a new setting.txt from the provided values
Gen {
/// The serial number of the console this file is for
serno: String,
/// Region of the console this file is for (USA, EUR, JPN, or KOR)
region: String
} }
} }
@@ -42,6 +51,9 @@ pub fn decrypt_setting(input: &str, output: &Option<String>) -> Result<()> {
}; };
let setting = setting::SettingTxt::from_bytes(&fs::read(in_path)?).with_context(|| "The provided setting.txt could not be parsed, and is likely invalid.")?; let setting = setting::SettingTxt::from_bytes(&fs::read(in_path)?).with_context(|| "The provided setting.txt could not be parsed, and is likely invalid.")?;
fs::write(out_path, setting.to_string()?)?; fs::write(out_path, setting.to_string()?)?;
println!("Successfully decrypted setting.txt!");
Ok(()) Ok(())
} }
@@ -57,5 +69,72 @@ pub fn encrypt_setting(input: &str, output: &Option<String>) -> Result<()> {
}; };
let setting = setting::SettingTxt::from_string(String::from_utf8(fs::read(in_path)?).with_context(|| "Invalid characters found in input file!")?)?; let setting = setting::SettingTxt::from_string(String::from_utf8(fs::read(in_path)?).with_context(|| "Invalid characters found in input file!")?)?;
fs::write(out_path, setting.to_bytes()?)?; fs::write(out_path, setting.to_bytes()?)?;
println!("Successfully encrypted setting.txt!");
Ok(())
}
pub fn generate_setting(serno: &str, region: &str) -> Result<()> {
// Validate the provided SN. It should be 2 or 3 letters followed by 9 numbers.
if serno.len() != 11 && serno.len() != 12 {
bail!("The provided Serial Number is not valid!")
}
let re = RegexBuilder::new(r"[0-9]+").case_insensitive(true).build()?;
if !re.is_match(&serno[serno.len() - 9..]) {
bail!("The provided Serial Number is not valid!")
}
let prefix = &serno[..serno.len() - 9];
// Detect the console revision based on the SN.
let revision = match prefix.chars().next().unwrap() {
'L' => "RVL-001",
'K' => "RVL-101",
'H' => "RVL-201",
_ => "RVL-001"
};
// Validate the region, and then validate the SN based on the region. USA has a two-letter
// prefix for a total length of 11 characters, while other regions have a three-letter prefix
// for a total length of 12 characters.
let valid_regions = ["USA", "EUR", "JPN", "KOR"];
if !valid_regions.contains(&region) {
bail!("The provided region \"{region}\" is not valid!")
}
if (prefix.len() == 2 && region != "USA") || (prefix.len() == 3 && region == "USA") {
bail!("The provided region \"{region}\" does not match the provided Serial Number {serno}!")
}
// Find the values of VIDEO and GAME.
let (video, game) = match region {
"USA" => ("NTSC", "US"),
"EUR" => ("PAL", "EU"),
"JPN" => ("NTSC", "JP"),
"KOR" => ("NTSC", "KR"),
_ => bail!("The provided region \"{region}\" is not valid!")
};
let model = format!("{revision}({region})");
let serial_number = &serno[serno.len() - 9..];
let setting_str = format!("\
AREA={}\r\n\
MODEL={}\r\n\
DVD=0\r\n\
MPCH=0x7FFE\r\n\
CODE={}\r\n\
SERNO={}\r\n\
VIDEO={}\r\n\
GAME={}\r\n", region, model, prefix, serial_number, video, game
);
let setting_txt = SettingTxt::from_string(setting_str)?;
fs::write("setting.txt", setting_txt.to_bytes()?)
.with_context(|| "Failed to write setting.txt!")?;
println!("Successfully created setting.txt for console with serial number {serno} and \
region {region}!");
Ok(()) Ok(())
} }