Exemplo n.º 1
0
def save(confFile='conf/nut.conf'):
    Print.debug("saving config")
    os.makedirs(os.path.dirname(confFile), exist_ok=True)
    j = {}
    try:
        with open(confFile, encoding='utf8') as f:
            j = json.load(f)
    except BaseException:  # pylint: disable=broad-except
        pass

    jset(j, ['paths'], paths.__dict__)
    jset(j, ['compression'], compression.__dict__)
    jset(j, ['pullUrls'], pullUrls)
    jset(j, ['threads'], threads)
    jset(j, ['download'], download.__dict__)
    jset(j, ['server', 'hostname'], server.hostname)
    jset(j, ['server', 'port'], server.port)

    jset(j, ['autolaunchBrowser'], autolaunchBrowser)
    jset(j, ['autoUpdateTitleDb'], autoUpdateTitleDb)
    jset(j, ['allowNoMetadata'], allowNoMetadata)

    with open(confFile, 'w', encoding='utf-8') as f:
        Print.debug("writing config to filesystem")
        json.dump(j, f, indent=4)
Exemplo n.º 2
0
def decrypt_NCA(fPath, outDir=''):
    fName = os.path.basename(fPath).split()[0]

    if outDir == '':
        outDir = os.path.splitext(fPath)[0]
    os.makedirs(outDir, exist_ok=True)

    commandLine = hactoolPath + ' "' + fPath + '"' + keysArg \
         + ' --exefsdir="' + outDir + os.sep + 'exefs"' \
         + ' --romfsdir="' + outDir + os.sep + 'romfs"' \
         + ' --section0dir="' + outDir + os.sep + 'section0"' \
         + ' --section1dir="' + outDir + os.sep + 'section1"' \
         + ' --section2dir="' + outDir + os.sep + 'section2"' \
         + ' --section3dir="' + outDir + os.sep + 'section3"' \
         + ' --header="' + outDir + os.sep + 'Header.bin"'

    try:
        Print.debug(commandLine)
        subprocess.check_output(commandLine, shell=True)
        if os.listdir(outDir) == []:
            raise subprocess.CalledProcessError(
                'Decryption failed, output folder %s is empty!' % outDir,
                cmd=commandLine)
    except subprocess.CalledProcessError:
        Print.error('Decryption failed!')
        raise

    return outDir
Exemplo n.º 3
0
def downloadFile(url, fPath):
    fName = os.path.basename(fPath).split()[0]
    r = makeRequest('GET', url)
    with open(fPath, 'wb') as f:
        f.write(r.content)
    Print.debug('\r\nSaved to %s!' % f.name)
    return fPath
Exemplo n.º 4
0
def makeRequest(method, url, hdArgs={}, start=None, end=None, accept='*/*'):
    if start is None:
        reqHd = {
            'Accept': accept,
            'Connection': None,
            'Accept-Encoding': None,
        }
    else:
        reqHd = {
            'Accept': accept,
            'Connection': None,
            'Accept-Encoding': None,
            'Range': 'bytes=%d-%d' % (start, end - 1),
        }

    reqHd.update(hdArgs)

    r = requests.request(method,
                         url,
                         headers=reqHd,
                         verify=False,
                         stream=True,
                         timeout=15)

    Print.debug('%s %s %s' % (method, str(r.status_code), url))
    Print.debug(r.request.headers)

    if r.status_code == 403:
        raise IOError('Forbidden ' + r.text)

    return r
Exemplo n.º 5
0
def download_file(url, fPath, titleId=None):
    fName = os.path.basename(fPath).split()[0]

    if os.path.exists(fPath):
        dlded = os.path.getsize(fPath)
        r = make_request('GET', url, hdArgs={'Range': 'bytes=%s-' % dlded})

        if r.headers.get('Server') != 'openresty/1.9.7.4':
            Print.info('Download is already complete, skipping!')
            return fPath
        elif r.headers.get(
                'Content-Range'
        ) == None:  # CDN doesn't return a range if request >= filesize
            fSize = int(r.headers.get('Content-Length'))
        else:
            fSize = dlded + int(r.headers.get('Content-Length'))

        if dlded == fSize:
            Print.info('Download is already complete, skipping!')
            return fPath
        elif dlded < fSize:
            Print.info('Resuming download...')
            f = open(fPath, 'ab')
        else:
            Print.error(
                'Existing file is bigger than expected (%s/%s), restarting download...'
                % (dlded, fSize))
            dlded = 0
            f = open(fPath, "wb")
    else:
        dlded = 0
        r = make_request('GET', url)
        fSize = int(r.headers.get('Content-Length'))
        f = open(fPath, 'wb')

    chunkSize = 0x100000

    if fSize >= 10000:
        s = Status.create(fSize, desc=fName, unit='B')
        s.id = titleId.upper()
        s.add(dlded)
        for chunk in r.iter_content(chunkSize):
            f.write(chunk)
            s.add(len(chunk))
            dlded += len(chunk)

            if not Config.isRunning:
                break
        s.close()
    else:
        f.write(r.content)
        dlded += len(r.content)

    if fSize != 0 and dlded != fSize:
        raise ValueError('Downloaded data is not as big as expected (%s/%s)!' %
                         (dlded, fSize))

    f.close()
    Print.debug('\r\nSaved to %s!' % f.name)
    return fPath
