Created
October 3, 2018 22:33
-
-
Save mondul/a4705c32de1687a7756173596372345c to your computer and use it in GitHub Desktop.
NUS Downloader example Python 2.7 port to download and decrypt the DSi's v512 launcher for the USA, JAP, EUR and AUS regions. In other words, generates the 00000002.app for HiyaCFW. Requires the PyCrypto library, though can be modified to use pyaes instead.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
from urllib2 import Request, urlopen, URLError | |
from sys import argv, exit | |
from struct import unpack_from | |
from binascii import hexlify | |
from math import ceil | |
from hashlib import sha1 | |
from Crypto.Cipher import AES | |
REGION_CODES = { | |
'USA': '45/', | |
'JAP': '4a/', | |
'EUR': '50/', | |
'AUS': '55/' | |
} | |
# Check arguments | |
if len(argv) > 1: | |
try: | |
region = REGION_CODES[argv[1].upper()] | |
except KeyError: | |
print('ERROR: Wrong region\nUsage: python nusd.py <usa|jap|eur|aus>') | |
exit(1) | |
else: | |
print('Usage: python nusd.py <usa|jap|eur|aus>') | |
exit() | |
TITLE_VERSION = '512' | |
C_K = '\xAF\x1B\xF5\x16\xA8\x07\xD2\x1A\xEAE\x98O\x04t(a' | |
HEADER = { 'User-Agent': 'Opera/9.50 (Nintendo; Opera/154; U; Nintendo DS; en)' } | |
title_url = 'http://nus.cdn.t.shop.nintendowifi.net/ccs/download/00030017484e41' + region | |
# Download ticket to memory | |
print('Downloading ticket...') | |
try: | |
ticket_raw = urlopen(Request(title_url + 'cetk', None, HEADER)).read() | |
except URLError: | |
print('ERROR: Could not download ticket') | |
exit(1) | |
# Load encrypted title key | |
encrypted_title_key = unpack_from('16s', ticket_raw, 0x1BF)[0] | |
# Decrypt it | |
iv = unpack_from('8s', ticket_raw, 0x1DC)[0].ljust(16, '\0') | |
title_key = AES.new(C_K, AES.MODE_CBC, iv).decrypt(encrypted_title_key) | |
# Download TMD to memory | |
print('Downloading TMD...') | |
try: | |
tmd_raw = urlopen(Request(title_url + 'tmd.' + TITLE_VERSION, None, | |
HEADER)).read() | |
except URLError: | |
print('ERROR: Could not download TMD') | |
exit(1) | |
# Load number of contents | |
num_contents = unpack_from('>H', tmd_raw, 0x1DE)[0] | |
# Read and download contents, decrypt and save them | |
content_offset = 0x1E4 | |
for i in range(num_contents): | |
content_id = hexlify(unpack_from('4s', tmd_raw, content_offset)[0]) | |
content_offset += 4 | |
iv = unpack_from('2s', tmd_raw, content_offset)[0].ljust(16, '\0') | |
content_offset += 2 | |
# content_type = unpack_from('>H', tmd_raw, content_offset)[0] | |
content_offset += 2 | |
content_size = unpack_from('>Q', tmd_raw, content_offset)[0] | |
content_offset += 8 | |
content_hash = unpack_from('20s', tmd_raw, content_offset)[0] | |
content_offset += 20 | |
# Download content | |
print('Downloading content {} of {}...'.format(i + 1, num_contents)) | |
try: | |
content_raw = urlopen(Request(title_url + content_id, None, HEADER)).read() | |
except URLError: | |
print('WARNiNG: Could not download content ' + content_id) | |
continue | |
print('Decrypting content...') | |
decrypted_content = AES.new(title_key, AES.MODE_CBC, | |
iv).decrypt(content_raw).ljust(content_size, '\xFF') | |
sha1_hash = sha1() | |
sha1_hash.update(decrypted_content) | |
if sha1_hash.digest() == content_hash: | |
filename = content_id + '.app' | |
print('Saving ' + filename + '...') | |
with open(filename, 'wb') as f: | |
f.write(decrypted_content) | |
else: | |
print('WARNiNG: Hash did not match, file not saved') | |
print('Done!') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment