SHARE
TWEET

FunKiiU 3 preview 2

a guest Jan 26th, 2020 102 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. #  FunKiiU 3.0
  4.  
  5. from __future__ import unicode_literals, print_function
  6.  
  7. __VERSION__ = 3.0
  8.  
  9. import argparse
  10. import base64
  11. import binascii
  12. import hashlib
  13. import json
  14. import os
  15. import re
  16. import shutil
  17. import sys
  18. import zlib
  19.  
  20. from time import time_ns
  21. from urllib.request import urlopen, Request
  22. from urllib.error import URLError, HTTPError
  23.  
  24. b64decompress = lambda d: zlib.decompress(base64.b64decode(d))
  25.  
  26. SYMBOLS = {
  27.     'customary': ('B', 'KB', 'MB', 'GB', 'T', 'P', 'E', 'Z', 'Y'),
  28. }
  29. #KEYSITE_MD5 = 'd098abb93c29005dbd07deb43d81c5df'
  30. BLANK_CONFIG = {'keysite': ''}
  31. MAGIC = binascii.a2b_hex('00010003704138EFBBBDA16A987DD901326D1C9459484C88A2861B91A312587AE70EF6237EC50E1032DC39DDE89A96A8E859D76A98A6E7E36A0CFE352CA893058234FF833FCB3B03811E9F0DC0D9A52F8045B4B2F9411B67A51C44B5EF8CE77BD6D56BA75734A1856DE6D4BED6D3A242C7C8791B3422375E5C779ABF072F7695EFA0F75BCB83789FC30E3FE4CC8392207840638949C7F688565F649B74D63D8D58FFADDA571E9554426B1318FC468983D4C8A5628B06B6FC5D507C13E7A18AC1511EB6D62EA5448F83501447A9AFB3ECC2903C9DD52F922AC9ACDBEF58C6021848D96E208732D3D1D9D9EA440D91621C7A99DB8843C59C1F2E2C7D9B577D512C166D6F7E1AAD4A774A37447E78FE2021E14A95D112A068ADA019F463C7A55685AABB6888B9246483D18B9C806F474918331782344A4B8531334B26303263D9D2EB4F4BB99602B352F6AE4046C69A5E7E8E4A18EF9BC0A2DED61310417012FD824CC116CFB7C4C1F7EC7177A17446CBDE96F3EDD88FCD052F0B888A45FDAF2B631354F40D16E5FA9C2C4EDA98E798D15E6046DC5363F3096B2C607A9D8DD55B1502A6AC7D3CC8D8C575998E7D796910C804C495235057E91ECD2637C9C1845151AC6B9A0490AE3EC6F47740A0DB0BA36D075956CEE7354EA3E9A4F2720B26550C7D394324BC0CB7E9317D8A8661F42191FF10B08256CE3FD25B745E5194906B4D61CB4C2E000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000526F6F7400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001434130303030303030330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007BE8EF6CB279C9E2EEE121C6EAF44FF639F88F078B4B77ED9F9560B0358281B50E55AB721115A177703C7A30FE3AE9EF1C60BC1D974676B23A68CC04B198525BC968F11DE2DB50E4D9E7F071E562DAE2092233E9D363F61DD7C19FF3A4A91E8F6553D471DD7B84B9F1B8CE7335F0F5540563A1EAB83963E09BE901011F99546361287020E9CC0DAB487F140D6626A1836D27111F2068DE4772149151CF69C61BA60EF9D949A0F71F5499F2D39AD28C7005348293C431FFBD33F6BCA60DC7195EA2BCC56D200BAF6D06D09C41DB8DE9C720154CA4832B69C08C69CD3B073A0063602F462D338061A5EA6C915CD5623579C3EB64CE44EF586D14BAAA8834019B3EEBEED3790001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100042EA66C66CFF335797D0497B77A197F9FE51AB5A41375DC73FD9E0B10669B1B9A5B7E8AB28F01B67B6254C14AA1331418F25BA549004C378DD72F0CE63B1F7091AAFE3809B7AC6C2876A61D60516C43A63729162D280BE21BE8E2FE057D8EB6E204242245731AB6FEE30E5335373EEBA970D531BBA2CB222D9684387D5F2A1BF75200CE0656E390CE19135B59E14F0FA5C1281A7386CCD1C8EC3FAD70FBCE74DEEE1FD05F46330B51F9B79E1DDBF4E33F14889D05282924C5F5DC2766EF0627D7EEDC736E67C2E5B93834668072216D1C78B823A072D34FF3ECF9BD11A29AF16C33BD09AFB2D74D534E027C19240D595A68EBB305ACC44AB38AB820C6D426560C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000526F6F742D43413030303030303033000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000143503030303030303062000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000137A080BA689C590FD0B2F0D4F56B632FB934ED0739517B33A79DE040EE92DC31D37C7F73BF04BD3E44E20AB5A6FEAF5984CC1F6062E9A9FE56C3285DC6F25DDD5D0BF9FE2EFE835DF2634ED937FAB0214D104809CF74B860E6B0483F4CD2DAB2A9602BC56F0D6BD946AED6E0BE4F08F26686BD09EF7DB325F82B18F6AF2ED525BFD828B653FEE6ECE400D5A48FFE22D538BB5335B4153342D4335ACF590D0D30AE2043C7F5AD214FC9C0FE6FA40A5C86506CA6369BCEE44A32D9E695CF00B4FD79ADB568D149C2028A14C9D71B850CA365B37F70B657791FC5D728C4E18FD22557C4062D74771533C70179D3DAE8F92B117E45CB332F3B3C2A22E705CFEC66F6DA3772B000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010004919EBE464AD0F552CD1B72E7884910CF55A9F02E50789641D896683DC005BD0AEA87079D8AC284C675065F74C8BF37C88044409502A022980BB8AD48383F6D28A79DE39626CCB2B22A0F19E41032F094B39FF0133146DEC8F6C1A9D55CD28D9E1C47B3D11F4F5426C2C780135A2775D3CA679BC7E834F0E0FB58E68860A71330FC95791793C8FBA935A7A6908F229DEE2A0CA6B9B23B12D495A6FE19D0D72648216878605A66538DBF376899905D3445FC5C727A0E13E0E2C8971C9CFA6C60678875732A4E75523D2F562F12AABD1573BF06C94054AEFA81A71417AF9A4A066D0FFC5AD64BAB28B1FF60661F4437D49E1E0D9412EB4BCACF4CFD6A3408847982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000526F6F742D43413030303030303033000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000158533030303030303063000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000137A0894AD505BB6C67E2E5BDD6A3BEC43D910C772E9CC290DA58588B77DCC11680BB3E29F4EABBB26E98C2601985C041BB14378E689181AAD770568E928A2B98167EE3E10D072BEEF1FA22FA2AA3E13F11E1836A92A4281EF70AAF4E462998221C6FBB9BDD017E6AC590494E9CEA9859CEB2D2A4C1766F2C33912C58F14A803E36FCCDCCCDC13FD7AE77C7A78D997E6ACC35557E0D3E9EB64B43C92F4C50D67A602DEB391B06661CD32880BD64912AF1CBCB7162A06F02565D3B0ECE4FCECDDAE8A4934DB8EE67F3017986221155D131C6C3F09AB1945C206AC70C942B36F49A1183BCD78B6E4B47C6C5CAC0F8D62F897C6953DD12F28B70C5B7DF751819A98346526250001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')
  32. TIKTEM = binascii.a2b_hex('00010004d15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11ad15ea5ed15abe11a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000526f6f742d434130303030303030332d585330303030303030630000000000000000000000000000000000000000000000000000000000000000000000000000feedfacefeedfacefeedfacefeedfacefeedfacefeedfacefeedfacefeedfacefeedfacefeedfacefeedfacefeedfacefeedfacefeedfacefeedface010000cccccccccccccccccccccccccccccccc00000000000000000000000000aaaaaaaaaaaaaaaa00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010014000000ac000000140001001400000000000000280000000100000084000000840003000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')
  33.  
  34. TK = 0x140
  35.  
  36. def positiveInt(nr):
  37.     value = int(nr)
  38.     if value < 0:
  39.         raise argparse.ArgumentTypeError("%s is a negative number" % nr)
  40.     return value
  41.  
  42. parser = argparse.ArgumentParser()
  43. parser.add_argument('-outputdir', action='store', dest='output_dir',
  44.                     help='The custom output directory to store output in, if desired')
  45. parser.add_argument('-retry', type=positiveInt, default=4, dest='retry_count',
  46.                     help='How many times a file download will be attempted')
  47. parser.add_argument('-title', nargs='+', dest='titles', default=[],
  48.                     help='Give TitleIDs to be specifically downloaded')
  49. parser.add_argument('-key', nargs='+', dest='keys', default=[],
  50.                     help='Encrypted Title Key for the Title IDs. Must be in the same order as TitleIDs if multiple')
  51. parser.add_argument('-onlinekeys', action='store_true', default=False, dest='onlinekeys',
  52.                     help='Gets latest titlekeys.json file from *theykeysite*, saves (overwrites) it and uses as input')
  53. parser.add_argument('-onlinetickets', action='store_true', default=False, dest='onlinetickets',
  54.                     help='Gets ticket file from *thekeysite*, should create a \'legit\' game')
  55. parser.add_argument('-nopatchdlc', action='store_false', default=True,
  56.                     dest='patch_dlc', help='This will disable unlocking all DLC content')
  57. parser.add_argument('-nopatchdemo', action='store_false', default=True,
  58.                     dest='patch_demo', help='This will disable patching the demo play limit')
  59. parser.add_argument('-region', nargs="?", default=[], dest='download_regions',
  60.                     help='Downloads/gets tickets for regions: [EUR|USA|JPN] from the keyfile')
  61. parser.add_argument('-simulate', action='store_true', default=False, dest='simulate',
  62.                     help="Don't download anything, just do like you would.")
  63. parser.add_argument('-ticketsonly', action='store_true', default=False, dest='tickets_only',
  64.                     help="Only download/generate tickets (and TMD and CERT), don't download any content")
  65.  
  66.  
  67. def bytes2human(n, f='%(value).2f %(symbol)s', symbols='customary'):
  68.     n = int(n)
  69.     if n < 0:
  70.         raise ValueError("n < 0")
  71.     symbols = SYMBOLS[symbols]
  72.     prefix = {}
  73.     for i, s in enumerate(symbols[1:]):
  74.         prefix[s] = 1 << (i + 1) * 10
  75.     for symbol in reversed(symbols[1:]):
  76.         if n >= prefix[symbol]:
  77.             value = float(n) / prefix[symbol]
  78.             return f % locals()
  79.     return f % dict(symbol=symbols[0], value=n)
  80.  
  81.  
  82. RE_16_HEX = re.compile(r'^[0-9a-f]{16}$', re.IGNORECASE)
  83. RE_32_HEX = re.compile(r'^[0-9a-f]{32}$', re.IGNORECASE)
  84.  
  85. check_title_id = RE_16_HEX.match
  86. check_title_key = RE_32_HEX.match
  87.  
  88.  
  89. def retry(count):
  90.     for i in range(1, count + 1):
  91.         if i > 1:
  92.             print("*Attempt {} of {}".format(i, count))
  93.         yield i
  94.  
  95.  
  96. def progress_bar(part, total, length=10, char='#', blank=' ', left='[', right=']', dlSpeed=None):
  97.     percent = int((float(part) / float(total) * 100) % 100)
  98.     bar_len = int((float(part) / float(total) * length) % length)
  99.     bar = char * bar_len
  100.     blanks = blank * (length - bar_len)
  101.     ret =  '{}{}{}{} {} of {}, {}%'.format(
  102.         left, bar, blanks, right, bytes2human(part), bytes2human(total), percent
  103.     )
  104.     if dlSpeed != None:
  105.         ret = '{} {}'.format(ret, dlSpeed)
  106.     return ret
  107.  
  108. def finishLine(line):
  109.     teminalSize = shutil.get_terminal_size((80, 20))
  110.     rest = teminalSize.columns - len(line) - 1
  111.     if rest < 1:
  112.         return line
  113.     return line + (' ' * rest)
  114.  
  115. def download_file(url, outfname, retry_count=3, ignore_404=False, chunk_size=4096, resume=False):
  116.     for _ in retry(retry_count):
  117.         infile = None
  118.         try:
  119.             if os.path.isfile(outfname):
  120.                 statinfo = os.stat(outfname)
  121.                 diskFilesize = statinfo.st_size
  122.             else:
  123.                 diskFilesize = 0
  124.  
  125.             infile = urlopen(url)
  126.             cl = infile.headers['Content-Length']
  127.             if cl == None:
  128.                 expected_size = None
  129.             else:
  130.                 expected_size = int(cl)
  131.             log('-Downloading {}.\n-File size is {}.\n-File in disk is {}.'.format(outfname, expected_size,diskFilesize))
  132.  
  133.             #if not (expected_size is None):
  134.             if expected_size != diskFilesize:
  135.                 if resume and diskFilesize < expected_size:
  136.                     if diskFilesize > 0:
  137.                         print('-Resuming download...')
  138.                         infile.close()
  139.                         req = Request(url)
  140.                         req.add_header("Range","bytes=%s-" % (diskFilesize))
  141.                         infile = urlopen(req)
  142.                         fileMode = 'ab'
  143.                     else:
  144.                         fileMode = 'wb'
  145.                 else:
  146.                     diskFilesize = 0
  147.                     fileMode = 'wb'
  148.  
  149.                 with open(outfname, fileMode) as outfile:
  150.                     oldStamp = None
  151.                     loopCounter = 0
  152.                     dlSpeed = ''
  153.                     while True:
  154.                          buf = infile.read(chunk_size)
  155.                          if not buf:
  156.                              break
  157.                          diskFilesize += len(buf)
  158.                          outfile.write(buf)
  159.  
  160.                          if expected_size and len(buf) == chunk_size:
  161.                              loopCounter = loopCounter + 1
  162.                              if oldStamp != None:
  163.                                  tmpSpeed = time_ns() - oldStamp
  164.                                  if tmpSpeed > 500000000: # We calculate the download speed every 0.5 seconds (in the best case. In the worst more time might have been passed)
  165.                                      tmpSpeed /= 1000000000 # nanoseconds to seconds
  166.                                      tmpSpeed = chunk_size * loopCounter / tmpSpeed # bytes readed / seconds passed = bytes/s
  167.                                      dlSpeed = bytes2human(tmpSpeed) + '/s'
  168.                                      loopCounter = 0
  169.                                      oldStamp = time_ns()
  170.                              else:
  171.                                  oldStamp = time_ns()
  172.                              print(finishLine(' Downloaded {}'.format(progress_bar(diskFilesize, expected_size, dlSpeed=dlSpeed))), end='\r')
  173.  
  174.                     outfile.flush()
  175.                     outfile.close()
  176.             else:
  177.                 print('-File skipped.')
  178.             infile.close()
  179.             infile = None
  180.  
  181.             if expected_size is not None:
  182.                 if int(os.path.getsize(outfname)) != expected_size:
  183.                     print('Content download not correct size\n')
  184.                     continue
  185.                 else:
  186.                     print(finishLine('Download complete: {}'.format(bytes2human(diskFilesize))) + '\n')
  187.         except HTTPError as e:
  188.             if infile != None:
  189.                 infile.close()
  190.             if e.code == 404 and ignore_404:
  191.                 # We are ignoring this because its a 404 error, not a failure
  192.                 return True
  193.         except URLError:
  194.             print('\nCould not download file...\n')
  195.         except ConnectionError as e:
  196.             print('\nError downloading file:')
  197.             print(e)
  198.             print('')
  199.         else:
  200.             if infile != None:
  201.                 infile.close()
  202.             return True
  203.         if infile != None:
  204.             infile.close()
  205.     return False
  206.  
  207.  
  208. def load_config():
  209.     try:
  210.         with open('config.json', 'r') as f:
  211.             return json.load(f)
  212.     except IOError:
  213.         save_config(BLANK_CONFIG)
  214.         return BLANK_CONFIG.copy()
  215.  
  216.  
  217. def save_config(config):
  218.     with open('config.json', 'w') as f:
  219.         json.dump(config, f)
  220.  
  221. def user_input_keysite():
  222.     print('\nPlease type *the* keysite to access online keys and tickets')
  223.     print('Type something like: http://wiiu.xxxxxxxx.xxx')
  224.     print('You MUST type the full address, INCLUDING http:// or https://')
  225.     print('A blank response will exit the program')
  226.     checkurl = input('Enter keysite >> ').strip()
  227.  
  228.     if not checkurl:
  229.         print('Please set "keysite" to that title keys site in config.json')
  230.         sys.exit(1)
  231.     config = load_config()
  232.     config['keysite'] = checkurl
  233.     save_config(config)
  234.     return checkurl
  235.  
  236. def ask_update_keysite():
  237.     print('Would you like to enter a different keysite and try again?')
  238.     choice = input('Enter Y or N >> ').lower().strip()
  239.     if choice == 'y' or choice == 'yes':
  240.         return True
  241.     else:
  242.         print('Exiting program...')
  243.         sys.exit(1)
  244.  
  245.  
  246. def get_keysite():
  247.     config = load_config()
  248.     keysite = config.get('keysite', '')
  249.     if not keysite:
  250.         keysite = user_input_keysite()
  251.     return keysite
  252.  
  253.  
  254. def patch_ticket_dlc(tikdata):
  255.     tikdata[TK + 0x164:TK + 0x210] = b64decompress('eNpjYGQQYWBgWAPEIgwQNghoADEjELeAMTNE8D8BwEBjAABCdSH/')
  256.  
  257.  
  258. def patch_ticket_demo(tikdata):
  259.     tikdata[TK + 0x124:TK + 0x164] = bytes([0x00] * 64)
  260.  
  261.  
  262. def make_ticket(title_id, title_key, title_version, fulloutputpath, patch_demo=False, patch_dlc=False):
  263.     tikdata = bytearray(TIKTEM)
  264.     tikdata[TK + 0xA6:TK + 0xA8] = title_version
  265.     tikdata[TK + 0x9C:TK + 0xA4] = binascii.a2b_hex(title_id)
  266.     tikdata[TK + 0x7F:TK + 0x8F] = binascii.a2b_hex(title_key)
  267.     # not sure what the value at 0xB3 is... mine is 0 but some i see 5.
  268.     # or 0xE0, the reserved data is...?
  269.     typecheck = title_id[4:8]
  270.     if typecheck == '0002' and patch_demo:
  271.         patch_ticket_demo(tikdata)
  272.     elif typecheck == '000c' and patch_dlc:
  273.         patch_ticket_dlc(tikdata)
  274.     open(fulloutputpath, 'wb').write(tikdata)
  275.  
  276.  
  277. def safe_filename(filename):
  278.     """Strip any non-path-safe characters from a filename
  279.    >>> print(safe_filename("Pokémon"))
  280.    Pokémon
  281.    >>> print(safe_filename("幻影異聞録♯FE"))
  282.    幻影異聞録_FE
  283.    """
  284.     keep = ' ._'
  285.     return re.sub(r'_+', '_', ''.join(c if (c.isalnum() or c in keep) else '_' for c in filename)).strip('_ ')
  286.  
  287.  
  288. def process_title_id(title_id, title_key, name=None, region=None, output_dir=None, retry_count=3, onlinetickets=False, patch_demo=False,
  289.                      patch_dlc=False, simulate=False, tickets_only=False):
  290.     if name:
  291.         dirname = '{} - {} - {}'.format(title_id, region, name)
  292.     else:
  293.         dirname = title_id
  294.  
  295.     typecheck = title_id[4:8]
  296.     if typecheck == '000c':
  297.         dirname = dirname + ' - DLC'
  298.     elif typecheck == '000e':
  299.         dirname = dirname + ' - Update'
  300.  
  301.     rawdir = os.path.join('install', safe_filename(dirname))
  302.  
  303.     if simulate:
  304.         log('Simulate: Would start work in in: "{}"'.format(rawdir))
  305.         return
  306.  
  307.     log('Starting work in: "{}"'.format(rawdir))
  308.  
  309.     if output_dir is not None:
  310.         rawdir = os.path.join(output_dir, rawdir)
  311.  
  312.     if not os.path.exists(rawdir):
  313.         os.makedirs(os.path.join(rawdir))
  314.  
  315.     # download stuff
  316.     print('Downloading TMD...')
  317.  
  318.     baseurl = 'http://ccs.cdn.c.shop.nintendowifi.net/ccs/download/{}'.format(title_id)
  319.     tmd_path = os.path.join(rawdir, 'title.tmd')
  320.     if not download_file(baseurl + '/tmd', tmd_path, retry_count):
  321.         print('ERROR: Could not download TMD...')
  322.         print('MAYBE YOU ARE BLOCKING CONNECTIONS TO NINTENDO? IF YOU ARE, DON\'T...! :)')
  323.         print('Skipping title...')
  324.         return
  325.  
  326.     with open(os.path.join(rawdir, 'title.cert'), 'wb') as f:
  327.         f.write(MAGIC)
  328.  
  329.     with open(tmd_path, 'rb') as f:
  330.         tmd = f.read()
  331.  
  332.     title_version = tmd[TK + 0x9C:TK + 0x9E]
  333.  
  334.     # get ticket from keysite, from cdn if game update, or generate ticket
  335.     if typecheck == '000e':
  336.         print('\nThis is an update, so we are getting the legit ticket straight from Nintendo.')
  337.         if not download_file(baseurl + '/cetk', os.path.join(rawdir, 'title.tik'), retry_count):
  338.             print('ERROR: Could not download ticket from {}'.format(baseurl + '/cetk'))
  339.             print('Skipping title...')
  340.             return
  341.     elif onlinetickets:
  342.         keysite = get_keysite()
  343.         tikurl = '{}/ticket/{}.tik'.format(keysite, title_id)
  344.         if not download_file(tikurl, os.path.join(rawdir, 'title.tik'), retry_count):
  345.             print('ERROR: Could not download ticket from {}'.format(keysite))
  346.             print('Skipping title...')
  347.             return
  348.     else:
  349.         make_ticket(title_id, title_key, title_version, os.path.join(rawdir, 'title.tik'), patch_demo, patch_dlc)
  350.  
  351.     if tickets_only:
  352.         print('Ticket, TMD, and CERT completed. Not downloading contents.')
  353.         return
  354.  
  355.  
  356.     print('Downloading Contents...')
  357.     content_count = int(binascii.hexlify(tmd[TK + 0x9E:TK + 0xA0]), 16)
  358.    
  359.     total_size = 0
  360.     for i in range(content_count):
  361.         c_offs = 0xB04 + (0x30 * i)
  362.         total_size += int(binascii.hexlify(tmd[c_offs + 0x08:c_offs + 0x10]), 16)
  363.     print('Total size is {}\n'.format(bytes2human(total_size)))
  364.  
  365.     for i in range(content_count):
  366.         c_offs = 0xB04 + (0x30 * i)
  367.         c_id = binascii.hexlify(tmd[c_offs:c_offs + 0x04]).decode()
  368.         c_type = binascii.hexlify(tmd[c_offs + 0x06:c_offs + 0x8])
  369.         print('Downloading {} of {}.'.format(i + 1, content_count))
  370.         outfname = os.path.join(rawdir, c_id + '.app')
  371.         outfnameh3 = os.path.join(rawdir, c_id + '.h3')
  372.  
  373.         if not download_file('{}/{}'.format(baseurl, c_id), outfname, retry_count, resume=True):
  374.             print('ERROR: Could not download content file... Skipping title')
  375.             return
  376.         if not download_file('{}/{}.h3'.format(baseurl, c_id), outfnameh3, retry_count, ignore_404=True, resume=True):
  377.             print('ERROR: Could not download h3 file... Skipping title')
  378.             return
  379.  
  380.     log('\nTitle download complete in "{}"\n'.format(dirname))
  381.  
  382.  
  383. def main(titles=None, keys=None, onlinekeys=False, onlinetickets=False, download_regions=False, output_dir=None,
  384.          retry_count=3, patch_demo=True, patch_dlc=True, simulate=False, tickets_only=False):
  385.     print('*******\nFunKiiU {} by cearp, the cerea1killer and V10lator\n*******\n'.format(__VERSION__))
  386.     titlekeys_data = []
  387.  
  388.     if download_regions is None:
  389.         print('You need to enter a region code after \'-region\', like \'-region USA\' or \'-region JPN EUR\'')
  390.         sys.exit(1)
  391.     if download_regions and (titles or keys):
  392.         print('If using \'-region\', don\'t give Title IDs or keys, it gets all titles from the keysite')
  393.         sys.exit(1)
  394.     if keys and (len(keys)!=len(titles)):
  395.         print('Number of keys and Title IDs do not match up')
  396.         sys.exit(1)
  397.     if titles and (not keys and not onlinekeys and not onlinetickets):
  398.         print('You also need to provide \'-keys\' or use \'-onlinekeys\' or \'-onlinetickets\'')
  399.         sys.exit(1)
  400.  
  401.  
  402.     if download_regions or onlinekeys or onlinetickets:        
  403.         while True:
  404.             keysite = get_keysite()
  405.             print(u'Downloading/updating data from {}'.format(keysite))
  406.             try:
  407.                 if not download_file('{}/json'.format(keysite), 'titlekeys.json', retry_count, resume=False):
  408.                     print('\nERROR: Could not download data file...')
  409.                     if ask_update_keysite():
  410.                         user_input_keysite()
  411.                 else:
  412.                     break
  413.             except ValueError:
  414.                 print('\nThe saved keysite doesn\'t appear to be a valid url.')
  415.                 if ask_update_keysite():
  416.                     user_input_keysite()
  417.                    
  418.         print('Downloaded data OK!')
  419.  
  420.         with open('titlekeys.json') as data_file:
  421.             titlekeys_data = json.load(data_file)
  422.  
  423.     for title_id in titles:
  424.         title_id = title_id.lower()
  425.         if not check_title_id(title_id):
  426.             print('The Title ID(s) must be 16 hexadecimal characters long')
  427.             print('{} - is not ok.'.format(title_id))
  428.             sys.exit(1)
  429.         title_key = None
  430.         name = None
  431.         region = None
  432.  
  433.         patch = title_id[4:8] == '000e'
  434.  
  435.         if keys:
  436.             title_key = keys.pop()
  437.             if not check_title_key(title_key):
  438.                 print('The key(s) must be 32 hexadecimal characters long')
  439.                 print('{} - is not ok.'.format(title_id))
  440.                 sys.exit(1)
  441.         elif onlinekeys or onlinetickets:
  442.             title_data = next((t for t in titlekeys_data if t['titleID'] == title_id.lower()), None)
  443.  
  444.             if not patch:
  445.                 if not title_data:
  446.                     print("ERROR: Could not find data on {} for {}, skipping".format(keysite, title_id))
  447.                     continue
  448.                 elif onlinetickets:
  449.                     if title_data['ticket'] == '0':
  450.                         print('ERROR: Ticket not available on {} for {}'.format(keysite,title_id))
  451.                         continue
  452.  
  453.                 elif onlinekeys:
  454.                     title_key = title_data['titleKey']
  455.  
  456.             if title_data:
  457.                 name = title_data.get('name', None)
  458.                 region = title_data.get('region', None)
  459.  
  460.         if not (title_key or onlinetickets or patch):
  461.             print('ERROR: Could not find title or ticket for {}'.format(title_id))
  462.             continue
  463.  
  464.         process_title_id(title_id, title_key, name, region, output_dir, retry_count, onlinetickets, patch_demo, patch_dlc, simulate, tickets_only)
  465.  
  466.     if download_regions:
  467.         for title_data in titlekeys_data:
  468.             title_id = title_data['titleID']
  469.             title_key = title_data.get('titleKey', None)
  470.             name = title_data.get('name', None)
  471.             region = title_data.get('region', None)
  472.             typecheck = title_id[4:8]
  473.  
  474.             if not region or (region not in download_regions):
  475.                 continue
  476.             #only get games+dlcs+updates
  477.             if typecheck not in ('0000', '000c', '000e'):
  478.                 continue
  479.             if onlinetickets and (not title_data['ticket']):
  480.                 continue
  481.             elif onlinekeys and (not title_data['titleKey']):
  482.                 continue
  483.  
  484.             process_title_id(title_id, title_key, name, region, output_dir, retry_count, onlinetickets, patch_demo, patch_dlc, simulate, tickets_only)
  485.  
  486.  
  487. def log(output):
  488.     output = output.encode(sys.stdout.encoding, errors='replace')
  489.     if sys.version_info[0] == 3:
  490.         output = output.decode(sys.stdout.encoding, errors='replace')
  491.     print(output)
  492.  
  493.  
  494. if __name__ == '__main__':
  495.     arguments = parser.parse_args()
  496.     main(titles=arguments.titles,
  497.          keys=arguments.keys,
  498.          onlinekeys=arguments.onlinekeys,
  499.          onlinetickets=arguments.onlinetickets,
  500.          download_regions=arguments.download_regions,
  501.          output_dir=arguments.output_dir,
  502.          retry_count=arguments.retry_count,
  503.          patch_demo=arguments.patch_demo,
  504.          patch_dlc=arguments.patch_dlc,
  505.          simulate=arguments.simulate,
  506.          tickets_only=arguments.tickets_only)
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
Top