Exemplo n.º 6
0
    def gen_xml(self, ncaPath, outf):
        data = self.parse()

        ContentMeta = ET.Element('ContentMeta')

        ET.SubElement(ContentMeta, 'Type').text = self.type
        ET.SubElement(ContentMeta, 'Id').text = '0x' + self.id
        ET.SubElement(ContentMeta, 'Version').text = self.ver
        ET.SubElement(ContentMeta,
                      'RequiredDownloadSystemVersion').text = self.dlsysver

        n = 1
        for titleId in data:
            locals()["Content" + str(n)] = ET.SubElement(
                ContentMeta, 'Content')
            ET.SubElement(locals()["Content" + str(n)],
                          'Type').text = data[titleId][0]
            ET.SubElement(locals()["Content" + str(n)], 'Id').text = titleId
            ET.SubElement(locals()["Content" + str(n)],
                          'Size').text = str(data[titleId][1])
            ET.SubElement(locals()["Content" + str(n)],
                          'Hash').text = data[titleId][2]
            ET.SubElement(locals()["Content" + str(n)],
                          'KeyGeneration').text = self.mkeyrev
            n += 1

        # cnmt.nca itself
        cnmt = ET.SubElement(ContentMeta, 'Content')
        ET.SubElement(cnmt, 'Type').text = 'Meta'
        ET.SubElement(cnmt,
                      'Id').text = os.path.basename(ncaPath).split('.')[0]
        ET.SubElement(cnmt, 'Size').text = str(os.path.getsize(ncaPath))
        ET.SubElement(cnmt, 'Hash').text = sha256_file(ncaPath)
        ET.SubElement(cnmt, 'KeyGeneration').text = self.mkeyrev

        ET.SubElement(ContentMeta, 'Digest').text = self.digest
        ET.SubElement(ContentMeta, 'KeyGenerationMin').text = self.mkeyrev
        ET.SubElement(ContentMeta, 'RequiredSystemVersion').text = self.sysver
        if self.type == 'Application':
            ET.SubElement(
                ContentMeta,
                'PatchId').text = '0x%016x' % (int(self.id, 16) + 0x800)
        elif self.type == 'Patch':
            ET.SubElement(
                ContentMeta,
                'OriginalId').text = '0x%016x' % (int(self.id, 16)
                                                  & 0xFFFFFFFFFFFFF000)
        elif self.type == 'AddOnContent':
            ET.SubElement(
                ContentMeta,
                'ApplicationId').text = '0x%016x' % (int(self.id, 16) - 0x1000
                                                     & 0xFFFFFFFFFFFFF000)

        string = ET.tostring(ContentMeta, encoding='utf-8')
        reparsed = minidom.parseString(string)
        with open(outf, 'wb') as f:
            f.write(reparsed.toprettyxml(encoding='utf-8', indent='  ')[:-1])

        Print.debug('\t\tGenerated %s!' % os.path.basename(outf))
        return outf
Exemplo n.º 7
0
 def data(self, index, role=Qt.DisplayRole):
     Print.debug('TableModel data')
     if role == Qt.DisplayRole:
         i = index.row()
         j = index.column()
         row = self.datatable[i]
         if Column(j) == Column.FILE_SIZE:
             return _format_size(row[Column(j)])
         return f"{row[Column(j)]}"
     else:
         return QtCore.QVariant()
Exemplo n.º 8
0
def poll_commands(in_ep, out_ep):
    p = Packet(in_ep, out_ep)
    while True:
        if p.recv(0):
            if p.command == 1:
                Print.debug('Recv command! %d' % p.command)
                req = UsbRequest(p.payload.decode('utf-8'))
                with UsbResponse(p) as resp:
                    Server.route(req, resp)
            else:
                Print.error('Unknown command! %d' % p.command)
        else:
            Print.error('failed to read!')
