@@ -7,6 +7,7 @@ use aes::{cipher::{KeyIvInit, StreamCipher}, Aes128};
77use std:: { collections:: HashMap , env, fs:: File , io:: { Cursor , Read , Seek , SeekFrom , Write } , path:: Path , usize, vec} ;
88
99use hex_literal:: hex;
10+ use log:: { debug, info, LevelFilter } ;
1011
1112const CMNKEYS : [ [ u8 ; 16 ] ; 6 ] = [
1213 hex ! ( "64c5fd55dd3ad988325baaec5243db98" ) ,
@@ -99,9 +100,9 @@ fn scramblekey(key_x: u128, key_y: u128) -> u128 {
99100fn dump_section ( ncch : & mut File , cia : & mut CiaReader , offset : u64 , size : u32 , sec_type : NcchSection , sec_idx : usize , ctr : [ u8 ; 16 ] , uses_extra_crypto : u8 , fixed_crypto : u8 , encrypted : bool , keyys : [ u128 ; 2 ] ) {
100101 let sections = [ "ExHeader" , "ExeFS" , "RomFS" ] ;
101102 const CHUNK : u32 = 4194304 ; // 4 MiB
102- println ! ( " {} offset: {:08X}" , sections[ sec_idx] , offset) ;
103- println ! ( " {} counter: {}" , sections[ sec_idx] , hex:: encode( & ctr) ) ;
104- println ! ( " {} size: {} bytes" , sections[ sec_idx] , size) ;
103+ debug ! ( " {} offset: {:08X}" , sections[ sec_idx] , offset) ;
104+ debug ! ( " {} counter: {}" , sections[ sec_idx] , hex:: encode( & ctr) ) ;
105+ debug ! ( " {} size: {} bytes" , sections[ sec_idx] , size) ;
105106
106107 // Prevent integer overflow
107108 match offset. checked_sub ( ncch. stream_position ( ) . unwrap ( ) ) {
@@ -256,7 +257,7 @@ fn get_new_key(key_y: u128, header: &NcchHdr, titleid: String) -> u128 {
256257 seeddb. seek ( SeekFrom :: Current ( 8 ) ) . unwrap ( ) ;
257258 }
258259 }
259- Err ( _) => println ! ( "seeddb.bin not found, trying to connect to Nintendo servers..." )
260+ Err ( _) => debug ! ( "seeddb.bin not found, trying to connect to Nintendo servers..." )
260261 }
261262
262263 // Check into Nintendo's servers
@@ -272,7 +273,7 @@ fn get_new_key(key_y: u128, header: &NcchHdr, titleid: String) -> u128 {
272273 match bytes. try_into ( ) {
273274 Ok ( bytes) => {
274275 seeds. insert ( titleid. clone ( ) , bytes) ;
275- println ! ( "A seed has been found online in the region {}" , country) ;
276+ debug ! ( "A seed has been found online in the region {}" , country) ;
276277 break ;
277278 }
278279 Err ( _) => ( )
@@ -297,7 +298,7 @@ fn get_new_key(key_y: u128, header: &NcchHdr, titleid: String) -> u128 {
297298}
298299
299300fn parse_ncsd ( cia : & mut CiaReader ) {
300- println ! ( "Parsing NCSD in file: {}" , cia. name) ;
301+ debug ! ( "Parsing NCSD in file: {}" , cia. name) ;
301302 cia. seek ( 0 ) ;
302303 let mut tmp: [ u8 ; 512 ] = [ 0u8 ; 512 ] ;
303304 cia. read ( & mut tmp) ;
@@ -314,11 +315,11 @@ fn parse_ncsd(cia: &mut CiaReader) {
314315
315316fn parse_ncch ( cia : & mut CiaReader , offs : u64 , mut titleid : [ u8 ; 8 ] ) {
316317 if cia. from_ncsd {
317- println ! ( " Parsing {} NCCH" , NCSD_PARTITIONS [ cia. cidx as usize ] ) ;
318+ debug ! ( " Parsing {} NCCH" , NCSD_PARTITIONS [ cia. cidx as usize ] ) ;
318319 } else if cia. single_ncch {
319- println ! ( " Parsing NCCH in file: {}" , cia. name) ;
320+ debug ! ( " Parsing NCCH in file: {}" , cia. name) ;
320321 } else {
321- println ! ( "Parsing NCCH: {}" , cia. cidx)
322+ debug ! ( "Parsing NCCH: {}" , cia. cidx)
322323 }
323324
324325 cia. seek ( offs) ;
@@ -332,39 +333,39 @@ fn parse_ncch(cia: &mut CiaReader, offs: u64, mut titleid: [u8; 8]) {
332333
333334 let ncch_key_y = BigEndian :: read_u128 ( header. signature [ 0 ..16 ] . try_into ( ) . unwrap ( ) ) ;
334335
335- println ! ( " Product code: {}" , std:: str :: from_utf8( & header. productcode) . unwrap( ) ) ;
336- println ! ( " KeyY: {:032X}" , ncch_key_y) ;
336+ debug ! ( " Product code: {}" , std:: str :: from_utf8( & header. productcode) . unwrap( ) ) ;
337+ debug ! ( " KeyY: {:032X}" , ncch_key_y) ;
337338 header. titleid . reverse ( ) ;
338- println ! ( " Title ID: {}" , hex:: encode( header. titleid) . to_uppercase( ) ) ;
339+ debug ! ( " Title ID: {}" , hex:: encode( header. titleid) . to_uppercase( ) ) ;
339340 header. titleid . reverse ( ) ;
340- println ! ( " Content ID: {:08X}\n " , cia. content_id) ;
341- println ! ( " Format version: {}\n " , header. formatversion) ;
341+ debug ! ( " Content ID: {:08X}\n " , cia. content_id) ;
342+ debug ! ( " Format version: {}\n " , header. formatversion) ;
342343
343344 let uses_extra_crypto: u8 = header. flags [ 3 ] ;
344345
345346 if flag_to_bool ( uses_extra_crypto) {
346- println ! ( " Uses extra NCCH crypto, keyslot 0x25" ) ;
347+ debug ! ( " Uses extra NCCH crypto, keyslot 0x25" ) ;
347348 }
348349
349350 let mut fixed_crypto: u8 = 0 ;
350351 let mut encrypted: bool = true ;
351352
352353 if flag_to_bool ( header. flags [ 7 ] & 1 ) {
353354 if flag_to_bool ( header. titleid [ 3 ] & 16 ) { fixed_crypto = 2 } else { fixed_crypto = 1 }
354- println ! ( " Uses fixed-key crypto" )
355+ debug ! ( " Uses fixed-key crypto" )
355356 }
356357
357358 if flag_to_bool ( header. flags [ 7 ] & 4 ) {
358359 encrypted = false ;
359- println ! ( " Not encrypted" )
360+ debug ! ( " Not encrypted" )
360361 }
361362
362363 let use_seed_crypto: bool = ( header. flags [ 7 ] & 32 ) != 0 ;
363364 let mut key_y = ncch_key_y;
364365
365366 if use_seed_crypto {
366367 key_y = get_new_key ( ncch_key_y, & header, hex:: encode ( titleid) ) ;
367- println ! ( "Uses 9.6 NCCH Seed crypto with KeyY: {:032X}" , key_y) ;
368+ debug ! ( "Uses 9.6 NCCH Seed crypto with KeyY: {:032X}" , key_y) ;
368369 }
369370
370371 let mut base: String ;
@@ -383,7 +384,7 @@ fn parse_ncch(cia: &mut CiaReader, offs: u64, mut titleid: [u8; 8]) {
383384 cia. content_id
384385 ) ;
385386
386- let mut ncch: File = File :: create ( base) . unwrap ( ) ;
387+ let mut ncch: File = File :: create ( base. clone ( ) ) . unwrap ( ) ;
387388 tmp[ 399 ] = tmp[ 399 ] & 2 | 4 ;
388389
389390 ncch. write_all ( & tmp) . unwrap ( ) ;
@@ -402,6 +403,8 @@ fn parse_ncch(cia: &mut CiaReader, offs: u64, mut titleid: [u8; 8]) {
402403 counter = get_ncch_aes_counter ( & header, NcchSection :: RomFS ) ;
403404 dump_section ( & mut ncch, cia, ( header. romfsoffset * MEDIA_UNIT_SIZE ) as u64 , header. romfssize * MEDIA_UNIT_SIZE , NcchSection :: RomFS , 2 , counter, uses_extra_crypto, fixed_crypto, encrypted, [ ncch_key_y, key_y] ) ;
404405 }
406+
407+ info ! ( "{}" , base) ;
405408}
406409
407410fn parse_cia ( mut romfile : File , filename : String , partition : Option < u8 > ) {
@@ -423,7 +426,7 @@ fn parse_cia(mut romfile: File, filename: String, partition: Option<u8>) {
423426 romfile. read_exact ( & mut tid[ 0 ..8 ] ) . unwrap ( ) ;
424427
425428 if hex:: encode ( tid) . starts_with ( "00048" ) {
426- println ! ( "Unsupported CIA file" ) ;
429+ debug ! ( "Unsupported CIA file" ) ;
427430 return
428431 }
429432
@@ -479,35 +482,58 @@ fn parse_cia(mut romfile: File, filename: String, partition: Option<u8>) {
479482 }
480483 parse_ncch ( & mut cia_handle, 0 , tid[ 0 ..8 ] . try_into ( ) . unwrap ( ) ) ;
481484
482- } else { println ! ( "CIA content can't be parsed, skipping partition" ) }
483- Err ( _) => println ! ( "CIA content can't be parsed, skipping partition" )
485+ } else { debug ! ( "CIA content can't be parsed, skipping partition" ) }
486+ Err ( _) => debug ! ( "CIA content can't be parsed, skipping partition" )
484487 }
485488 }
486489}
487490
488491fn main ( ) {
489492 let args: Vec < String > = std:: env:: args ( ) . collect ( ) ;
493+
490494 let mut partition: Option < u8 > = None ;
491- if args. len ( ) < 2 || args. len ( ) == 3 {
492- // Decrypting a specific NCCH container is supported only for .cia backups
493- println ! ( "Usage: ctrdecrypt <ROMFILE> [--ncch <partion-number>]" ) ;
495+ let mut verbose = true ;
496+
497+ if args. len ( ) < 2 {
498+ println ! ( "Usage: ctrdecrypt <ROMFILE> [OPTIONS]\n Options:\n \t --ncch <partition-number>\n \t --no-verbose" ) ;
494499 return ;
495- } else if !Path :: exists ( Path :: new ( & args[ 1 ] ) ) {
500+ }
501+
502+ if !Path :: exists ( Path :: new ( & args[ 1 ] ) ) {
496503 println ! ( "ROM does not exist" ) ;
497504 return ;
498- } else if args. len ( ) == 4 {
499- if args[ 2 ] != "--ncch" {
500- println ! ( "Invalid argument: {}" , args[ 2 ] ) ;
501- return ;
502- }
503- if args[ 3 ] . parse :: < u8 > ( ) . is_err ( ) {
504- println ! ( "Invalid partition number: {}" , args[ 3 ] ) ;
505- return ;
506- } else {
507- partition = Some ( args[ 3 ] . parse :: < u8 > ( ) . unwrap ( ) ) ;
505+ }
506+
507+ let mut i = 2 ;
508+ while i < args. len ( ) {
509+ match args[ i] . as_str ( ) {
510+ "--ncch" => {
511+ if i + 1 >= args. len ( ) {
512+ println ! ( "Missing partition number" ) ;
513+ return ;
514+ }
515+ match args[ i + 1 ] . parse :: < u8 > ( ) {
516+ Ok ( num) => partition = Some ( num) ,
517+ Err ( _) => {
518+ println ! ( "Invalid partition number: {}" , args[ i + 1 ] ) ;
519+ }
520+ }
521+ i += 1 ; // Partition number already checked
522+ }
523+ "--no-verbose" => verbose = false ,
524+ _ => {
525+ println ! ( "Invalid argument: {}" , args[ i] ) ;
526+ return ;
527+ }
508528 }
529+ i += 1 ;
509530 }
510531
532+ env_logger:: Builder :: new ( )
533+ . format ( |buf, record| writeln ! ( buf, "{}" , record. args( ) ) )
534+ . filter ( None , if verbose { LevelFilter :: Debug } else { LevelFilter :: Info } )
535+ . init ( ) ;
536+
511537 let mut rom = File :: open ( & args[ 1 ] ) . unwrap ( ) ;
512538 rom. seek ( SeekFrom :: Start ( 256 ) ) . unwrap ( ) ;
513539 let mut magic: [ u8 ; 4 ] = [ 0u8 ; 4 ] ;
0 commit comments