88from modules .core import fatal_error
99
1010
11+ def handle_nus_content (args ):
12+ tid = args .tid
13+ cid = args .cid
14+ version = args .version
15+ if args .decrypt :
16+ decrypt_content = True
17+ else :
18+ decrypt_content = False
19+
20+ # Only accepting the 000000xx format because it's the one that would be most commonly known, rather than using the
21+ # actual integer that the hex Content ID translates to.
22+ content_id = None
23+ try :
24+ content_id = int .from_bytes (binascii .unhexlify (cid ))
25+ except binascii .Error :
26+ fatal_error ("The provided Content ID is invalid! The Content ID must be in the format \" 000000xx\" !" )
27+
28+ # Use the supplied output path if one was specified, otherwise generate one using the Content ID.
29+ if args .output is None :
30+ content_file_name = f"{ content_id :08X} " .lower ()
31+ output_path = pathlib .Path (content_file_name )
32+ else :
33+ output_path = pathlib .Path (args .output )
34+
35+ # Ensure that a version was supplied before downloading, because we need the matching TMD for decryption to work.
36+ if decrypt_content is True and version is None :
37+ fatal_error ("You must specify the version that the requested content belongs to for decryption!" )
38+
39+ # Try to download the content, and catch the ValueError libWiiPy will throw if it can't be found.
40+ print (f"Downloading content with Content ID { cid } ..." )
41+ content_data = None
42+ try :
43+ content_data = libWiiPy .title .download_content (tid , content_id )
44+ except ValueError :
45+ fatal_error ("The specified Title ID or Content ID could not be found!" )
46+
47+ if decrypt_content is True :
48+ output_path = output_path .with_suffix (".app" )
49+ tmd = libWiiPy .title .TMD ()
50+ tmd .load (libWiiPy .title .download_tmd (tid , version ))
51+ # Try to get a Ticket for the title, if a common one is available.
52+ ticket = None
53+ try :
54+ ticket = libWiiPy .title .Ticket ()
55+ ticket .load (libWiiPy .title .download_ticket (tid , wiiu_endpoint = True ))
56+ except ValueError :
57+ fatal_error ("No Ticket is available! Content cannot be decrypted." )
58+
59+ content_hash = 'gggggggggggggggggggggggggggggggggggggggg'
60+ content_size = 0
61+ content_index = 0
62+ for record in tmd .content_records :
63+ if record .content_id == content_id :
64+ content_hash = record .content_hash .decode ()
65+ content_size = record .content_size
66+ content_index = record .index
67+
68+ # If the default hash never changed, then a content record matching the downloaded content couldn't be found,
69+ # which most likely means that the wrong version was specified.
70+ if content_hash == 'gggggggggggggggggggggggggggggggggggggggg' :
71+ fatal_error ("Content was not found in the TMD for the specified version! Content cannot be decrypted." )
72+
73+ # Manually decrypt the content and verify its hash, which is what libWiiPy's get_content() methods do. We just
74+ # can't really use that here because that require setting up a lot more of the title than is necessary.
75+ content_dec = libWiiPy .title .decrypt_content (content_data , ticket .get_title_key (), content_index , content_size )
76+ content_dec_hash = hashlib .sha1 (content_dec ).hexdigest ()
77+ if content_hash != content_dec_hash :
78+ fatal_error ("The decrypted content provided does not match the record at the provided index. \n "
79+ "Expected hash is: {}\n " .format (content_hash ) +
80+ "Actual hash is: {}" .format (content_dec_hash ))
81+ output_path .write_bytes (content_dec )
82+ else :
83+ output_path .write_bytes (content_data )
84+
85+ print (f"Downloaded content with Content ID \" { cid } \" !" )
86+
87+
1188def handle_nus_title (args ):
1289 title_version = None
1390 wad_file = None
1491 output_dir = None
1592 can_decrypt = False
1693 tid = args .tid
17- if args .wii :
18- wiiu_nus_enabled = False
19- else :
20- wiiu_nus_enabled = True
94+ wiiu_nus_enabled = False if args .wii else True
95+ endpoint_override = args .endpoint if args .endpoint else None
2196
2297 # Check if --version was passed, because it'll be None if it wasn't.
2398 if args .version is not None :
@@ -53,9 +128,11 @@ def handle_nus_title(args):
53128 print (" - Downloading and parsing TMD..." )
54129 # Download a specific TMD version if a version was specified, otherwise just download the latest TMD.
55130 if title_version is not None :
56- title .load_tmd (libWiiPy .title .download_tmd (tid , title_version , wiiu_endpoint = wiiu_nus_enabled ))
131+ title .load_tmd (libWiiPy .title .download_tmd (tid , title_version , wiiu_endpoint = wiiu_nus_enabled ,
132+ endpoint_override = endpoint_override ))
57133 else :
58- title .load_tmd (libWiiPy .title .download_tmd (tid , wiiu_endpoint = wiiu_nus_enabled ))
134+ title .load_tmd (libWiiPy .title .download_tmd (tid , wiiu_endpoint = wiiu_nus_enabled ,
135+ endpoint_override = endpoint_override ))
59136 title_version = title .tmd .title_version
60137 # Write out the TMD to a file.
61138 if output_dir is not None :
@@ -64,7 +141,8 @@ def handle_nus_title(args):
64141 # Download the ticket, if we can.
65142 print (" - Downloading and parsing Ticket..." )
66143 try :
67- title .load_ticket (libWiiPy .title .download_ticket (tid , wiiu_endpoint = wiiu_nus_enabled ))
144+ title .load_ticket (libWiiPy .title .download_ticket (tid , wiiu_endpoint = wiiu_nus_enabled ,
145+ endpoint_override = endpoint_override ))
68146 can_decrypt = True
69147 if output_dir is not None :
70148 output_dir .joinpath ("tik" ).write_bytes (title .ticket .dump ())
@@ -87,7 +165,8 @@ def handle_nus_title(args):
87165 f"(Content ID: { title .tmd .content_records [content ].content_id } , "
88166 f"Size: { title .tmd .content_records [content ].content_size } bytes)..." )
89167 content_list .append (libWiiPy .title .download_content (tid , title .tmd .content_records [content ].content_id ,
90- wiiu_endpoint = wiiu_nus_enabled ))
168+ wiiu_endpoint = wiiu_nus_enabled ,
169+ endpoint_override = endpoint_override ))
91170 print (" - Done!" )
92171 # If we're supposed to be outputting to a folder, then write these files out.
93172 if output_dir is not None :
@@ -110,7 +189,8 @@ def handle_nus_title(args):
110189 if wad_file is not None :
111190 # Get the WAD certificate chain.
112191 print (" - Building certificate..." )
113- title .load_cert_chain (libWiiPy .title .download_cert_chain (wiiu_endpoint = wiiu_nus_enabled ))
192+ title .load_cert_chain (libWiiPy .title .download_cert_chain (wiiu_endpoint = wiiu_nus_enabled ,
193+ endpoint_override = endpoint_override ))
114194 # Ensure that the path ends in .wad, and add that if it doesn't.
115195 print ("Packing WAD..." )
116196 if wad_file .suffix != ".wad" :
@@ -121,83 +201,6 @@ def handle_nus_title(args):
121201 print (f"Downloaded title with Title ID \" { args .tid } \" !" )
122202
123203
124- def handle_nus_content (args ):
125- tid = args .tid
126- cid = args .cid
127- version = args .version
128- if args .decrypt :
129- decrypt_content = True
130- else :
131- decrypt_content = False
132-
133- # Only accepting the 000000xx format because it's the one that would be most commonly known, rather than using the
134- # actual integer that the hex Content ID translates to.
135- content_id = None
136- try :
137- content_id = int .from_bytes (binascii .unhexlify (cid ))
138- except binascii .Error :
139- fatal_error ("The provided Content ID is invalid! The Content ID must be in the format \" 000000xx\" !" )
140-
141- # Use the supplied output path if one was specified, otherwise generate one using the Content ID.
142- if args .output is None :
143- content_file_name = f"{ content_id :08X} " .lower ()
144- output_path = pathlib .Path (content_file_name )
145- else :
146- output_path = pathlib .Path (args .output )
147-
148- # Ensure that a version was supplied before downloading, because we need the matching TMD for decryption to work.
149- if decrypt_content is True and version is None :
150- fatal_error ("You must specify the version that the requested content belongs to for decryption!" )
151-
152- # Try to download the content, and catch the ValueError libWiiPy will throw if it can't be found.
153- print (f"Downloading content with Content ID { cid } ..." )
154- content_data = None
155- try :
156- content_data = libWiiPy .title .download_content (tid , content_id )
157- except ValueError :
158- fatal_error ("The specified Title ID or Content ID could not be found!" )
159-
160- if decrypt_content is True :
161- output_path = output_path .with_suffix (".app" )
162- tmd = libWiiPy .title .TMD ()
163- tmd .load (libWiiPy .title .download_tmd (tid , version ))
164- # Try to get a Ticket for the title, if a common one is available.
165- ticket = None
166- try :
167- ticket = libWiiPy .title .Ticket ()
168- ticket .load (libWiiPy .title .download_ticket (tid , wiiu_endpoint = True ))
169- except ValueError :
170- fatal_error ("No Ticket is available! Content cannot be decrypted." )
171-
172- content_hash = 'gggggggggggggggggggggggggggggggggggggggg'
173- content_size = 0
174- content_index = 0
175- for record in tmd .content_records :
176- if record .content_id == content_id :
177- content_hash = record .content_hash .decode ()
178- content_size = record .content_size
179- content_index = record .index
180-
181- # If the default hash never changed, then a content record matching the downloaded content couldn't be found,
182- # which most likely means that the wrong version was specified.
183- if content_hash == 'gggggggggggggggggggggggggggggggggggggggg' :
184- fatal_error ("Content was not found in the TMD for the specified version! Content cannot be decrypted." )
185-
186- # Manually decrypt the content and verify its hash, which is what libWiiPy's get_content() methods do. We just
187- # can't really use that here because that require setting up a lot more of the title than is necessary.
188- content_dec = libWiiPy .title .decrypt_content (content_data , ticket .get_title_key (), content_index , content_size )
189- content_dec_hash = hashlib .sha1 (content_dec ).hexdigest ()
190- if content_hash != content_dec_hash :
191- fatal_error ("The decrypted content provided does not match the record at the provided index. \n "
192- "Expected hash is: {}\n " .format (content_hash ) +
193- "Actual hash is: {}" .format (content_dec_hash ))
194- output_path .write_bytes (content_dec )
195- else :
196- output_path .write_bytes (content_data )
197-
198- print (f"Downloaded content with Content ID \" { cid } \" !" )
199-
200-
201204def handle_nus_tmd (args ):
202205 tid = args .tid
203206
0 commit comments