Exemplo n.º 9
0
    def repack(self):
        Print.debug('\tRepacking to NSP...')

        hd = self.gen_header()

        totSize = len(hd) + sum(os.path.getsize(file) for file in self.files)
        if os.path.exists(self.path) and os.path.getsize(self.path) == totSize:
            Print.info('\t\tRepack %s is already complete!' % self.path)
            return

        t = Status.create(totSize, unit='B', desc=os.path.basename(self.path))

        Print.debug('\t\tWriting header...')
        outf = open(self.path, 'wb')
        outf.write(hd)
        t.update(len(hd))

        done = 0
        for file in self.files:
            Print.debug('\t\tAppending %s...' % os.path.basename(file))
            with open(file, 'rb') as inf:
                while True:
                    buf = inf.read(4096)
                    if not buf:
                        break
                    outf.write(buf)
                    t.update(len(buf))
        t.close()

        Print.debug('\t\tRepacked to %s!' % outf.name)
        outf.close()
Exemplo n.º 10
0
    def update(self, dataIn):
        Print.debug('TableModel update start')
        self.datatable = []

        for value in dataIn.values():
            new_item = {}
            new_item[Column.FILENAME] = os.path.basename(value.path)
            new_item[Column.TITLE_ID] = str(value.titleId)
            titleType = "UPD" if value.isUpdate() else "DLC" if value.isDLC() \
              else "BASE"
            new_item[Column.TITLE_TYPE] = titleType
            new_item[Column.FILE_SIZE] = value.fileSize
            self.datatable.append(new_item)

        self._sort()
        Print.debug('TableModel update finished')
Exemplo n.º 11
0
def makeRequest(method, url, hdArgs={}, start=None, end=None, accept='*/*'):
    if start is None:
        reqHd = {
            'Accept':
            accept,
            'Connection':
            None,
            'Accept-Encoding':
            None,
            'User-Agent':
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
        }
    else:
        reqHd = {
            'Accept':
            accept,
            'Connection':
            None,
            'Accept-Encoding':
            None,
            'Range':
            'bytes=%d-%d' % (start, end - 1),
            'User-Agent':
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
        }

    reqHd.update(hdArgs)

    r = requests.request(method,
                         url,
                         headers=reqHd,
                         verify=False,
                         stream=True,
                         timeout=15)

    Print.debug('%s %s %s' % (method, str(r.status_code), url))
    Print.debug(r.request.headers)
    #data = dump.dump_all(r)
    #print(data.decode('utf-8'))

    if r.status_code == 403:
        raise IOError('Forbidden ' + r.text)

    return r
Exemplo n.º 12
0
def verify_NCA(ncaFile, titleKey):
    if not titleKey:
        return False

    commandLine = hactoolPath + ' "' + ncaFile + '"' + keysArg + ' --titlekey="' + titleKey + '"'

    try:
        output = str(
            subprocess.check_output(commandLine,
                                    stderr=subprocess.STDOUT,
                                    shell=True))
    except subprocess.CalledProcessError as exc:
        Print.error("Status : FAIL" + str(exc.returncode) + ', ' +
                    str(exc.output))
        return False
    else:
        if "Error: section 0 is corrupted!" in output or "Error: section 1 is corrupted!" in output:
            Print.error("\nNCA Verification failed. Probably a bad titlekey.")
            return False
    Print.debug("\nTitlekey verification successful.")
    return True
Exemplo n.º 13
0
def reload(file_name=DEFAULT_TRANSLATION_FILE):
    global _initialized
    global _lang_db
    global _en_db
    global _lang
    global _locale
    global _file_name

    _file_name = file_name
    _lang = ENGLISH_LANG_ID if Config.language is None or Config.language == "en" else Config.language
    Print.debug(f"translation file is '{_file_name}'")
    Print.debug(f"_lang is '{_lang}'")
    try:
        with open(_file_name, encoding='utf-8') as json_file:
            data = json.load(json_file)
            _en_db = data[ENGLISH_LANG_ID]
            if _lang != ENGLISH_LANG_ID:
                _lang_db = data[_lang]
        _initialized = True
    except (FileNotFoundError, ValueError):
        Print.warning(
            f"missing translation file '{_file_name}' or it's not a JSON-file")
        _initialized = False
    Print.debug(f"_initialized is '{_initialized}'")
    return _initialized
