11mod ctrutils;
2- use ctrutils:: { cbc_decrypt, gen_iv, CiaContent , CiaFile , CiaReader , NcchHdr } ;
2+ use ctrutils:: { cbc_decrypt, gen_iv, CiaContent , CiaFile , CiaReader , NcchHdr , NcsdHdr } ;
33
44use byteorder:: { ByteOrder , BigEndian , LittleEndian , ReadBytesExt } ;
55use aes:: { cipher:: { KeyIvInit , StreamCipher } , Aes128 } ;
66
7- use std:: { collections:: HashMap , env, fs:: File , io:: { Read , Seek , SeekFrom , Write , Cursor } , path:: Path , usize, vec} ;
7+ use std:: { collections:: HashMap , env, fs:: File , io:: { Cursor , Read , Seek , SeekFrom , Write } , path:: Path , usize, vec} ;
88
99use hex_literal:: hex;
1010
@@ -211,10 +211,9 @@ fn dump_section(ncch: &mut File, cia: &mut CiaReader, offset: u64, size: u32, se
211211 let mut sizeleft = size;
212212 let mut buf = vec ! [ 0u8 ; CHUNK as usize ] ;
213213 let mut ctr_cipher = Aes128Ctr :: new_from_slices ( & key, & ctr) . unwrap ( ) ;
214-
215214 while sizeleft > CHUNK {
216215 cia. read ( & mut buf) ;
217- if cia. cidx != 0 { buf[ 1 ] = buf[ 1 ] ^ cia. cidx as u8 }
216+ if cia. cidx > 0 && ! ( cia . single_ncch || cia . from_ncsd ) { buf[ 1 ] = buf[ 1 ] ^ cia. cidx as u8 }
218217 ctr_cipher. apply_keystream ( & mut buf) ;
219218 ncch. write_all ( & buf) . unwrap ( ) ;
220219 sizeleft -= CHUNK ;
@@ -223,7 +222,7 @@ fn dump_section(ncch: &mut File, cia: &mut CiaReader, offset: u64, size: u32, se
223222 if sizeleft > 0 {
224223 buf = vec ! [ 0u8 ; sizeleft as usize ] ;
225224 cia. read ( & mut buf) ;
226- if cia. cidx != 0 { buf[ 1 ] = buf[ 1 ] ^ cia. cidx as u8 }
225+ if cia. cidx > 0 && ! ( cia . single_ncch || cia . from_ncsd ) { buf[ 1 ] = buf[ 1 ] ^ cia. cidx as u8 }
227226 ctr_cipher. apply_keystream ( & mut buf) ;
228227 ncch. write_all ( & buf) . unwrap ( ) ;
229228 }
@@ -262,9 +261,6 @@ fn get_new_key(key_y: u128, header: &NcchHdr, titleid: String) -> u128 {
262261
263262 // Check into Nintendo's servers
264263 if !seeds. contains_key ( & titleid) {
265- println ! ( "\t ********************************" ) ;
266- println ! ( "\t Couldn't find seed in seeddb, checking online..." ) ;
267- println ! ( "\t ********************************" ) ;
268264 for country in [ "JP" , "US" , "GB" , "KR" , "TW" , "AU" , "NZ" ] {
269265 let req = attohttpc:: get ( format ! ( "https://kagiya-ctr.cdn.nintendo.net/title/0x{}/ext_key?country={}" , titleid, country) )
270266 . danger_accept_invalid_certs ( true )
@@ -273,9 +269,13 @@ fn get_new_key(key_y: u128, header: &NcchHdr, titleid: String) -> u128 {
273269 if req. is_success ( ) {
274270 let bytes = req. bytes ( ) . unwrap ( ) ;
275271
276- if bytes. len ( ) == 16 {
277- seeds. insert ( titleid. clone ( ) , bytes. try_into ( ) . unwrap ( ) ) ;
278- break ;
272+ match bytes. try_into ( ) {
273+ Ok ( bytes) => {
274+ seeds. insert ( titleid. clone ( ) , bytes) ;
275+ println ! ( "A seed has been found online in the region {}" , country) ;
276+ break ;
277+ }
278+ Err ( _) => ( )
279279 }
280280 }
281281 }
@@ -296,9 +296,31 @@ fn get_new_key(key_y: u128, header: &NcchHdr, titleid: String) -> u128 {
296296 new_key
297297}
298298
299- fn parse_ncch ( mut cia : CiaReader , mut titleid : [ u8 ; 8 ] , from_ncsd : bool ) {
300- println ! ( "Parsing NCCH : {}" , cia. cidx ) ;
299+ fn parse_ncsd ( cia : & mut CiaReader ) {
300+ println ! ( "Parsing NCSD in file : {}" , cia. name ) ;
301301 cia. seek ( 0 ) ;
302+ let mut tmp: [ u8 ; 512 ] = [ 0u8 ; 512 ] ;
303+ cia. read ( & mut tmp) ;
304+ let mut header: NcsdHdr = unsafe { std:: mem:: transmute ( tmp) } ;
305+ for idx in 0 ..header. offset_sizetable . len ( ) {
306+ if header. offset_sizetable [ idx] . offset != 0 {
307+ cia. cidx = idx as u16 ;
308+ header. titleid . reverse ( ) ;
309+ parse_ncch ( cia, ( header. offset_sizetable [ idx] . offset * MEDIA_UNIT_SIZE ) . clone ( ) . into ( ) , header. titleid ) ;
310+ }
311+ }
312+ }
313+
314+ fn parse_ncch ( cia : & mut CiaReader , offs : u64 , mut titleid : [ u8 ; 8 ] ) {
315+ if cia. from_ncsd {
316+ println ! ( " Parsing {} NCCH" , NCSD_PARTITIONS [ cia. cidx as usize ] ) ;
317+ } else if cia. single_ncch {
318+ println ! ( " Parsing NCCH in file: {}" , cia. name) ;
319+ } else {
320+ println ! ( "Parsing NCCH: {}" , cia. cidx)
321+ }
322+
323+ cia. seek ( offs) ;
302324 let mut tmp = [ 0u8 ; 512 ] ;
303325 cia. read ( & mut tmp) ;
304326 let mut header: NcchHdr = unsafe { std:: mem:: transmute ( tmp) } ;
@@ -342,11 +364,18 @@ fn parse_ncch(mut cia: CiaReader, mut titleid: [u8; 8], from_ncsd: bool) {
342364 key_y = get_new_key ( ncch_key_y, & header, hex:: encode ( titleid) ) ;
343365 println ! ( "Uses 9.6 NCCH Seed crypto with KeyY: {:032X}" , key_y) ;
344366 }
345- let mut base: String = cia. name . strip_suffix ( ".cia" ) . unwrap ( ) . to_string ( ) ;
367+
368+ let mut base: String ;
369+ if cia. single_ncch || cia. from_ncsd {
370+ base = cia. name . strip_suffix ( ".3ds" ) . unwrap ( ) . to_string ( ) ;
371+ } else {
372+ base = cia. name . strip_suffix ( ".cia" ) . unwrap ( ) . to_string ( ) ;
373+ }
374+
346375 base = format ! ( "{}/{}.{}.ncch" ,
347376 env:: current_dir( ) . unwrap( ) . to_str( ) . unwrap( ) ,
348377 base,
349- if from_ncsd { NCSD_PARTITIONS [ cia. cidx as usize ] . to_string( ) } else { cia. cidx. to_string( ) }
378+ if cia . from_ncsd { NCSD_PARTITIONS [ cia. cidx as usize ] . to_string( ) } else { cia. cidx. to_string( ) }
350379 ) ;
351380
352381 let mut ncch: File = File :: create ( base) . unwrap ( ) ;
@@ -356,17 +385,17 @@ fn parse_ncch(mut cia: CiaReader, mut titleid: [u8; 8], from_ncsd: bool) {
356385 let mut counter: [ u8 ; 16 ] ;
357386 if header. exhdrsize != 0 {
358387 counter = get_ncch_aes_counter ( & header, NcchSection :: ExHeader ) ;
359- dump_section ( & mut ncch, & mut cia, 512 , header. exhdrsize * 2 , NcchSection :: ExHeader , 0 , counter, uses_extra_crypto, fixed_crypto, encrypted, [ ncch_key_y, key_y] ) ;
388+ dump_section ( & mut ncch, cia, 512 , header. exhdrsize * 2 , NcchSection :: ExHeader , 0 , counter, uses_extra_crypto, fixed_crypto, encrypted, [ ncch_key_y, key_y] ) ;
360389 }
361390
362391 if header. exefssize != 0 {
363392 counter = get_ncch_aes_counter ( & header, NcchSection :: ExeFS ) ;
364- dump_section ( & mut ncch, & mut cia, ( header. exefsoffset * MEDIA_UNIT_SIZE ) as u64 , header. exefssize * MEDIA_UNIT_SIZE , NcchSection :: ExeFS , 1 , counter, uses_extra_crypto, fixed_crypto, encrypted, [ ncch_key_y, key_y] ) ;
393+ dump_section ( & mut ncch, cia, ( header. exefsoffset * MEDIA_UNIT_SIZE ) as u64 , header. exefssize * MEDIA_UNIT_SIZE , NcchSection :: ExeFS , 1 , counter, uses_extra_crypto, fixed_crypto, encrypted, [ ncch_key_y, key_y] ) ;
365394 }
366395
367396 if header. romfssize != 0 {
368397 counter = get_ncch_aes_counter ( & header, NcchSection :: RomFS ) ;
369- dump_section ( & mut ncch, & mut 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] ) ;
398+ 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] ) ;
370399 }
371400}
372401
@@ -436,9 +465,9 @@ fn parse_cia(mut romfile: File, filename: String) {
436465 Ok ( utf8) => if utf8 == "NCCH"
437466 {
438467 romfile. seek ( SeekFrom :: Start ( contentoffs + next_content_offs) ) . unwrap ( ) ;
439- let cia_handle = CiaReader :: new ( romfile. try_clone ( ) . unwrap ( ) , cenc, filename. clone ( ) , titkey, content. cidx , contentoffs + next_content_offs) ;
468+ let mut cia_handle = CiaReader :: new ( romfile. try_clone ( ) . unwrap ( ) , cenc, filename. clone ( ) , titkey, content. cidx , contentoffs + next_content_offs, false , false ) ;
440469 next_content_offs += align ( content. csize , 64 ) ;
441- parse_ncch ( cia_handle, tid[ 0 ..8 ] . try_into ( ) . unwrap ( ) , false ) ;
470+ parse_ncch ( & mut cia_handle, 0 , tid[ 0 ..8 ] . try_into ( ) . unwrap ( ) ) ;
442471 } else { println ! ( "CIA content can't be parsed, skipping partition" ) }
443472 Err ( _) => println ! ( "CIA content can't be parsed, skipping partition" )
444473 }
@@ -456,7 +485,25 @@ fn main() {
456485 }
457486
458487 let mut rom = File :: open ( & args[ 1 ] ) . unwrap ( ) ;
459-
488+ rom. seek ( SeekFrom :: Start ( 256 ) ) . unwrap ( ) ;
489+ let mut magic: [ u8 ; 4 ] = [ 0u8 ; 4 ] ;
490+ rom. read_exact ( & mut magic) . unwrap ( ) ;
491+
492+ match std:: str:: from_utf8 ( & magic) {
493+ Ok ( ptype) => {
494+ if ptype == "NCSD" {
495+ let mut reader = CiaReader :: new ( rom. try_clone ( ) . unwrap ( ) , false , args[ 1 ] . to_string ( ) , [ 0u8 ; 16 ] , 0 , 0 , false , true ) ;
496+ parse_ncsd ( & mut reader) ;
497+ return ;
498+ } else if ptype == "NCCH" {
499+ let mut reader = CiaReader :: new ( rom. try_clone ( ) . unwrap ( ) , false , args[ 1 ] . to_string ( ) , [ 0u8 ; 16 ] , 0 , 0 , true , false ) ;
500+ parse_ncch ( & mut reader, 0 , [ 0u8 ; 8 ] ) ;
501+ return ;
502+ }
503+ }
504+ Err ( _) => ( )
505+ }
506+
460507 if args[ 1 ] . ends_with ( ".cia" ) {
461508 let mut check: [ u8 ; 4 ] = [ 0 ; 4 ] ;
462509 rom. read_exact ( & mut check) . unwrap ( ) ;
0 commit comments