33//
44// Code for WAD-related commands in the rustii CLI.
55
6- use std:: { str, fs} ;
6+ use std:: { str, fs, fmt } ;
77use std:: path:: { Path , PathBuf } ;
8+ use anyhow:: { bail, Context , Result } ;
89use clap:: { Subcommand , Args } ;
910use glob:: glob;
10- use rustii:: title:: { cert, tmd, ticket, content, wad} ;
11+ use rustii:: title:: { cert, crypto , tmd, ticket, content, wad} ;
1112use rustii:: title;
1213
1314#[ derive( Subcommand ) ]
@@ -20,6 +21,8 @@ pub enum Commands {
2021 /// An (optional) WAD name; defaults to <input name>_<new type>.wad
2122 #[ arg( short, long) ]
2223 output : Option < String > ,
24+ #[ command( flatten) ]
25+ target : ConvertTargets ,
2326 } ,
2427 /// Pack a directory into a WAD file
2528 Pack {
@@ -38,13 +41,103 @@ pub enum Commands {
3841}
3942
4043#[ derive( Args ) ]
44+ #[ clap( next_help_heading = "Encryption Targets" ) ]
4145#[ group( multiple = false , required = true ) ]
42- struct ConvertTargets {
43-
46+ pub struct ConvertTargets {
47+ /// Use the retail common key, allowing this WAD to be installed on retail consoles and Dolphin
48+ #[ arg( long) ]
49+ retail : bool ,
50+ /// Use the development common key, allowing this WAD to be installed on development consoles
51+ #[ arg( long) ]
52+ dev : bool ,
53+ /// Use the vWii key, allowing this WAD to theoretically be installed from Wii U mode if a Wii U mode WAD installer is created
54+ #[ arg( long) ]
55+ vwii : bool ,
4456}
4557
46- pub fn convert_wad ( input : & str , output : & Option < String > ) {
47- todo ! ( ) ;
58+ enum Target {
59+ Retail ,
60+ Dev ,
61+ Vwii ,
62+ }
63+
64+ impl fmt:: Display for Target {
65+ fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
66+ match self {
67+ Target :: Retail => write ! ( f, "retail" ) ,
68+ Target :: Dev => write ! ( f, "development" ) ,
69+ Target :: Vwii => write ! ( f, "vWii" ) ,
70+ }
71+ }
72+ }
73+
74+ pub fn convert_wad ( input : & str , target : & ConvertTargets , output : & Option < String > ) -> Result < ( ) > {
75+ let in_path = Path :: new ( input) ;
76+ if !in_path. exists ( ) {
77+ bail ! ( "Source WAD \" {}\" could not be found." , input) ;
78+ }
79+ // Parse the target passed to identify the encryption target.
80+ let target = if target. dev {
81+ Target :: Dev
82+ } else if target. vwii {
83+ Target :: Vwii
84+ } else {
85+ Target :: Retail
86+ } ;
87+ // Get the output name now that we know the target, if one wasn't passed.
88+ let out_path = if output. is_some ( ) {
89+ PathBuf :: from ( output. clone ( ) . unwrap ( ) ) . with_extension ( "wad" )
90+ } else {
91+ match target {
92+ Target :: Retail => PathBuf :: from ( format ! ( "{}_retail" , in_path. file_stem( ) . unwrap( ) . to_str( ) . unwrap( ) ) ) . with_extension ( "wad" ) ,
93+ Target :: Dev => PathBuf :: from ( format ! ( "{}_dev" , in_path. file_stem( ) . unwrap( ) . to_str( ) . unwrap( ) ) ) . with_extension ( "wad" ) ,
94+ Target :: Vwii => PathBuf :: from ( format ! ( "{}_vWii" , in_path. file_stem( ) . unwrap( ) . to_str( ) . unwrap( ) ) ) . with_extension ( "wad" ) ,
95+ }
96+ } ;
97+ let mut title = title:: Title :: from_bytes ( fs:: read ( in_path) ?. as_slice ( ) ) . with_context ( || "The provided WAD file could not be loaded, and is likely invalid." ) ?;
98+ // Bail if the WAD is already using the selected encryption.
99+ if matches ! ( target, Target :: Dev ) && title. ticket . is_dev ( ) {
100+ bail ! ( "This is already a development WAD!" ) ;
101+ } else if matches ! ( target, Target :: Retail ) && !title. ticket . is_dev ( ) && !title. tmd . is_vwii ( ) {
102+ bail ! ( "This is already a retail WAD!" ) ;
103+ } else if matches ! ( target, Target :: Vwii ) && !title. ticket . is_dev ( ) && title. tmd . is_vwii ( ) {
104+ bail ! ( "This is already a vWii WAD!" ) ;
105+ }
106+ // Save the current encryption to display at the end.
107+ let source = if title. ticket . is_dev ( ) {
108+ "development"
109+ } else if title. tmd . is_vwii ( ) {
110+ "vWii"
111+ } else {
112+ "retail"
113+ } ;
114+ let title_key = title. ticket . dec_title_key ( ) ;
115+ let title_key_new: [ u8 ; 16 ] ;
116+ match target {
117+ Target :: Dev => {
118+ title. tmd . set_signature_issuer ( String :: from ( "Root-CA00000002-CP00000007" ) ) ?;
119+ title. ticket . set_signature_issuer ( String :: from ( "Root-CA00000002-XS00000006" ) ) ?;
120+ title_key_new = crypto:: encrypt_title_key ( title_key, 0 , title. ticket . title_id , Some ( true ) ) ;
121+ title. ticket . common_key_index = 0 ;
122+ } ,
123+ Target :: Retail => {
124+ title. tmd . set_signature_issuer ( String :: from ( "Root-CA00000001-CP00000004" ) ) ?;
125+ title. ticket . set_signature_issuer ( String :: from ( "Root-CA00000001-XS00000003" ) ) ?;
126+ title_key_new = crypto:: encrypt_title_key ( title_key, 0 , title. ticket . title_id , Some ( false ) ) ;
127+ title. ticket . common_key_index = 0 ;
128+ } ,
129+ Target :: Vwii => {
130+ title. tmd . set_signature_issuer ( String :: from ( "Root-CA00000001-CP00000004" ) ) ?;
131+ title. ticket . set_signature_issuer ( String :: from ( "Root-CA00000001-XS00000003" ) ) ?;
132+ title_key_new = crypto:: encrypt_title_key ( title_key, 2 , title. ticket . title_id , Some ( false ) ) ;
133+ title. ticket . common_key_index = 2 ;
134+ }
135+ }
136+ title. ticket . title_key = title_key_new;
137+ title. fakesign ( ) ?;
138+ fs:: write ( out_path. clone ( ) , title. to_wad ( ) ?. to_bytes ( ) ?) ?;
139+ println ! ( "Successfully converted {} WAD to {} WAD \" {}\" !" , source, target, out_path. file_name( ) . unwrap( ) . to_str( ) . unwrap( ) ) ;
140+ Ok ( ( ) )
48141}
49142
50143pub fn pack_wad ( input : & str , output : & str ) {
0 commit comments