Exemplo n.º 14
0
def download_game(titleId,
                  ver,
                  tkey=None,
                  nspRepack=False,
                  name='',
                  verify=False):
    name = get_name(titleId)
    gameType = ''

    if name == 'Unknown Title':
        temp = "[" + titleId + "]"
    else:
        temp = name + " [" + titleId + "]"

    outputDir = os.path.join(os.path.dirname(__file__), nspout)

    basetitleId = ''
    if titleId.endswith('000'):  # Base game
        gameDir = os.path.join(outputDir, temp)
        gameType = 'BASE'
    elif titleId.endswith('800'):  # Update
        basetitleId = '%s000' % titleId[:-3]
        gameDir = os.path.join(outputDir, temp)
        gameType = 'UPD'
    else:  # DLC
        basetitleId = '%s%s000' % (titleId[:-4], str(int(titleId[-4], 16) - 1))
        gameDir = os.path.join(outputDir, temp)
        gameType = 'DLC'

    os.makedirs(gameDir, exist_ok=True)

    if not os.path.exists(outputDir):
        os.makedirs(outputDir, exist_ok=True)

    if name != "":
        if gameType == 'BASE':
            outf = os.path.join(outputDir,
                                '%s [%s][v%s]' % (name, titleId, ver))
        else:
            outf = os.path.join(
                outputDir, '%s [%s][%s][v%s]' % (name, gameType, titleId, ver))
    else:
        if gameType == 'BASE':
            outf = os.path.join(outputDir, '%s [v%s]' % (titleId, ver))
        else:
            outf = os.path.join(outputDir,
                                '%s [%s][v%s]' % (titleId, gameType, ver))

    if truncateName:
        name = name.replace(' ', '')[0:20]
        outf = os.path.join(outputDir, '%s%sv%s' % (name, titleId, ver))

    if tkey:
        outf = outf + '.nsp'
    else:
        outf = outf + '.nsx'

    for item in os.listdir(outputDir):
        if item.find('%s' % titleId) != -1:
            if item.find('v%s' % ver) != -1:
                Print.info('%s already exists, skipping download' % outf)
                shutil.rmtree(gameDir)
                return

    files = download_title(gameDir,
                           titleId,
                           ver,
                           tkey,
                           nspRepack,
                           verify=verify)

    if gameType != 'UPD':
        if tkey:
            verified = verify_NCA(get_biggest_file(gameDir), tkey)

            if not verified:
                shutil.rmtree(gameDir)
                Print.debug('cleaned up downloaded content')
                return

    if nspRepack == True:
        if files == None:
            return
        NSP = nsp(outf, files)
        Print.debug('starting repack, This may take a while')
        NSP.repack()
        shutil.rmtree(gameDir)
        Print.debug('cleaned up downloaded content')

        if enxhop:
            enxhopDir = os.path.join(outputDir, 'switch')
            os.makedirs(enxhopDir, exist_ok=True)
            with open(os.path.join(enxhopDir, 'eNXhop.txt'), 'a+') as f:
                f.write(titleId + '\n')

        return outf

    return gameDir
