From 449097967c773b2392b1c0e2f07dbab95296ea7d Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Mon, 2 Mar 2026 02:43:38 -0500 Subject: [PATCH] Add setting.txt gen command --- src/bin/rustwii/main.rs | 3 ++ src/bin/rustwii/nand/setting.rs | 79 +++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/src/bin/rustwii/main.rs b/src/bin/rustwii/main.rs index 6645614..0177875 100644 --- a/src/bin/rustwii/main.rs +++ b/src/bin/rustwii/main.rs @@ -188,6 +188,9 @@ fn main() -> Result<()> { }, nand::setting::Commands::Encrypt { input, output } => { nand::setting::encrypt_setting(input, output)?; + }, + nand::setting::Commands::Gen { serno, region } => { + nand::setting::generate_setting(serno, region)?; } } }, diff --git a/src/bin/rustwii/nand/setting.rs b/src/bin/rustwii/nand/setting.rs index 45e65d3..db081db 100644 --- a/src/bin/rustwii/nand/setting.rs +++ b/src/bin/rustwii/nand/setting.rs @@ -7,7 +7,9 @@ use std::{str, fs}; use std::path::{Path, PathBuf}; use anyhow::{bail, Context, Result}; use clap::Subcommand; +use regex::RegexBuilder; use rustwii::nand::setting; +use rustwii::nand::setting::SettingTxt; #[derive(Subcommand)] #[command(arg_required_else_help = true)] @@ -27,6 +29,13 @@ pub enum Commands { /// An optional output path; defaults to setting_enc.txt #[arg(short, long)] output: Option, + }, + /// 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) -> 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.")?; fs::write(out_path, setting.to_string()?)?; + + println!("Successfully decrypted setting.txt!"); + Ok(()) } @@ -57,5 +69,72 @@ pub fn encrypt_setting(input: &str, output: &Option) -> Result<()> { }; 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()?)?; + + 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(®ion) { + 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(()) }