diff --git a/Cargo.lock b/Cargo.lock index 8e9b9c4..e4a2aaa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -405,6 +405,22 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -884,9 +900,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.88" +version = "0.3.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e709f3e3d22866f9c25b3aff01af289b18422cc8b4262fb19103ee80fe513d" +checksum = "14dc6f6450b3f6d4ed5b16327f38fed626d375a886159ca555bd7822c0c3a5a6" dependencies = [ "once_cell", "wasm-bindgen", @@ -919,6 +935,12 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "litemap" version = "0.8.1" @@ -1280,9 +1302,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" @@ -1366,10 +1388,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] -name = "rustls" -version = "0.23.36" +name = "rustix" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "aws-lc-rs", "once_cell", @@ -1462,6 +1497,7 @@ dependencies = [ "reqwest", "rsa", "sha1", + "tempfile", "thiserror 2.0.18", "walkdir", ] @@ -1702,6 +1738,19 @@ dependencies = [ "libc", ] +[[package]] +name = "tempfile" +version = "3.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" +dependencies = [ + "fastrand", + "getrandom 0.4.1", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -1973,9 +2022,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.111" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec1adf1535672f5b7824f817792b1afd731d7e843d2d04ec8f27e8cb51edd8ac" +checksum = "60722a937f594b7fde9adb894d7c092fc1bb6612897c46368d18e7a20208eff2" dependencies = [ "cfg-if", "once_cell", @@ -1986,9 +2035,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.61" +version = "0.4.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe88540d1c934c4ec8e6db0afa536876c5441289d7f9f9123d4f065ac1250a6b" +checksum = "8a89f4650b770e4521aa6573724e2aed4704372151bd0de9d16a3bbabb87441a" dependencies = [ "cfg-if", "futures-util", @@ -2000,9 +2049,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.111" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e638317c08b21663aed4d2b9a2091450548954695ff4efa75bff5fa546b3b1" +checksum = "0fac8c6395094b6b91c4af293f4c79371c163f9a6f56184d2c9a85f5a95f3950" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2010,9 +2059,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.111" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c64760850114d03d5f65457e96fc988f11f01d38fbaa51b254e4ab5809102af" +checksum = "ab3fabce6159dc20728033842636887e4877688ae94382766e00b180abac9d60" dependencies = [ "bumpalo", "proc-macro2", @@ -2023,9 +2072,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.111" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60eecd4fe26177cfa3339eb00b4a36445889ba3ad37080c2429879718e20ca41" +checksum = "de0e091bdb824da87dc01d967388880d017a0a9bc4f3bdc0d86ee9f9336e3bb5" dependencies = [ "unicode-ident", ] @@ -2066,9 +2115,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.88" +version = "0.3.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6bb20ed2d9572df8584f6dc81d68a41a625cadc6f15999d649a70ce7e3597a" +checksum = "705eceb4ce901230f8625bd1d665128056ccbe4b7408faa625eec1ba80f59a97" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index a6eeaeb..af7b45b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustwii" -authors = ["NinjaCheetah "] +authors = ["NinjaCheetah "] license = "MIT" description = "A Rust library and CLI for handling files and formats used by the Wii" version = "0.1.0" @@ -29,7 +29,7 @@ cbc = "0" aes = "0" rsa = { version = "0", features = ["sha2"] } hex = "0" -sha1 = { version = "0", features = ["oid"]} +sha1 = { version = "0", features = ["oid"] } glob = "0" regex = "1" clap = { version = "4", features = ["derive"] } @@ -38,3 +38,4 @@ thiserror = "2" reqwest = { version = "0", features = ["blocking"] } rand = "0" walkdir = "2" +tempfile = "3" diff --git a/src/archive/u8.rs b/src/archive/u8.rs index 63fcb4d..96029e3 100644 --- a/src/archive/u8.rs +++ b/src/archive/u8.rs @@ -3,9 +3,8 @@ // // Implements the structures and methods required for parsing U8 archives. -use std::cell::RefCell; +use std::cmp::max; use std::io::{Cursor, Read, Seek, SeekFrom, Write}; -use std::rc::{Rc, Weak}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use thiserror::Error; @@ -26,83 +25,18 @@ pub enum U8Error { #[derive(Clone, Debug)] pub struct U8Directory { pub name: String, - pub parent: Option>>, - pub dirs: Vec>>, - pub files: Vec>>, + pub dirs: Vec, + pub files: Vec, } #[derive(Clone, Debug)] pub struct U8File { pub name: String, pub data: Vec, - pub parent: Option>>, -} - -impl U8Directory { - pub fn new(name: String) -> Rc> { - Rc::new(RefCell::new(Self { - name, - parent: None, - dirs: Vec::new(), - files: Vec::new(), - })) - } - - pub fn add_dir(parent: &Rc>, child: Rc>) { - child.borrow_mut().parent = Some(Rc::downgrade(parent)); - parent.borrow_mut().dirs.push(child); - } - - pub fn add_file(parent: &Rc>, file: Rc>) { - file.borrow_mut().parent = Some(Rc::downgrade(parent)); - parent.borrow_mut().files.push(file); - } - - pub fn get_parent(&self) -> Option>> { - self.parent.as_ref()?.upgrade() - } - - pub fn get_child_dir(parent: &Rc>, name: &str) -> Option>> { - parent.borrow().dirs.iter() - .find(|dir| dir.borrow().name == name) - .map(Rc::clone) - } - - fn count_recursive(dir: &Rc>, count: &mut usize) { - *count += dir.borrow().files.len(); - for dir in dir.borrow().dirs.iter() { - *count += 1; - Self::count_recursive(dir, count); - } - } - - pub fn count(&self) -> usize { - let mut count: usize = 1; - count += self.files.len(); - for dir in &self.dirs { - count += 1; - Self::count_recursive(dir, &mut count); - } - count - } -} - -impl U8File { - pub fn new(name: String, data: Vec) -> Rc> { - Rc::new(RefCell::new(Self { - name, - data, - parent: None, - })) - } - - pub fn get_parent(&self) -> Option>> { - self.parent.as_ref()?.upgrade() - } } #[derive(Clone, Debug)] -pub struct U8Node { +struct U8Node { pub node_type: u8, pub name_offset: u32, // This is really type u24, so the most significant byte will be ignored. pub data_offset: u32, @@ -110,13 +44,15 @@ pub struct U8Node { } #[derive(Clone, Debug)] -pub struct U8Archive { - pub node_tree: Rc>, +struct U8Reader { + buf: Cursor>, + u8_nodes: Vec, + index: usize, + base_name_offset: u64 } -impl U8Archive { - /// Creates a new U8 instance from the binary data of a U8 file. - pub fn from_bytes(data: &[u8]) -> Result { +impl U8Reader { + fn new(data: Box<[u8]>) -> Result { let mut buf = Cursor::new(data); let mut magic = [0u8; 4]; buf.read_exact(&mut magic)?; @@ -135,7 +71,7 @@ impl U8Archive { } println!("ignoring IMET header at 0x40"); } - // Check for an IMET header that comes after a built tag. + // Check for an IMET header that comes after a build tag. else { buf.seek(SeekFrom::Start(0x80))?; buf.read_exact(&mut magic)?; @@ -150,6 +86,7 @@ impl U8Archive { } } } + // We're skipping the following values: // root_node_offset (u32): constant value, always 0x20 // header_size (u32): we don't need this because we already know how long the string table is @@ -168,6 +105,7 @@ impl U8Archive { data_offset: root_node_data_offset, size: root_node_size, }; + // Create a vec of nodes, push the root node, and then iterate over the remaining number // of nodes in the file and push them to the vec. let mut u8_nodes: Vec = Vec::new(); @@ -179,103 +117,131 @@ impl U8Archive { let size = buf.read_u32::()?; u8_nodes.push(U8Node { node_type, name_offset, data_offset, size }) } - // Iterate over the loaded nodes and load the file names and data associated with them. + // Save the base name offset for later. let base_name_offset = buf.position(); - let mut file_names = Vec::::new(); - let mut file_data = Vec::>::new(); - for node in &u8_nodes { - buf.seek(SeekFrom::Start(base_name_offset + node.name_offset as u64))?; - let mut name_bin = Vec::::new(); - // Read the file name one byte at a time until we find a null byte. - loop { - let byte = buf.read_u8()?; - if byte == b'\0' { - break; - } - name_bin.push(byte); - } - file_names.push(String::from_utf8(name_bin).map_err(|_| U8Error::InvalidFileName(base_name_offset + node.name_offset as u64))?.to_owned()); - // If this is a file node, read the data for the file. - if node.node_type == 0 { - buf.seek(SeekFrom::Start(node.data_offset as u64))?; - let mut data = vec![0u8; node.size as usize]; - buf.read_exact(&mut data)?; - file_data.push(data); - } else { - file_data.push(Vec::new()); + + Ok(Self { + buf, + u8_nodes, + index: 0, + base_name_offset + }) + } + + fn file_name(&mut self, name_offset: u64) -> Result { + self.buf.seek(SeekFrom::Start(self.base_name_offset + name_offset))?; + let mut name_bin = Vec::::new(); + loop { + let byte = self.buf.read_u8()?; + if byte == b'\0' { + break; } + name_bin.push(byte); } - // Now that we have all the data loaded out of the file, assemble the tree of U8Items that - // provides an actual map of the archive's data. - let node_tree = U8Directory::new(String::new()); - let mut focused_node = Rc::clone(&node_tree); - // This is the order of directory nodes we've traversed down. - let mut parent_dirs: Vec = Vec::from([0]); - for i in 0..u8_nodes.len() { - match u8_nodes[i].node_type { + Ok(String::from_utf8(name_bin) + .map_err(|_| U8Error::InvalidFileName(self.base_name_offset + name_offset))?.to_owned() + ) + } + + fn file_data(&mut self, data_offset: u64, size: usize) -> Result, U8Error> { + self.buf.seek(SeekFrom::Start(data_offset))?; + let mut data = vec![0u8; size]; + self.buf.read_exact(&mut data)?; + Ok(data) + } + + fn read_dir_recursive(&mut self) -> Result { + let mut current_dir = U8Directory::new(self.file_name(self.u8_nodes[self.index].name_offset as u64)?); + + let current_dir_end = self.u8_nodes[self.index].size as usize; + self.index += 1; + while self.index < current_dir_end { + match self.u8_nodes[self.index].node_type { 1 => { - // Code for a directory node. - if u8_nodes[i].name_offset != 0 { - // If we're already at the correct level, push a new empty dir item to the - // item we're currently working on. - if u8_nodes[i].data_offset == *parent_dirs.last().unwrap() { - parent_dirs.push(i as u32); - U8Directory::add_dir(&focused_node, U8Directory::new(file_names[i].clone())); - focused_node = U8Directory::get_child_dir(&focused_node, &file_names[i]).unwrap(); - } - // Otherwise, go back up the path until we're at the correct level. - else { - while u8_nodes[i].data_offset != *parent_dirs.last().unwrap() { - parent_dirs.pop(); - let parent = focused_node.as_ref().borrow().get_parent().unwrap(); - focused_node = parent; - } - parent_dirs.push(i as u32); - // Rebuild current working directory, and make sure all directories in the - // path exist. - U8Directory::add_dir(&focused_node, U8Directory::new(file_names[i].clone())); - focused_node = U8Directory::get_child_dir(&focused_node, &file_names[i]).unwrap() - } - } + // Directory node, recursive over the child dir and then add it to the + // current one. + let child_dir = self.read_dir_recursive()?; + current_dir.add_dir(child_dir); }, 0 => { - // Code for a file node. - U8Directory::add_file(&focused_node, U8File::new(file_names[i].clone(), file_data[i].clone())); + // File node, add + current_dir.add_file( + U8File::new( + self.file_name(self.u8_nodes[self.index].name_offset as u64)?, + self.file_data(self.u8_nodes[self.index].data_offset as u64, self.u8_nodes[self.index].size as usize)? + ) + ); + self.index += 1; }, - x => return Err(U8Error::InvalidNodeType(x, i)) + x => return Err(U8Error::InvalidNodeType(x, self.index)) } } - Ok(U8Archive { - node_tree, - }) + + Ok(current_dir) } - - pub fn from_tree(node_tree: &Rc>) -> Result { - Ok(U8Archive { - node_tree: node_tree.clone(), - }) +} + +impl U8Directory { + pub fn new(name: String) -> Self { + Self { + name, + dirs: vec![], + files: vec![] + } } - - fn pack_dir_recursive(file_names: &mut Vec, file_data: &mut Vec>, u8_nodes: &mut Vec, current_node: &Rc>) { - // For files, read their data into the file data list, add their name into the file name - // list, then calculate the offset for their file name and create a new U8Node() for them. + + pub fn dirs(&self) -> &Vec { + &self.dirs + } + + pub fn set_dirs(&mut self, dirs: Vec) { + self.dirs = dirs + } + + pub fn files(&self) -> &Vec { + &self.files + } + + pub fn set_files(&mut self, files: Vec) { + self.files = files + } + + pub fn add_dir(&mut self, child: Self) -> &mut U8Directory { + self.dirs.push(child); + self.dirs.last_mut().unwrap() + } + + pub fn add_file(&mut self, file: U8File) { + self.files.push(file); + } + + /// Creates a new U8 instance from the binary data of a U8 file. + pub fn from_bytes(data: Box<[u8]>) -> Result { + let mut u8_reader = U8Reader::new(data)?; + u8_reader.read_dir_recursive() + } + + fn pack_dir_recursive(&self, file_names: &mut Vec, file_data: &mut Vec>, u8_nodes: &mut Vec) { + // For files, read their data into the file data list, add their name into the file name + // list, then calculate the offset for their file name and create a new U8Node() for them. // 0 values for name/data offsets are temporary and are set later. let parent_node = u8_nodes.len() - 1; - for file in ¤t_node.borrow().files { - file_names.push(file.borrow().name.clone()); - file_data.push(file.borrow().data.clone()); + for file in &self.files { + file_names.push(file.name.clone()); + file_data.push(file.data.clone()); u8_nodes.push(U8Node { node_type: 0, name_offset: 0, data_offset: 0, size: file_data[u8_nodes.len()].len() as u32}); } - // For directories, add their name to the file name list, add empty data to the file data - // list, find the total number of files and directories inside the directory to calculate - // the final node included in it, then recursively call this function again on that + + // For directories, add their name to the file name list, add empty data to the file data + // list, find the total number of files and directories inside the directory to calculate + // the final node included in it, then recursively call this function again on that // directory to process it. - for dir in ¤t_node.borrow().dirs { - file_names.push(dir.borrow().name.clone()); + for dir in &self.dirs { + file_names.push(dir.name.clone()); file_data.push(Vec::new()); - let max_node = u8_nodes.len() + current_node.borrow().count() + 1; + let max_node = u8_nodes.len() + dir.count(); u8_nodes.push(U8Node { node_type: 1, name_offset: 0, data_offset: parent_node as u32, size: max_node as u32}); - U8Archive::pack_dir_recursive(file_names, file_data, u8_nodes, dir) + dir.pack_dir_recursive(file_names, file_data, u8_nodes); } } @@ -285,9 +251,9 @@ impl U8Archive { let mut file_names: Vec = vec![String::new()]; let mut file_data: Vec> = vec![Vec::new()]; let mut u8_nodes: Vec = Vec::new(); - u8_nodes.push(U8Node { node_type: 1, name_offset: 0, data_offset: 0, size: self.node_tree.borrow().count() as u32 }); - let root_node = Rc::clone(&self.node_tree); - U8Archive::pack_dir_recursive(&mut file_names, &mut file_data, &mut u8_nodes, &root_node); + u8_nodes.push(U8Node { node_type: 1, name_offset: 0, data_offset: 0, size: self.count() as u32 }); + self.pack_dir_recursive(&mut file_names, &mut file_data, &mut u8_nodes); + // Header size starts at 0 because the header size starts with the nodes and does not // include the actual file header. let mut header_size: u32 = 0; @@ -315,6 +281,7 @@ impl U8Archive { u8_nodes[i].name_offset = current_name_offset; current_name_offset += file_names[i].len() as u32 + 1 } + // Begin writing file data. let mut buf: Vec = Vec::new(); buf.write_all(b"\x55\xAA\x38\x2D")?; @@ -344,4 +311,26 @@ impl U8Archive { } Ok(buf) } + + fn count_recursive(&self) -> usize { + let mut count = self.files.len() + self.dirs.len(); + + for dir in self.dirs.iter() { + count += dir.count_recursive(); + } + count + } + + pub fn count(&self) -> usize { + 1 + self.count_recursive() + } +} + +impl U8File { + pub fn new(name: String, data: Vec) -> Self { + Self { + name, + data + } + } } diff --git a/src/bin/playground/main.rs b/src/bin/playground/main.rs index 0029025..32024d0 100644 --- a/src/bin/playground/main.rs +++ b/src/bin/playground/main.rs @@ -3,47 +3,49 @@ use std::fs; use rustwii::title::{wad, cert}; use rustwii::title; +use rustwii::archive::u8; // use rustii::title::content; fn main() { - let data = fs::read("sm.wad").unwrap(); - let title = title::Title::from_bytes(&data).unwrap(); - println!("Title ID from WAD via Title object: {}", hex::encode(title.tmd.title_id())); - - let wad = wad::WAD::from_bytes(&data).unwrap(); - println!("size of tmd: {:?}", wad.tmd().len()); - println!("num content records: {:?}", title.tmd.content_records().len()); - println!("first record data: {:?}", title.tmd.content_records().first().unwrap()); - println!("TMD is fakesigned: {:?}",title.tmd.is_fakesigned()); - - println!("title version from ticket is: {:?}", title.ticket.title_version()); - println!("title key (enc): {:?}", title.ticket.title_key()); - println!("title key (dec): {:?}", title.ticket.title_key_dec()); - println!("ticket is fakesigned: {:?}", title.ticket.is_fakesigned()); - - println!("title is fakesigned: {:?}", title.is_fakesigned()); - - let cert_chain = &title.cert_chain; - println!("cert chain OK"); - let result = cert::verify_ca_cert(&cert_chain.ca_cert()).unwrap(); - println!("CA cert {} verified successfully: {}", cert_chain.ca_cert().child_cert_identity(), result); - - let result = cert::verify_child_cert(&cert_chain.ca_cert(), &cert_chain.tmd_cert()).unwrap(); - println!("TMD cert {} verified successfully: {}", cert_chain.tmd_cert().child_cert_identity(), result); - let result = cert::verify_tmd(&cert_chain.tmd_cert(), &title.tmd).unwrap(); - println!("TMD verified successfully: {}", result); - - let result = cert::verify_child_cert(&cert_chain.ca_cert(), &cert_chain.ticket_cert()).unwrap(); - println!("Ticket cert {} verified successfully: {}", cert_chain.ticket_cert().child_cert_identity(), result); - let result = cert::verify_ticket(&cert_chain.ticket_cert(), &title.ticket).unwrap(); - println!("Ticket verified successfully: {}", result); - - let result = title.verify().unwrap(); - println!("full title verified successfully: {}", result); + // let data = fs::read("sm.wad").unwrap(); + // let title = title::Title::from_bytes(&data).unwrap(); + // println!("Title ID from WAD via Title object: {}", hex::encode(title.tmd.title_id())); + // + // let wad = wad::WAD::from_bytes(&data).unwrap(); + // println!("size of tmd: {:?}", wad.tmd().len()); + // println!("num content records: {:?}", title.tmd.content_records().len()); + // println!("first record data: {:?}", title.tmd.content_records().first().unwrap()); + // println!("TMD is fakesigned: {:?}",title.tmd.is_fakesigned()); + // + // println!("title version from ticket is: {:?}", title.ticket.title_version()); + // println!("title key (enc): {:?}", title.ticket.title_key()); + // println!("title key (dec): {:?}", title.ticket.title_key_dec()); + // println!("ticket is fakesigned: {:?}", title.ticket.is_fakesigned()); + // + // println!("title is fakesigned: {:?}", title.is_fakesigned()); + // + // let cert_chain = &title.cert_chain; + // println!("cert chain OK"); + // let result = cert::verify_ca_cert(&cert_chain.ca_cert()).unwrap(); + // println!("CA cert {} verified successfully: {}", cert_chain.ca_cert().child_cert_identity(), result); + // + // let result = cert::verify_child_cert(&cert_chain.ca_cert(), &cert_chain.tmd_cert()).unwrap(); + // println!("TMD cert {} verified successfully: {}", cert_chain.tmd_cert().child_cert_identity(), result); + // let result = cert::verify_tmd(&cert_chain.tmd_cert(), &title.tmd).unwrap(); + // println!("TMD verified successfully: {}", result); + // + // let result = cert::verify_child_cert(&cert_chain.ca_cert(), &cert_chain.ticket_cert()).unwrap(); + // println!("Ticket cert {} verified successfully: {}", cert_chain.ticket_cert().child_cert_identity(), result); + // let result = cert::verify_ticket(&cert_chain.ticket_cert(), &title.ticket).unwrap(); + // println!("Ticket verified successfully: {}", result); + // + // let result = title.verify().unwrap(); + // println!("full title verified successfully: {}", result); - - // let mut u8_archive = u8::U8Archive::from_bytes(&fs::read("00000001.app").unwrap()).unwrap(); + + let u8_archive = u8::U8Directory::from_bytes(fs::read("testu8.arc").unwrap().into_boxed_slice()).unwrap(); + println!("{:#?}", u8_archive); // println!("files and dirs counted: {}", u8_archive.node_tree.borrow().count()); // fs::write("outfile.arc", u8_archive.to_bytes().unwrap()).unwrap(); // println!("re-written"); diff --git a/src/bin/rustwii/archive/mod.rs b/src/bin/rustwii/archive/mod.rs index 4623b4a..339ca95 100644 --- a/src/bin/rustwii/archive/mod.rs +++ b/src/bin/rustwii/archive/mod.rs @@ -4,3 +4,4 @@ pub mod ash; pub mod lz77; pub mod u8; +pub mod theme; diff --git a/src/bin/rustwii/archive/theme.rs b/src/bin/rustwii/archive/theme.rs new file mode 100644 index 0000000..c241806 --- /dev/null +++ b/src/bin/rustwii/archive/theme.rs @@ -0,0 +1,28 @@ +// archive/theme.rs from ruswtii (c) 2025 NinjaCheetah & Contributors +// https://github.com/NinjaCheetah/rustwii +// +// Code for the theme building commands in the rustii CLI. + +use anyhow::{bail, Context, Result}; +use clap::Subcommand; +use tempfile::tempdir; + +#[derive(Subcommand)] +#[command(arg_required_else_help = true)] +pub enum Commands { + /// Apply an MYM theme to the Wii Menu + ApplyMym { + /// The path to the source MYM file to apply + mym_path: String, + /// The path to the base Wii Menu asset archive (000000xx.app) + base_path: String, + /// The file to output the finished theme to (.csm) + output: String, + } +} + +pub fn theme_apply_mym(mym_path: &str, base_path: &str, output: &str) -> Result<()> { + todo!(); + + Ok(()) +} diff --git a/src/bin/rustwii/archive/u8.rs b/src/bin/rustwii/archive/u8.rs index a0c678c..3eeceeb 100644 --- a/src/bin/rustwii/archive/u8.rs +++ b/src/bin/rustwii/archive/u8.rs @@ -4,9 +4,7 @@ // Code for the U8 packing/unpacking commands in the rustii CLI. use std::{str, fs}; -use std::cell::RefCell; use std::path::{Path, PathBuf}; -use std::rc::Rc; use anyhow::{bail, Context, Result}; use clap::Subcommand; use glob::glob; @@ -31,7 +29,7 @@ pub enum Commands { } } -fn pack_dir_recursive(dir: &Rc>, in_path: PathBuf) -> Result<()> { +fn pack_dir_recursive(dir: &mut u8::U8Directory, in_path: PathBuf) -> Result<()> { let mut files = Vec::new(); let mut dirs = Vec::new(); for entry in glob(&format!("{}/*", in_path.display()))?.flatten() { @@ -43,13 +41,12 @@ fn pack_dir_recursive(dir: &Rc>, in_path: PathBuf) -> R } for file in files { let node = u8::U8File::new(file.file_name().unwrap().to_str().unwrap().to_owned(), fs::read(file)?); - u8::U8Directory::add_file(dir, node); + dir.add_file(node); } for child_dir in dirs { let node = u8::U8Directory::new(child_dir.file_name().unwrap().to_str().unwrap().to_owned()); - u8::U8Directory::add_dir(dir, node); - let dir = u8::U8Directory::get_child_dir(dir, child_dir.file_name().unwrap().to_str().unwrap()).unwrap(); - pack_dir_recursive(&dir, child_dir)?; + let dir = dir.add_dir(node); + pack_dir_recursive(dir, child_dir)?; } Ok(()) } @@ -60,22 +57,21 @@ pub fn pack_u8_archive(input: &str, output: &str) -> Result<()> { bail!("Source directory \"{}\" could not be found.", in_path.display()); } let out_path = PathBuf::from(output); - let node_tree = u8::U8Directory::new(String::new()); - pack_dir_recursive(&node_tree, in_path.to_path_buf()).with_context(|| "A U8 archive could not be packed.")?; - let u8_archive = u8::U8Archive::from_tree(&node_tree).with_context(|| "An unknown error occurred while creating a U8 archive from the data.")?; - fs::write(&out_path, &u8_archive.to_bytes()?).with_context(|| format!("Could not open output file \"{}\" for writing.", out_path.display()))?; + let mut root_dir = u8::U8Directory::new(String::new()); + pack_dir_recursive(&mut root_dir, in_path.to_path_buf()).with_context(|| "A U8 archive could not be packed.")?; + fs::write(&out_path, &root_dir.to_bytes()?).with_context(|| format!("Could not open output file \"{}\" for writing.", out_path.display()))?; println!("Successfully packed directory \"{}\" into U8 archive \"{}\"!", in_path.display(), out_path.display()); Ok(()) } -fn unpack_dir_recursive(dir: &Rc>, out_path: PathBuf) -> Result<()> { - let out_path = out_path.join(&dir.borrow().name); - for file in &dir.borrow().files { - fs::write(out_path.join(&file.borrow().name), &file.borrow().data).with_context(|| format!("Failed to write output file \"{}\".", &file.borrow().name))?; +fn unpack_dir_recursive(dir: &u8::U8Directory, out_path: PathBuf) -> Result<()> { + let out_path = out_path.join(&dir.name); + for file in &dir.files { + fs::write(out_path.join(&file.name), &file.data).with_context(|| format!("Failed to write output file \"{}\".", &file.name))?; } - for dir in &dir.borrow().dirs { - if !out_path.join(&dir.borrow().name).exists() { - fs::create_dir(out_path.join(&dir.borrow().name)).with_context(|| format!("The output directory \"{}\" could not be created.", out_path.display()))?; + for dir in &dir.dirs { + if !out_path.join(&dir.name).exists() { + fs::create_dir(out_path.join(&dir.name)).with_context(|| format!("The output directory \"{}\" could not be created.", out_path.display()))?; } unpack_dir_recursive(dir, out_path.clone())?; } @@ -97,8 +93,8 @@ pub fn unpack_u8_archive(input: &str, output: &str) -> Result<()> { } // Extract the files and directories in the root, and then recurse over each directory to // extract the files and directories they contain. - let u8_archive = u8::U8Archive::from_bytes(&fs::read(in_path).with_context(|| format!("Input file \"{}\" could not be read.", in_path.display()))?)?; - unpack_dir_recursive(&u8_archive.node_tree, out_path.clone())?; + let root_dir = u8::U8Directory::from_bytes(fs::read(in_path).with_context(|| format!("Input file \"{}\" could not be read.", in_path.display()))?.into_boxed_slice())?; + unpack_dir_recursive(&root_dir, out_path.clone())?; println!("Successfully unpacked U8 archive to directory \"{}\"!", out_path.display()); Ok(()) } diff --git a/src/bin/rustwii/info.rs b/src/bin/rustwii/info.rs index 64d655e..c82031e 100644 --- a/src/bin/rustwii/info.rs +++ b/src/bin/rustwii/info.rs @@ -4,9 +4,7 @@ // Code for the info command in the rustii CLI. use std::{str, fs}; -use std::cell::RefCell; use std::path::Path; -use std::rc::Rc; use anyhow::{bail, Context, Result}; use rustwii::archive::u8; use rustwii::{title, title::cert, title::tmd, title::ticket, title::wad, title::versions}; @@ -243,32 +241,32 @@ fn print_wad_info(wad: wad::WAD) -> Result<()> { Ok(()) } -fn print_full_tree(dir: &Rc>, indent: usize) { +fn print_full_tree(dir: &u8::U8Directory, indent: usize) { let prefix = " ".repeat(indent); - let dir_name = if !dir.borrow().name.is_empty() { - &dir.borrow().name + let dir_name = if !dir.name.is_empty() { + &dir.name } else { &String::from("root") }; println!("{}D {}", prefix, dir_name); // Print subdirectories - for subdir in &dir.borrow().dirs { + for subdir in &dir.dirs { print_full_tree(subdir, indent + 1); } // Print files - for file in &dir.borrow().files { - let file_name = &file.borrow().name; + for file in &dir.files { + let file_name = &file.name; println!("{} F {}", prefix, file_name); } } -fn print_u8_info(u8_archive: u8::U8Archive) -> Result<()> { +fn print_u8_info(root_dir: u8::U8Directory) -> Result<()> { println!("U8 Archive Info"); - println!(" Node Count: {}", u8_archive.node_tree.borrow().count()); + println!(" Node Count: {}", root_dir.count()); println!(" Archive Data:"); - print_full_tree(&u8_archive.node_tree, 2); + print_full_tree(&root_dir, 2); Ok(()) } @@ -291,7 +289,7 @@ pub fn info(input: &str) -> Result<()> { print_wad_info(wad)?; }, Some(WiiFileType::U8) => { - let u8_archive = u8::U8Archive::from_bytes(&fs::read(in_path)?).with_context(|| "The provided U8 archive could not be parsed, and is likely invalid.")?; + let u8_archive = u8::U8Directory::from_bytes(fs::read(in_path)?.into_boxed_slice()).with_context(|| "The provided U8 archive could not be parsed, and is likely invalid.")?; print_u8_info(u8_archive)?; } None => { diff --git a/src/bin/rustwii/main.rs b/src/bin/rustwii/main.rs index 284e04b..e6325b9 100644 --- a/src/bin/rustwii/main.rs +++ b/src/bin/rustwii/main.rs @@ -60,6 +60,11 @@ enum Commands { #[command(subcommand)] command: nand::setting::Commands }, + /// Apply custom themes to the Wii Menu + Theme { + #[command(subcommand)] + command: archive::theme::Commands + }, /// Edit a TMD file Tmd { #[command(subcommand)] @@ -149,7 +154,14 @@ fn main() -> Result<()> { } } }, - Some(Commands::Tmd { command}) => { + Some(Commands::Theme { command }) => { + match command { + archive::theme::Commands::ApplyMym { mym_path, base_path, output } => { + archive::theme::theme_apply_mym(mym_path, base_path, output)? + } + } + }, + Some(Commands::Tmd { command }) => { match command { title::tmd::Commands::Edit { input, output, edits} => { title::tmd::tmd_edit(input, output, edits)?