Exemplo n.º 15
0
def download_title(gameDir,
                   titleId,
                   ver,
                   tkey=None,
                   nspRepack=False,
                   n='',
                   verify=False,
                   retry=0):
    try:
        Print.info('Downloading %s [%s] v%s:' %
                   (get_name(titleId), titleId, ver))
        titleId = titleId.lower()
        isNsx = False

        if tkey:
            tkey = tkey.lower()

        if len(titleId) != 16:
            titleId = (16 - len(titleId)) * '0' + titleId

        url = 'https://atum%s.hac.%s.d4c.nintendo.net/t/a/%s/%s?device_id=%s' % (
            n, env, titleId, ver, deviceId)
        Print.debug(url)
        try:
            r = make_request('HEAD', url)
        except Exception as e:
            Print.error(
                "Error downloading title. Check for incorrect titleid or version: "
                + str(e))
            return
        CNMTid = r.headers.get('X-Nintendo-Content-ID')

        if CNMTid == None:
            Print.info('title not available on CDN')
            return

        Print.debug('Downloading CNMT (%s.cnmt.nca)...' % CNMTid)
        url = 'https://atum%s.hac.%s.d4c.nintendo.net/c/a/%s?device_id=%s' % (
            n, env, CNMTid, deviceId)
        fPath = os.path.join(gameDir, CNMTid + '.cnmt.nca')
        cnmtNCA = download_file(url, fPath, titleId)
        cnmtDir = decrypt_NCA(cnmtNCA)
        CNMT = cnmt(
            os.path.join(cnmtDir, 'section0',
                         os.listdir(os.path.join(cnmtDir, 'section0'))[0]),
            os.path.join(cnmtDir, 'Header.bin'))

        if nspRepack == True:
            outf = os.path.join(
                gameDir, '%s.xml' % os.path.basename(cnmtNCA.strip('.nca')))
            cnmtXML = CNMT.gen_xml(cnmtNCA, outf)

            rightsID = '%s%s%s' % (titleId, (16 - len(CNMT.mkeyrev)) * '0',
                                   CNMT.mkeyrev)

            tikPath = os.path.join(gameDir, '%s.tik' % rightsID)
            certPath = os.path.join(gameDir, '%s.cert' % rightsID)
            if CNMT.type == 'Application' or CNMT.type == 'AddOnContent':
                shutil.copy(
                    os.path.join(os.path.dirname(__file__),
                                 'Certificate.cert'), certPath)

                if tkey:
                    with open(
                            os.path.join(os.path.dirname(__file__),
                                         'Ticket.tik'), 'rb') as intik:
                        data = bytearray(intik.read())
                        data[0x180:0x190] = uhx(tkey)
                        data[0x285] = int(CNMT.mkeyrev)
                        data[0x2A0:0x2B0] = uhx(rightsID)

                        with open(tikPath, 'wb') as outtik:
                            outtik.write(data)
                else:
                    isNsx = True
                    with open(
                            os.path.join(os.path.dirname(__file__),
                                         'Ticket.tik'), 'rb') as intik:
                        data = bytearray(intik.read())
                        data[0x180:0x190] = uhx(
                            '00000000000000000000000000000000')
                        data[0x285] = int(CNMT.mkeyrev)
                        data[0x2A0:0x2B0] = uhx(rightsID)

                        with open(tikPath, 'wb') as outtik:
                            outtik.write(data)

                Print.debug(
                    'Generated %s and %s!' %
                    (os.path.basename(certPath), os.path.basename(tikPath)))
            elif CNMT.type == 'Patch':
                Print.debug('Downloading cetk...')

                with open(
                        download_cetk(
                            rightsID,
                            os.path.join(gameDir, '%s.cetk' % rightsID)),
                        'rb') as cetk:
                    cetk.seek(0x180)
                    tkey = hx(cetk.read(0x10)).decode()
                    Print.info('Titlekey: %s' % tkey)

                    with open(tikPath, 'wb') as tik:
                        cetk.seek(0x0)
                        tik.write(cetk.read(0x2C0))

                    with open(certPath, 'wb') as cert:
                        cetk.seek(0x2C0)
                        cert.write(cetk.read(0x700))

                Print.debug(
                    'Extracted %s and %s from cetk!' %
                    (os.path.basename(certPath), os.path.basename(tikPath)))

        NCAs = {
            0: [],
            1: [],
            2: [],
            3: [],
            4: [],
            5: [],
            6: [],
        }
        for type in [0, 3, 4, 5, 1, 2, 6]:  # Download smaller files first
            for ncaID in CNMT.parse(CNMT.ncaTypes[type]):
                Print.debug('Downloading %s entry (%s.nca)...' %
                            (CNMT.ncaTypes[type], ncaID))
                url = 'https://atum%s.hac.%s.d4c.nintendo.net/c/c/%s?device_id=%s' % (
                    n, env, ncaID, deviceId)
                fPath = os.path.join(gameDir, ncaID + '.nca')
                NCAs[type].append(download_file(url, fPath, titleId))
                if verify:
                    if calc_sha256(fPath) != CNMT.parse(
                            CNMT.ncaTypes[type])[ncaID][2]:
                        os.remove(fPath)
                        raise BaseException(
                            '%s is corrupted, hashes don\'t match!' %
                            os.path.basename(fPath))
                    else:
                        Print.info('Verified %s...' % os.path.basename(fPath))

        if nspRepack == True:
            files = []
            files.append(certPath)
            files.append(tikPath)
            for key in [1, 5, 2, 4, 6]:
                files.extend(NCAs[key])
            files.append(cnmtNCA)
            files.append(cnmtXML)
            files.extend(NCAs[3])
            return files
    except KeyboardInterrupt:
        raise
    except BaseException as e:
        retry += 1

        if retry < 5:
            Print.error(
                'An error occured while downloading, retry attempt %d: %s' %
                (retry, str(e)))
            download_title(gameDir, titleId, ver, tkey, nspRepack, n, verify,
                           retry)
        else:
            raise
Exemplo n.º 16
0
 def flags(self, index):
     Print.debug('TableModel flags')
     return Qt.ItemIsEnabled
Exemplo n.º 17
0
 def columnCount(self, parent=QtCore.QModelIndex()):
     Print.debug('TableModel columnCount')
     return self.column_count
Exemplo n.º 18
0
 def rowCount(self, parent=QtCore.QModelIndex()):
     Print.debug('TableModel rowCount')
     return len(self.datatable)