Skip to content

Instantly share code, notes, and snippets.

@mondul
Created October 3, 2018 22:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mondul/a4705c32de1687a7756173596372345c to your computer and use it in GitHub Desktop.
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.
#!/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